git: e779914354db - main - ixl: Validate VF virtchnl configuration

Kevin Bowling <[email protected]>
Newsgroups gmane.os.freebsd.devel.cvs.src
Message-ID <6a799781.3100e.142376f8__35074.9386868969$1786353560$gmane$org@gitrepo.freebsd.org>
The branch main has been updated by kbowling:

URL: https://cgit.FreeBSD.org/src/commit/?id=e779914354db5cb3a20f4ec894d08b0f81d05ba2

commit e779914354db5cb3a20f4ec894d08b0f81d05ba2
Author:     Kevin Bowling <[email protected]>
AuthorDate: 2026-08-10 03:15:17 +0000
Commit:     Kevin Bowling <[email protected]>
CommitDate: 2026-08-10 09:17:25 +0000

    ixl: Validate VF virtchnl configuration
    
    Bound variable-length virtchnl messages before computing their expected
    length, following the newer Intel virtchnl implementation.
    
    Validate VF ring sizes and alignments before programming HMC contexts.
    DPDK uses 128-byte ring alignment and 64 through 8160 descriptors;
    the virtchnl ABI further specifies TX multiples of 8 and RX multiples
    of 32.  Preserve the 4096-descriptor limit on X722.
    
    Validate queue bitmaps before changing any rings, validate all queue
    and interrupt contexts before applying a request, and reject invalid
    RSS table entries.  Also avoid sending an ACK after VLAN-strip setup
    fails and reply to delete-VLAN errors with the correct opcode.
    
    These checks prevent malformed or oversized requests from an untrusted
    VF from partially programming resources outside its allocation.
    
    MFC after:      2 weeks
---
 sys/dev/ixl/ixl_pf_iov.c | 156 ++++++++++++++++++++++++++++++++---------------
 sys/dev/ixl/virtchnl.h   |  70 +++++++++++++++++----
 2 files changed, 165 insertions(+), 61 deletions(-)

