Merge branch 'net-enetc-sr-iov-robustness-and-security-fixes'

Wei Fang says:

====================
net: enetc: SR-IOV robustness and security fixes

This patch series addresses a number of robustness, security, and
correctness issues in the ENETC driver's SR-IOV subsystem, focusing
primarily on the VF-to-PF mailbox communication path.

The series can be grouped into the following categories:

1. DoS and security fixes:
   - Prevent an unbounded loop DoS in the VF-to-PF message handler,
     which could be triggered by a malicious or misbehaving VF.
   - Fix a TOCTOU (Time-of-Check-Time-of-Use) race and add proper
     validation of VF MAC addresses to prevent spoofing or invalid
     configuration from being applied.

2. Race condition fixes:
   - Fix a race condition in VF MAC address configuration that could
     lead to inconsistent state between the VF request and PF
     application.
   - Fix a race condition during SR-IOV teardown that could cause
     VF->PF mailbox operations to time out, resulting in unnecessary
     errors during shutdown.

3. Memory safety fixes:
   - Fix a DMA write to freed memory in enetc_msg_free_mbx(), which
     could cause silent memory corruption or system instability.

4. Error handling and initialization fixes:
   - Fix missing error code propagation when pf->vf_state allocation
     fails, ensuring callers receive a proper errno instead of
     succeeding silently.
   - Fix incorrect mailbox message status values returned to VFs,
     which could cause VFs to misinterpret PF responses.
   - Fix initialization order to prevent the use of uninitialized
     resources during driver probe, which could cause undefined
     behavior on certain configurations.

5. Diagnostics improvement:
   - Add rate limiting to VF mailbox error messages to prevent log
     flooding in the presence of a misbehaving VF.

These fixes improve the overall stability and security of the ENETC
SR-IOV implementation, particularly in multi-tenant environments where
VFs may be assigned to untrusted guests.
====================

Link: https://patch.msgid.link/20260520064421.91569-1-wei.fang@nxp.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-05-21 08:49:01 -07:00
commit c33f944a33
4 changed files with 128 additions and 71 deletions

View File

@ -56,11 +56,21 @@ static inline u32 enetc_vsi_set_msize(u32 size)
}
#define ENETC_PSIMSGRR 0x204
#define ENETC_PSIMSGRR_MR_MASK GENMASK(2, 1)
#define ENETC_PSIMSGRR_MR(n) BIT((n) + 1) /* n = VSI index */
#define ENETC_PSIVMSGRCVAR0(n) (0x210 + (n) * 0x8) /* n = VSI index */
#define ENETC_PSIVMSGRCVAR1(n) (0x214 + (n) * 0x8)
/* Message received mask, n is the active number of VSIs.
* It is available for ENETC_PSIMSGRR, ENETC_PSIIER, and
* ENETC_PSIIDR registers.
*/
#define ENETC_PSIMR_MASK(n) \
({ typeof(n) _n = (n); (_n) ? GENMASK((_n), 1) : 0; })
/* Message received bit, n is VSI index. It is available for
* ENETC_PSIMSGRR, ENETC_PSIIER, and ENETC_PSIIDR registers.
*/
#define ENETC_PSIMR_BIT(n) BIT((n) + 1)
#define ENETC_VSIMSGSR 0x204 /* RO */
#define ENETC_VSIMSGSR_MB BIT(0)
#define ENETC_VSIMSGSR_MS BIT(1)
@ -94,7 +104,6 @@ static inline u32 enetc_vsi_set_msize(u32 size)
#define ENETC_SICAPR1 0x904
#define ENETC_PSIIER 0xa00
#define ENETC_PSIIER_MR_MASK GENMASK(2, 1)
#define ENETC_PSIIDR 0xa08
#define ENETC_SITXIDR 0xa18
#define ENETC_SIRXIDR 0xa28

View File

