Commit c33f944a authored by Jakub Kicinski's avatar Jakub Kicinski
Browse files

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: default avatarJakub Kicinski <kuba@kernel.org>
parents bdd39576 9e68817f
Loading
Loading
Loading
Loading
+12 −3
Original line number Diff line number Diff line
@@ -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
+58 −48
Original line number Diff line number Diff line
@@ -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;

	psiier = enetc_rd(hw, ENETC_PSIIER) | ENETC_PSIMR_MASK(pf->num_vfs);

	enetc_wr(hw, ENETC_PSIIER, psiier | ENETC_PSIIER_MR_MASK);
	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;

			if (!(ENETC_PSIMSGRR_MR(i) & mr_mask))
		if (!(ENETC_PSIMR_BIT(i) & mr_status))
			continue;

		enetc_msg_handle_rxmsg(pf, i, &msg_code);

		/* 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_PSIMSGRR_MR(i); /* w1c */
		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;

	/* disable MR interrupts */
	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);

	/* disable MR interrupts */
	enetc_msg_disable_mr_int(&si->hw);
	/* 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);

	/* de-register message passing interrupt handler */
	free_irq(pci_irq_vector(si->pdev, ENETC_SI_INT_IDX), si);
}
+56 −19
Original line number Diff line number Diff line
@@ -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",
	if (!is_valid_ether_addr(addr)) {
		dev_err_ratelimited(dev, "VF%d attempted to set invalid MAC\n",
				    vf_id);
	else
		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",
		*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,10 +992,15 @@ 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);
	if (err)
		goto err_setup_mac_addresses;
+1 −0
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@ enum enetc_vf_flags {
};

struct enetc_vf_state {
	struct mutex lock; /* Prevent concurrent access */
	enum enetc_vf_flags flags;
};