diff --git a/sys/dev/ixl/ixl_pf_iov.c b/sys/dev/ixl/ixl_pf_iov.c
index e749797fa700..b360955e25b2 100644
--- a/sys/dev/ixl/ixl_pf_iov.c
+++ b/sys/dev/ixl/ixl_pf_iov.c
@@ -58,6 +58,8 @@ static void	ixl_vf_reset_msg(struct ixl_pf *pf, struct ixl_vf *vf, void *msg, ui
 static void	ixl_vf_get_resources_msg(struct ixl_pf *pf, struct ixl_vf *vf, void *msg, uint16_t msg_size);
 static int	ixl_vf_config_tx_queue(struct ixl_pf *pf, struct ixl_vf *vf, struct virtchnl_txq_info *info);
 static int	ixl_vf_config_rx_queue(struct ixl_pf *pf, struct ixl_vf *vf, struct virtchnl_rxq_info *info);
+static bool	ixl_vf_tx_queue_valid(struct ixl_pf *pf, const struct virtchnl_txq_info *info);
+static bool	ixl_vf_rx_queue_valid(struct ixl_pf *pf, const struct virtchnl_rxq_info *info);
 static void	ixl_vf_config_vsi_msg(struct ixl_pf *pf, struct ixl_vf *vf, void *msg, uint16_t msg_size);
 static void	ixl_vf_set_qctl(struct ixl_pf *pf, const struct virtchnl_vector_map *vector, enum i40e_queue_type cur_type, uint16_t cur_queue,
     enum i40e_queue_type *last_type, uint16_t *last_queue);
@@ -77,6 +79,8 @@ static int	ixl_config_pf_vsi_loopback(struct ixl_pf *pf, bool enable);
 
 static int	ixl_adminq_err_to_errno(enum i40e_admin_queue_err err);
 
+#define IXL_VF_MAX_RING_XL710	8160
+
 /*
  * TODO: Move pieces of this into iflib and call the rest in a handler?
  *
@@ -599,6 +603,33 @@ ixl_vf_config_tx_queue(struct ixl_pf *pf, struct ixl_vf *vf,
 	return (0);
 }
 
+static uint32_t
+ixl_vf_max_ring(const struct i40e_hw *hw)
+{
+
+	if (hw->mac.type == I40E_MAC_XL710)
+		return (IXL_VF_MAX_RING_XL710);
+	return (IXL_MAX_RING);
+}
+
+static bool
+ixl_vf_tx_queue_valid(struct ixl_pf *pf,
+    const struct virtchnl_txq_info *info)
+{
+
+	if (info->ring_len < IXL_MIN_RING ||
+	    info->ring_len > ixl_vf_max_ring(&pf->hw) ||
+	    info->ring_len % 8 != 0 ||
+	    info->dma_ring_addr == 0 ||
+	    info->dma_ring_addr % IXL_TX_CTX_BASE_UNITS != 0 ||
+	    info->headwb_enabled > 1)
+		return (false);
+	if (info->headwb_enabled != 0 &&
+	    (info->dma_headwb_addr == 0 || info->dma_headwb_addr % 4 != 0))
+		return (false);
+	return (true);
+}
+
 static int
 ixl_vf_config_rx_queue(struct ixl_pf *pf, struct ixl_vf *vf,
     struct virtchnl_rxq_info *info)
@@ -666,6 +697,33 @@ ixl_vf_config_rx_queue(struct ixl_pf *pf, struct ixl_vf *vf,
 	return (0);
 }
 
+static bool
+ixl_vf_rx_queue_valid(struct ixl_pf *pf,
+    const struct virtchnl_rxq_info *info)
+{
+
+	if (info->ring_len < IXL_MIN_RING ||
+	    info->ring_len > ixl_vf_max_ring(&pf->hw) ||
+	    info->ring_len % IXL_RING_INCREMENT != 0 ||
+	    info->dma_ring_addr == 0 ||
+	    info->dma_ring_addr % IXL_RX_CTX_BASE_UNITS != 0)
+		return (false);
+	if (info->databuffer_size == 0 ||
+	    info->databuffer_size > IXL_VF_MAX_BUFFER ||
+	    info->databuffer_size % (1U << I40E_RXQ_CTX_DBUFF_SHIFT) != 0)
+		return (false);
+	if (info->max_pkt_size > IXL_VF_MAX_FRAME ||
+	    info->max_pkt_size < ETHER_MIN_LEN)
+		return (false);
+	if (info->splithdr_enabled > 1)
+		return (false);
+	if (info->splithdr_enabled != 0 &&
+	    (info->hdr_size == 0 || info->hdr_size > IXL_VF_MAX_HDR_BUFFER ||
+	    info->hdr_size % (1U << I40E_RXQ_CTX_HBUFF_SHIFT) != 0))
+		return (false);
+	return (true);
+}
+
 static void
 ixl_vf_config_vsi_msg(struct ixl_pf *pf, struct ixl_vf *vf, void *msg,
     uint16_t msg_size)
@@ -697,12 +755,18 @@ ixl_vf_config_vsi_msg(struct ixl_pf *pf, struct ixl_vf *vf, void *msg,
 		if (pair->txq.vsi_id != vf->vsi.vsi_num ||
 		    pair->rxq.vsi_id != vf->vsi.vsi_num ||
 		    pair->txq.queue_id != pair->rxq.queue_id ||
-		    pair->txq.queue_id >= vf->vsi.num_tx_queues) {
+		    pair->txq.queue_id >= vf->vsi.num_tx_queues ||
+		    !ixl_vf_tx_queue_valid(pf, &pair->txq) ||
+		    !ixl_vf_rx_queue_valid(pf, &pair->rxq)) {
 
 			i40e_send_vf_nack(pf, vf,
 			    VIRTCHNL_OP_CONFIG_VSI_QUEUES, I40E_ERR_PARAM);
 			return;
 		}
+	}
+
+	for (i = 0; i < info->num_queue_pairs; i++) {
+		pair = &info->qpair[i];
 
 		if (ixl_vf_config_tx_queue(pf, vf, &pair->txq) != 0) {
 			i40e_send_vf_nack(pf, vf,
@@ -854,9 +918,11 @@ ixl_vf_config_irq_msg(struct ixl_pf *pf, struct ixl_vf *vf, void *msg,
 			return;
 		}
 
-		ixl_vf_config_vector(pf, vf, vector);
 	}
 
+	for (i = 0; i < map->num_vectors; i++)
+		ixl_vf_config_vector(pf, vf, &map->vecmap[i]);
+
 	ixl_send_vf_ack(pf, vf, VIRTCHNL_OP_CONFIG_IRQ_MAP);
 }
 
@@ -865,29 +931,33 @@ ixl_vf_enable_queues_msg(struct ixl_pf *pf, struct ixl_vf *vf, void *msg,
     uint16_t msg_size)
 {
 	struct virtchnl_queue_select *select;
+	uint32_t queue_mask;
 	int error = 0;
 
 	select = msg;
 
+	queue_mask = (1U << vf->qtag.num_active) - 1;
 	if (select->vsi_id != vf->vsi.vsi_num ||
-	    select->rx_queues == 0 || select->tx_queues == 0) {
+	    (select->rx_queues == 0 && select->tx_queues == 0) ||
+	    ((select->rx_queues | select->tx_queues) & ~queue_mask) != 0) {
 		i40e_send_vf_nack(pf, vf, VIRTCHNL_OP_ENABLE_QUEUES,
 		    I40E_ERR_PARAM);
 		return;
 	}
+	for (int i = 0; i < vf->qtag.num_active; i++) {
+		if (((select->tx_queues & (1U << i)) != 0 &&
+		    !ixl_pf_qmgr_is_queue_configured(&vf->qtag, i, true)) ||
+		    ((select->rx_queues & (1U << i)) != 0 &&
+		    !ixl_pf_qmgr_is_queue_configured(&vf->qtag, i, false))) {
+			i40e_send_vf_nack(pf, vf, VIRTCHNL_OP_ENABLE_QUEUES,
+			    I40E_ERR_PARAM);
+			return;
+		}
+	}
 
 	/* Enable TX rings selected by the VF */
-	for (int i = 0; i < 32; i++) {
-		if ((1 << i) & select->tx_queues) {
-			/* Warn if queue is out of VF allocation range */
-			if (i >= vf->vsi.num_tx_queues) {
-				device_printf(pf->dev, "VF %d: TX ring %d is outside of VF VSI allocation!\n",
-				    vf->vf_num, i);
-				break;
-			}
-			/* Skip this queue if it hasn't been configured */
-			if (!ixl_pf_qmgr_is_queue_configured(&vf->qtag, i, true))
-				continue;
+	for (int i = 0; i < vf->qtag.num_active; i++) {
+		if ((1U << i) & select->tx_queues) {
 			/* Warn if this queue is already marked as enabled */
 			if (ixl_pf_qmgr_is_queue_enabled(&vf->qtag, i, true))
 				ixl_dbg_iov(pf, "VF %d: TX ring %d is already enabled!\n",
@@ -902,17 +972,8 @@ ixl_vf_enable_queues_msg(struct ixl_pf *pf, struct ixl_vf *vf, void *msg,
 	}
 
 	/* Enable RX rings selected by the VF */
-	for (int i = 0; i < 32; i++) {
-		if ((1 << i) & select->rx_queues) {
-			/* Warn if queue is out of VF allocation range */
-			if (i >= vf->vsi.num_rx_queues) {
-				device_printf(pf->dev, "VF %d: RX ring %d is outside of VF VSI allocation!\n",
-				    vf->vf_num, i);
-				break;
-			}
-			/* Skip this queue if it hasn't been configured */
-			if (!ixl_pf_qmgr_is_queue_configured(&vf->qtag, i, false))
-				continue;
+	for (int i = 0; i < vf->qtag.num_active; i++) {
+		if ((1U << i) & select->rx_queues) {
 			/* Warn if this queue is already marked as enabled */
 			if (ixl_pf_qmgr_is_queue_enabled(&vf->qtag, i, false))
 				ixl_dbg_iov(pf, "VF %d: RX ring %d is already enabled!\n",
@@ -939,26 +1000,23 @@ ixl_vf_disable_queues_msg(struct ixl_pf *pf, struct ixl_vf *vf,
     void *msg, uint16_t msg_size)
 {
 	struct virtchnl_queue_select *select;
+	uint32_t queue_mask;
 	int error = 0;
 
 	select = msg;
 
+	queue_mask = (1U << vf->qtag.num_active) - 1;
 	if (select->vsi_id != vf->vsi.vsi_num ||
-	    select->rx_queues == 0 || select->tx_queues == 0) {
+	    (select->rx_queues == 0 && select->tx_queues == 0) ||
+	    ((select->rx_queues | select->tx_queues) & ~queue_mask) != 0) {
 		i40e_send_vf_nack(pf, vf, VIRTCHNL_OP_DISABLE_QUEUES,
 		    I40E_ERR_PARAM);
 		return;
 	}
 
 	/* Disable TX rings selected by the VF */
-	for (int i = 0; i < 32; i++) {
-		if ((1 << i) & select->tx_queues) {
-			/* Warn if queue is out of VF allocation range */
-			if (i >= vf->vsi.num_tx_queues) {
-				device_printf(pf->dev, "VF %d: TX ring %d is outside of VF VSI allocation!\n",
-				    vf->vf_num, i);
-				break;
-			}
+	for (int i = 0; i < vf->qtag.num_active; i++) {
+		if ((1U << i) & select->tx_queues) {
 			/* Skip this queue if it hasn't been configured */
 			if (!ixl_pf_qmgr_is_queue_configured(&vf->qtag, i, true))
 				continue;
@@ -977,14 +1035,8 @@ ixl_vf_disable_queues_msg(struct ixl_pf *pf, struct ixl_vf *vf,
 	}
 
 	/* Enable RX rings selected by the VF */
-	for (int i = 0; i < 32; i++) {
-		if ((1 << i) & select->rx_queues) {
-			/* Warn if queue is out of VF allocation range */
-			if (i >= vf->vsi.num_rx_queues) {
-				device_printf(pf->dev, "VF %d: RX ring %d is outside of VF VSI allocation!\n",
-				    vf->vf_num, i);
-				break;
-			}
+	for (int i = 0; i < vf->qtag.num_active; i++) {
+		if ((1U << i) & select->rx_queues) {
 			/* Skip this queue if it hasn't been configured */
 			if (!ixl_pf_qmgr_is_queue_configured(&vf->qtag, i, false))
 				continue;
@@ -1147,6 +1199,7 @@ ixl_vf_add_vlan_msg(struct ixl_pf *pf, struct ixl_vf *vf, void *msg,
 	if (code != I40E_SUCCESS) {
 		i40e_send_vf_nack(pf, vf, VIRTCHNL_OP_ADD_VLAN,
 		    I40E_ERR_PARAM);
+		return;
 	}
 
 	for (i = 0; i < filter_list->num_elements; i++)
@@ -1172,14 +1225,14 @@ ixl_vf_del_vlan_msg(struct ixl_pf *pf, struct ixl_vf *vf, void *msg,
 
 	for (i = 0; i < filter_list->num_elements; i++) {
 		if (filter_list->vlan_id[i] > EVL_VLID_MASK) {
-			i40e_send_vf_nack(pf, vf, VIRTCHNL_OP_ADD_VLAN,
+			i40e_send_vf_nack(pf, vf, VIRTCHNL_OP_DEL_VLAN,
 			    I40E_ERR_PARAM);
 			return;
 		}
 	}
 
 	if (!(vf->vf_flags & VF_FLAG_VLAN_CAP)) {
-		i40e_send_vf_nack(pf, vf, VIRTCHNL_OP_ADD_VLAN,
+		i40e_send_vf_nack(pf, vf, VIRTCHNL_OP_DEL_VLAN,
 		    I40E_ERR_PARAM);
 		return;
 	}
@@ -1271,8 +1324,8 @@ ixl_vf_config_rss_key_msg(struct ixl_pf *pf, struct ixl_vf *vf, void *msg,
 
 	key = msg;
 
-	if (key->key_len > 52) {
-		device_printf(pf->dev, "VF %d: Key size in msg (%d) is greater than max key size (%d)\n",
+	if (key->key_len != 52) {
+		device_printf(pf->dev, "VF %d: Key size in msg (%d) does not match required size (%d)\n",
 		    vf->vf_num, key->key_len, 52);
 		i40e_send_vf_nack(pf, vf, VIRTCHNL_OP_CONFIG_RSS_KEY,
 		    I40E_ERR_PARAM);
@@ -1327,13 +1380,20 @@ ixl_vf_config_rss_lut_msg(struct ixl_pf *pf, struct ixl_vf *vf, void *msg,
 
 	lut = msg;
 
-	if (lut->lut_entries > 64) {
-		device_printf(pf->dev, "VF %d: # of LUT entries in msg (%d) is greater than max (%d)\n",
+	if (lut->lut_entries != 64) {
+		device_printf(pf->dev, "VF %d: # of LUT entries in msg (%d) does not match required size (%d)\n",
 		    vf->vf_num, lut->lut_entries, 64);
 		i40e_send_vf_nack(pf, vf, VIRTCHNL_OP_CONFIG_RSS_LUT,
 		    I40E_ERR_PARAM);
 		return;
 	}
+	for (int i = 0; i < lut->lut_entries; i++) {
+		if (lut->lut[i] >= vf->vsi.num_rx_queues) {
+			i40e_send_vf_nack(pf, vf, VIRTCHNL_OP_CONFIG_RSS_LUT,
+			    I40E_ERR_PARAM);
+			return;
+		}
+	}
 
 	if (lut->vsi_id != vf->vsi.vsi_num) {
 		device_printf(pf->dev, "VF %d: VSI id in recvd message (%d) does not match expected id (%d)\n",
diff --git a/sys/dev/ixl/virtchnl.h b/sys/dev/ixl/virtchnl.h
index f1b5777fd07b..aa8e5862fee3 100644
--- a/sys/dev/ixl/virtchnl.h
+++ b/sys/dev/ixl/virtchnl.h
@@ -594,6 +594,28 @@ enum virtchnl_vfr_states {
 	VIRTCHNL_VFR_VFACTIVE,
 };
 
+/*
+ * Since VF messages are limited by u16 size, bound the number of nested
+ * elements that can be described by a single virtual channel message.
+ */
+enum virtchnl_vector_limits {
+	VIRTCHNL_OP_CONFIG_VSI_QUEUES_MAX =
+	    ((u16)(~0) - sizeof(struct virtchnl_vsi_queue_config_info)) /
+	    sizeof(struct virtchnl_queue_pair_info),
+	VIRTCHNL_OP_CONFIG_IRQ_MAP_MAX =
+	    ((u16)(~0) - sizeof(struct virtchnl_irq_map_info)) /
+	    sizeof(struct virtchnl_vector_map),
+	VIRTCHNL_OP_ADD_DEL_ETH_ADDR_MAX =
+	    ((u16)(~0) - sizeof(struct virtchnl_ether_addr_list)) /
+	    sizeof(struct virtchnl_ether_addr),
+	VIRTCHNL_OP_ADD_DEL_VLAN_MAX =
+	    ((u16)(~0) - sizeof(struct virtchnl_vlan_filter_list)) /
+	    sizeof(u16),
+	VIRTCHNL_OP_CONFIG_IWARP_IRQ_MAP_MAX =
+	    ((u16)(~0) - sizeof(struct virtchnl_iwarp_qvlist_info)) /
+	    sizeof(struct virtchnl_iwarp_qv_info),
+};
+
 /**
  * virtchnl_vc_validate_vf_msg
  * @ver: Virtchnl version info
@@ -608,7 +630,7 @@ virtchnl_vc_validate_vf_msg(struct virtchnl_version_info *ver, u32 v_opcode,
 			    u8 *msg, u16 msglen)
 {
 	bool err_msg_format = FALSE;
-	int valid_len = 0;
+	u32 valid_len = 0;
 
 	/* Validate message length. */
 	switch (v_opcode) {
@@ -632,11 +654,15 @@ virtchnl_vc_validate_vf_msg(struct virtchnl_version_info *ver, u32 v_opcode,
 		if (msglen >= valid_len) {
 			struct virtchnl_vsi_queue_config_info *vqc =
 			    (struct virtchnl_vsi_queue_config_info *)msg;
-			valid_len += (vqc->num_queue_pairs *
-				      sizeof(struct
-					     virtchnl_queue_pair_info));
-			if (vqc->num_queue_pairs == 0)
+			if (vqc->num_queue_pairs == 0 ||
+			    vqc->num_queue_pairs >
+			    VIRTCHNL_OP_CONFIG_VSI_QUEUES_MAX) {
 				err_msg_format = TRUE;
+				break;
+			}
+
+			valid_len += vqc->num_queue_pairs *
+			    sizeof(struct virtchnl_queue_pair_info);
 		}
 		break;
 	case VIRTCHNL_OP_CONFIG_IRQ_MAP:
@@ -644,10 +670,14 @@ virtchnl_vc_validate_vf_msg(struct virtchnl_version_info *ver, u32 v_opcode,
 		if (msglen >= valid_len) {
 			struct virtchnl_irq_map_info *vimi =
 			    (struct virtchnl_irq_map_info *)msg;
-			valid_len += (vimi->num_vectors *
-				      sizeof(struct virtchnl_vector_map));
-			if (vimi->num_vectors == 0)
+			if (vimi->num_vectors == 0 ||
+			    vimi->num_vectors > VIRTCHNL_OP_CONFIG_IRQ_MAP_MAX) {
 				err_msg_format = TRUE;
+				break;
+			}
+
+			valid_len += vimi->num_vectors *
+			    sizeof(struct virtchnl_vector_map);
 		}
 		break;
 	case VIRTCHNL_OP_ENABLE_QUEUES:
@@ -660,10 +690,15 @@ virtchnl_vc_validate_vf_msg(struct virtchnl_version_info *ver, u32 v_opcode,
 		if (msglen >= valid_len) {
 			struct virtchnl_ether_addr_list *veal =
 			    (struct virtchnl_ether_addr_list *)msg;
+			if (veal->num_elements == 0 ||
+			    veal->num_elements >
+			    VIRTCHNL_OP_ADD_DEL_ETH_ADDR_MAX) {
+				err_msg_format = TRUE;
+				break;
+			}
+
 			valid_len += veal->num_elements *
 			    sizeof(struct virtchnl_ether_addr);
-			if (veal->num_elements == 0)
-				err_msg_format = TRUE;
 		}
 		break;
 	case VIRTCHNL_OP_ADD_VLAN:
@@ -672,9 +707,13 @@ virtchnl_vc_validate_vf_msg(struct virtchnl_version_info *ver, u32 v_opcode,
 		if (msglen >= valid_len) {
 			struct virtchnl_vlan_filter_list *vfl =
 			    (struct virtchnl_vlan_filter_list *)msg;
-			valid_len += vfl->num_elements * sizeof(u16);
-			if (vfl->num_elements == 0)
+			if (vfl->num_elements == 0 ||
+			    vfl->num_elements > VIRTCHNL_OP_ADD_DEL_VLAN_MAX) {
 				err_msg_format = TRUE;
+				break;
+			}
+
+			valid_len += vfl->num_elements * sizeof(u16);
 		}
 		break;
 	case VIRTCHNL_OP_CONFIG_PROMISCUOUS_MODE:
@@ -700,7 +739,8 @@ virtchnl_vc_validate_vf_msg(struct virtchnl_version_info *ver, u32 v_opcode,
 		if (msglen >= valid_len) {
 			struct virtchnl_iwarp_qvlist_info *qv =
 				(struct virtchnl_iwarp_qvlist_info *)msg;
-			if (qv->num_vectors == 0) {
+			if (qv->num_vectors == 0 || qv->num_vectors >
+			    VIRTCHNL_OP_CONFIG_IWARP_IRQ_MAP_MAX) {
 				err_msg_format = TRUE;
 				break;
 			}
@@ -713,6 +753,8 @@ virtchnl_vc_validate_vf_msg(struct virtchnl_version_info *ver, u32 v_opcode,
 		if (msglen >= valid_len) {
 			struct virtchnl_rss_key *vrk =
 				(struct virtchnl_rss_key *)msg;
+			if (vrk->key_len == 0)
+				break;
 			valid_len += vrk->key_len - 1;
 		}
 		break;
@@ -721,6 +763,8 @@ virtchnl_vc_validate_vf_msg(struct virtchnl_version_info *ver, u32 v_opcode,
 		if (msglen >= valid_len) {
 			struct virtchnl_rss_lut *vrl =
 				(struct virtchnl_rss_lut *)msg;
+			if (vrl->lut_entries == 0)
+				break;
 			valid_len += vrl->lut_entries - 1;
 		}
 		break;
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.