@ -3,18 +3,25 @@
#include "enetc_pf.h"
static void enetc_msg_disable_mr_int(struct enetc_hw *hw)
static void enetc_msg_disable_mr_int(struct enetc_pf *pf)
{
u32 psiier = enetc_rd(hw, ENETC_PSIIER);
struct enetc_hw *hw = &pf->si->hw;
u32 psiier;
psiier = enetc_rd(hw, ENETC_PSIIER) & ~ENETC_PSIMR_MASK(pf->num_vfs);
/* disable MR int source(s) */
enetc_wr(hw, ENETC_PSIIER, psiier & ~ENETC_PSIIER_MR_MASK);
enetc_wr(hw, ENETC_PSIIER, psiier);
}
static void enetc_msg_enable_mr_int(struct enetc_hw *hw)
static void enetc_msg_enable_mr_int(struct enetc_pf *pf)
{
u32 psiier = enetc_rd(hw, ENETC_PSIIER);
struct enetc_hw *hw = &pf->si->hw;
u32 psiier;
enetc_wr(hw, ENETC_PSIIER, psiier | ENETC_PSIIER_MR_MASK);
psiier = enetc_rd(hw, ENETC_PSIIER) | ENETC_PSIMR_MASK(pf->num_vfs);
enetc_wr(hw, ENETC_PSIIER, psiier);
}
static irqreturn_t enetc_msg_psi_msix(int irq, void *data)
@ -22,7 +29,7 @@ static irqreturn_t enetc_msg_psi_msix(int irq, void *data)
struct enetc_si *si = (struct enetc_si *)data;
struct enetc_pf *pf = enetc_si_priv(si);
enetc_msg_disable_mr_int(&si->hw);
enetc_msg_disable_mr_int(pf);
schedule_work(&pf->msg_task);
return IRQ_HANDLED;
@ -31,33 +38,35 @@ static irqreturn_t enetc_msg_psi_msix(int irq, void *data)
static void enetc_msg_task(struct work_struct *work)
{
struct enetc_pf *pf = container_of(work, struct enetc_pf, msg_task);
u32 mr_mask = ENETC_PSIMR_MASK(pf->num_vfs);
struct enetc_hw *hw = &pf->si->hw;
unsigned long mr_mask;
u32 mr_status;
int i;
for (;;) {
mr_mask = enetc_rd(hw, ENETC_PSIMSGRR) & ENETC_PSIMSGRR_MR_MASK;
if (!mr_mask) {
/* re-arm MR interrupts, w1c the IDR reg */
enetc_wr(hw, ENETC_PSIIDR, ENETC_PSIIER_MR_MASK);
enetc_msg_enable_mr_int(hw);
return;
}
mr_status = (enetc_rd(hw, ENETC_PSIMSGRR) & mr_mask) |
(enetc_rd(hw, ENETC_PSIIDR) & mr_mask);
if (!mr_status)
goto out;
for (i = 0; i < pf->num_vfs; i++) {
u32 psimsgrr;
u16 msg_code;
for (i = 0; i < pf->num_vfs; i++) {
u32 psimsgrr;
u16 msg_code;
if (!(ENETC_PSIMSGRR_MR(i) & mr_mask))
continue;
if (!(ENETC_PSIMR_BIT(i) & mr_status))
continue;
enetc_msg_handle_rxmsg(pf, i, &msg_code);
enetc_msg_handle_rxmsg(pf, i, &msg_code);
psimsgrr = ENETC_SIMSGSR_SET_MC(msg_code);
psimsgrr |= ENETC_PSIMSGRR_MR(i); /* w1c */
enetc_wr(hw, ENETC_PSIMSGRR, psimsgrr);
}
/* w1c to clear the corresponding VF MR bit */
enetc_wr(hw, ENETC_PSIIDR, ENETC_PSIMR_BIT(i));
psimsgrr = ENETC_SIMSGSR_SET_MC(msg_code);
psimsgrr |= ENETC_PSIMR_BIT(i); /* w1c */
enetc_wr(hw, ENETC_PSIMSGRR, psimsgrr);
}
out:
enetc_msg_enable_mr_int(pf);
}
/* Init */
@ -96,12 +105,12 @@ static void enetc_msg_free_mbx(struct enetc_si *si, int idx)
struct enetc_hw *hw = &si->hw;
struct enetc_msg_swbd *msg;
enetc_wr(hw, ENETC_PSIVMSGRCVAR0(idx), 0);
enetc_wr(hw, ENETC_PSIVMSGRCVAR1(idx), 0);
msg = &pf->rxmsg[idx];
dma_free_coherent(&si->pdev->dev, msg->size, msg->vaddr, msg->dma);
memset(msg, 0, sizeof(*msg));
enetc_wr(hw, ENETC_PSIVMSGRCVAR0(idx), 0);
enetc_wr(hw, ENETC_PSIVMSGRCVAR1(idx), 0);
}
int enetc_msg_psi_init(struct enetc_pf *pf)
@ -109,6 +118,15 @@ int enetc_msg_psi_init(struct enetc_pf *pf)
struct enetc_si *si = pf->si;
int vector, i, err;
for (i = 0; i < pf->num_vfs; i++) {
err = enetc_msg_alloc_mbx(si, i);
if (err)
goto free_mbx;
}
/* initialize PSI mailbox */
INIT_WORK(&pf->msg_task, enetc_msg_task);
/* register message passing interrupt handler */
snprintf(pf->msg_int_name, sizeof(pf->msg_int_name), "%s-vfmsg",
si->ndev->name);
@ -117,32 +135,21 @@ int enetc_msg_psi_init(struct enetc_pf *pf)
if (err) {
dev_err(&si->pdev->dev,
"PSI messaging: request_irq() failed!\n");
return err;
goto free_mbx;
}
/* set one IRQ entry for PSI message receive notification (SI int) */
enetc_wr(&si->hw, ENETC_SIMSIVR, ENETC_SI_INT_IDX);
/* initialize PSI mailbox */
INIT_WORK(&pf->msg_task, enetc_msg_task);
for (i = 0; i < pf->num_vfs; i++) {
err = enetc_msg_alloc_mbx(si, i);
if (err)
goto err_init_mbx;
}
/* enable MR interrupts */
enetc_msg_enable_mr_int(&si->hw);
enetc_msg_enable_mr_int(pf);
return 0;
err_init_mbx:
free_mbx:
for (i--; i >= 0; i--)
enetc_msg_free_mbx(si, i);
free_irq(vector, si);
return err;
}
@ -151,14 +158,17 @@ void enetc_msg_psi_free(struct enetc_pf *pf)
struct enetc_si *si = pf->si;
int i;
cancel_work_sync(&pf->msg_task);
/* disable MR interrupts */
enetc_msg_disable_mr_int(&si->hw);
for (i = 0; i < pf->num_vfs; i++)
enetc_msg_free_mbx(si, i);
enetc_msg_disable_mr_int(pf);
/* de-register message passing interrupt handler */
free_irq(pci_irq_vector(si->pdev, ENETC_SI_INT_IDX), si);
cancel_work_sync(&pf->msg_task);
/* MR interrupts may be re-enabled by workqueue */
enetc_msg_disable_mr_int(pf);
for (i = 0; i < pf->num_vfs; i++)
enetc_msg_free_mbx(si, i);
}

View File

@ -252,8 +252,12 @@ static int enetc_pf_set_vf_mac(struct net_device *ndev, int vf, u8 *mac)
return -EADDRNOTAVAIL;
vf_state = &pf->vf_state[vf];
mutex_lock(&vf_state->lock);
vf_state->flags |= ENETC_VF_FLAG_PF_SET_MAC;
enetc_pf_set_primary_mac_addr(&priv->si->hw, vf + 1, mac);
mutex_unlock(&vf_state->lock);
return 0;
}
@ -478,49 +482,77 @@ static void enetc_configure_port(struct enetc_pf *pf)
/* Messaging */
static u16 enetc_msg_pf_set_vf_primary_mac_addr(struct enetc_pf *pf,
int vf_id)
int vf_id, void *msg)
{
struct enetc_vf_state *vf_state = &pf->vf_state[vf_id];
struct enetc_msg_swbd *msg = &pf->rxmsg[vf_id];
struct enetc_msg_cmd_set_primary_mac *cmd;
struct enetc_msg_cmd_set_primary_mac *cmd = msg;
struct device *dev = &pf->si->pdev->dev;
u16 cmd_id;
u16 cmd_id = cmd->header.id;
char *addr;
cmd = (struct enetc_msg_cmd_set_primary_mac *)msg->vaddr;
cmd_id = cmd->header.id;
if (cmd_id != ENETC_MSG_CMD_MNG_ADD)
return ENETC_MSG_CMD_STATUS_FAIL;
addr = cmd->mac.sa_data;
if (vf_state->flags & ENETC_VF_FLAG_PF_SET_MAC)
dev_warn(dev, "Attempt to override PF set mac addr for VF%d\n",
vf_id);
else
enetc_pf_set_primary_mac_addr(&pf->si->hw, vf_id + 1, addr);
if (!is_valid_ether_addr(addr)) {
dev_err_ratelimited(dev, "VF%d attempted to set invalid MAC\n",
vf_id);
return ENETC_MSG_CMD_STATUS_FAIL;
}
mutex_lock(&vf_state->lock);
if (vf_state->flags & ENETC_VF_FLAG_PF_SET_MAC) {
mutex_unlock(&vf_state->lock);
dev_err_ratelimited(dev,
"VF%d attempted to override PF set MAC\n",
vf_id);
return ENETC_MSG_CMD_STATUS_FAIL;
}
enetc_pf_set_primary_mac_addr(&pf->si->hw, vf_id + 1, addr);
mutex_unlock(&vf_state->lock);
return ENETC_MSG_CMD_STATUS_OK;
}
void enetc_msg_handle_rxmsg(struct enetc_pf *pf, int vf_id, u16 *status)
{
struct enetc_msg_swbd *msg = &pf->rxmsg[vf_id];
struct enetc_msg_swbd *msg_swbd = &pf->rxmsg[vf_id];
struct device *dev = &pf->si->pdev->dev;
struct enetc_msg_cmd_header *cmd_hdr;
u16 cmd_type;
u8 *msg;
*status = ENETC_MSG_CMD_STATUS_OK;
cmd_hdr = (struct enetc_msg_cmd_header *)msg->vaddr;
msg = kzalloc_objs(*msg, msg_swbd->size);
if (!msg) {
dev_err_ratelimited(dev,
"Failed to allocate message buffer\n");
*status = ENETC_MSG_CMD_STATUS_FAIL;
return;
}
/* Currently, only ENETC_MSG_CMD_MNG_MAC command is supported, so
* only sizeof(struct enetc_msg_cmd_set_primary_mac) bytes need to
* be copied. This data already includes the cmd_type field, so it
* can correctly return an error code.
*/
memcpy(msg, msg_swbd->vaddr,
sizeof(struct enetc_msg_cmd_set_primary_mac));
cmd_hdr = (struct enetc_msg_cmd_header *)msg;
cmd_type = cmd_hdr->type;
switch (cmd_type) {
case ENETC_MSG_CMD_MNG_MAC:
*status = enetc_msg_pf_set_vf_primary_mac_addr(pf, vf_id);
*status = enetc_msg_pf_set_vf_primary_mac_addr(pf, vf_id, msg);
break;
default:
dev_err(dev, "command not supported (cmd_type: 0x%x)\n",
cmd_type);
*status = ENETC_MSG_CMD_STATUS_FAIL;
dev_err_ratelimited(dev,
"command not supported (cmd_type: 0x%x)\n",
cmd_type);
}
kfree(msg);
}
#ifdef CONFIG_PCI_IOV
@ -531,9 +563,9 @@ static int enetc_sriov_configure(struct pci_dev *pdev, int num_vfs)
int err;
if (!num_vfs) {
pci_disable_sriov(pdev);
enetc_msg_psi_free(pf);
pf->num_vfs = 0;
pci_disable_sriov(pdev);
} else {
pf->num_vfs = num_vfs;
@ -960,8 +992,13 @@ static int enetc_pf_probe(struct pci_dev *pdev,
if (pf->total_vfs) {
pf->vf_state = kzalloc_objs(struct enetc_vf_state,
pf->total_vfs);
if (!pf->vf_state)
if (!pf->vf_state) {
err = -ENOMEM;
goto err_alloc_vf_state;
}
for (int i = 0; i < pf->total_vfs; i++)
mutex_init(&pf->vf_state[i].lock);
}
err = enetc_setup_mac_addresses(node, pf);

View File

@ -14,6 +14,7 @@ enum enetc_vf_flags {
};
struct enetc_vf_state {
struct mutex lock; /* Prevent concurrent access */
enum enetc_vf_flags flags;
};