DPDK-dev Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v1 1/1] ethdev: add flow item structure size query
@ 2026-10-23 12:15 Anatoly Burakov
  2026-10-23 16:04 ` Stephen Hemminger
                   ` (3 more replies)
  0 siblings, 4 replies; 20+ messages in thread
From: Anatoly Burakov @ 2026-10-23 12:15 UTC (permalink / raw)
  To: dev

Add RTE_FLOW_CONV_OP_ITEM_SIZE to retrieve the fixed specification
structure size for a flow item type using the existing descriptor table.

Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
 lib/ethdev/rte_flow.c | 16 ++++++++++++++++
 lib/ethdev/rte_flow.h | 18 ++++++++++++++++++
 2 files changed, 34 insertions(+)

diff --git a/lib/ethdev/rte_flow.c b/lib/ethdev/rte_flow.c
index 0ebf59165d4..1b2d263331a 100644
--- a/lib/ethdev/rte_flow.c
+++ b/lib/ethdev/rte_flow.c
@@ -1266,6 +1266,22 @@ rte_flow_conv(enum rte_flow_conv_op op,
 	case RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR:
 		ret = rte_flow_conv_item_default_mask(dst, size, src, error);
 		break;
+	case RTE_FLOW_CONV_OP_ITEM_SIZE:
+	{
+		uintptr_t type = (uintptr_t)src;
+
+		if (type >= RTE_DIM(rte_flow_desc_item) ||
+				rte_flow_desc_item[type].name == NULL) {
+			ret = rte_flow_error_set(error, EINVAL,
+					RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+					"unknown item type to retrieve the size of");
+			break;
+		}
+		if (size >= sizeof(size_t))
+			*(size_t *)dst = rte_flow_desc_item[type].size;
+		ret = sizeof(size_t);
+		break;
+	}
 	default:
 		ret = rte_flow_error_set
 		(error, ENOTSUP, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
diff --git a/lib/ethdev/rte_flow.h b/lib/ethdev/rte_flow.h
index df8101e4395..3d6a56f51e3 100644
--- a/lib/ethdev/rte_flow.h
+++ b/lib/ethdev/rte_flow.h
@@ -4426,6 +4426,24 @@ enum rte_flow_conv_op {
 	 *   @code const void ** @endcode
 	 */
 	RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR,
+
+	/**
+	 * Retrieve the fixed size of an item specification structure.
+	 *
+	 * Writes the size of rte_flow_item_{name} to @p dst, or zero for
+	 * known item types without a specification structure. Does not include
+	 * variable-length or externally referenced data (for example, RAW
+	 * pattern bytes). Unknown and PMD-private item types return an error.
+	 *
+	 * No output is written if @p size is smaller than sizeof(size_t).
+	 * The return value is sizeof(size_t), not the item structure size.
+	 *
+	 * - @p src type:
+	 *   @code (const void *)(uintptr_t)enum rte_flow_item_type @endcode
+	 * - @p dst type:
+	 *   @code size_t * @endcode
+	 */
+	RTE_FLOW_CONV_OP_ITEM_SIZE,
 };
 
 /**
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* Re: [PATCH v1 1/1] ethdev: add flow item structure size query
  2026-10-23 12:15 [PATCH v1 1/1] ethdev: add flow item structure size query Anatoly Burakov
@ 2026-10-23 16:04 ` Stephen Hemminger
  2026-10-24 12:04 ` [PATCH v2 1/4] ethdev: fix missing MACsec flow item descriptor Anatoly Burakov
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 20+ messages in thread
From: Stephen Hemminger @ 2026-10-23 16:04 UTC (permalink / raw)
  To: Anatoly Burakov; +Cc: dev

On Fri, 23 Oct 2026 13:15:46 +0100
Anatoly Burakov <anatoly.burakov@intel.com> wrote:

> Add RTE_FLOW_CONV_OP_ITEM_SIZE to retrieve the fixed specification
> structure size for a flow item type using the existing descriptor table.
> 
> Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
> ---

I am not sure about the details AI is complaining about here,
but it does look like it has a point. Also it should be possible
to get some code coverage for this in a standalone test.

[PATCH v1 1/1] ethdev: add flow item structure size query

Warning

RTE_FLOW_ITEM_TYPE_MACSEC has no entry in rte_flow_desc_item[]. The
table uses designated initializers, so the MACSEC slot is zero filled.
With this patch a size query for MACSEC fails with EINVAL "unknown
item type", although it is a public item with a spec structure
(struct rte_flow_item_macsec). The doc comment says only unknown and
PMD-private types fail.

The hole predates this patch and hurts existing ops too:
 - RTE_FLOW_CONV_OP_ITEM_NAME on MACSEC calls strlcpy(dst, NULL, size);
   rte_flow_conv_name() has no NULL name check.
 - rte_flow_conv_copy() sees desc[type].size == 0, so rte_flow_copy()
   and RTE_FLOW_CONV_OP_PATTERN silently drop MACSEC spec/last/mask.

Please send a separate fix ahead of this patch, in rte_flow_desc_item[]:

	MK_FLOW_ITEM(MACSEC, sizeof(struct rte_flow_item_macsec)),

	Fixes: 0aaf097585a6 ("ethdev: add MACsec flow item")
	Cc: stable@dpdk.org

Missing release note. RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR, also new
in 26.11, has one. Extend that entry in release_26_11.rst:

	* **Added flow item default mask and size lookup.**

	  Added ``RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR`` and
	  ``RTE_FLOW_CONV_OP_ITEM_SIZE`` conversion operations
	  to ``rte_flow_conv()`` to retrieve the default mask
	  and the spec structure size of a flow item type.

No user and no test. The commit message does not say what needs this.
If the consumer is flow_graph or a driver parser, send it as part of
that series. app/test has no rte_flow_conv() coverage at all; a short
test checking END -> 0, ETH -> sizeof(struct rte_flow_item_eth),
MACSEC -> sizeof(struct rte_flow_item_macsec), a negative type ->
EINVAL, and size < sizeof(size_t) -> dst untouched would have caught
the MACSEC hole.

Info

The doc text "Writes the size of rte_flow_item_{name}" is wrong for
GRE_KEY (rte_be32_t), GTPC/GTPU (struct rte_flow_item_gtp) and
ICMP6_ECHO_REQUEST/REPLY (struct rte_flow_item_icmp6_echo). Suggest:

	 * Writes the size of the item specification structure to
	 * @p dst, or zero for known item types without one.

Every other op dispatches to a static helper. Move the braced case
block next to rte_flow_conv_item_default_mask():

	/** Retrieve the spec structure size of a flow item type. */
	static int
	rte_flow_conv_item_size(void *dst, const size_t size,
				const void *src,
				struct rte_flow_error *error)
	{
		unsigned int type = (uintptr_t)src;

		if (type >= RTE_DIM(rte_flow_desc_item) ||
		    rte_flow_desc_item[type].name == NULL)
			return rte_flow_error_set(error, EINVAL,
				RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
				"unknown item type to retrieve the size of");
		if (size >= sizeof(size_t))
			*(size_t *)dst = rte_flow_desc_item[type].size;
		return sizeof(size_t);
	}

	case RTE_FLOW_CONV_OP_ITEM_SIZE:
		ret = rte_flow_conv_item_size(dst, size, src, error);
		break;

The name == NULL check here is good; the same check belongs in
rte_flow_conv_name() and rte_flow_conv_item_default_mask().

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v2 1/4] ethdev: fix missing MACsec flow item descriptor
  2026-10-23 12:15 [PATCH v1 1/1] ethdev: add flow item structure size query Anatoly Burakov
  2026-10-23 16:04 ` Stephen Hemminger
@ 2026-10-24 12:04 ` Anatoly Burakov
  2026-10-24 12:04   ` [PATCH v2 2/4] ethdev: fix missing flow action descriptors Anatoly Burakov
                     ` (2 more replies)
  2026-10-24 13:50 ` [PATCH v3 1/5] ethdev: fix flow conversion with null buffer Anatoly Burakov
  2026-10-27  9:50 ` [PATCH v4 1/6] " Anatoly Burakov
  3 siblings, 3 replies; 20+ messages in thread
From: Anatoly Burakov @ 2026-10-24 12:04 UTC (permalink / raw)
  To: dev, Ori Kam, Thomas Monjalon, Andrew Rybchenko, Akhil Goyal

RTE_FLOW_ITEM_TYPE_MACSEC was never added to rte_flow_desc_item[].
The table uses designated initializers, so the MACSEC slot is zero
filled: its name is NULL and its size is zero.

As a result, RTE_FLOW_CONV_OP_ITEM_NAME on MACSEC passes a NULL
source to strlcpy(), and rte_flow_copy() as well as the pattern
conversion operations reject any pattern containing a MACSEC item
as an unknown item type.

Add the missing descriptor entry.

Fixes: 0aaf097585a6 ("ethdev: add MACsec flow item")
Cc: stable@dpdk.org

Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
 lib/ethdev/rte_flow.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lib/ethdev/rte_flow.c b/lib/ethdev/rte_flow.c
index 0ebf59165d4..eeab79aa5d9 100644
--- a/lib/ethdev/rte_flow.c
+++ b/lib/ethdev/rte_flow.c
@@ -229,6 +229,7 @@ static const struct rte_flow_desc_data rte_flow_desc_item[] = {
 			rte_flow_item_l2tpv2_mask),
 	MK_FLOW_ITEM_MASK(PPP, sizeof(struct rte_flow_item_ppp),
 			rte_flow_item_ppp_mask),
+	MK_FLOW_ITEM(MACSEC, sizeof(struct rte_flow_item_macsec)),
 	MK_FLOW_ITEM_MASK(METER_COLOR, sizeof(struct rte_flow_item_meter_color),
 			rte_flow_item_meter_color_mask),
 	MK_FLOW_ITEM(IPV6_ROUTING_EXT, sizeof(struct rte_flow_item_ipv6_routing_ext)),
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v2 2/4] ethdev: fix missing flow action descriptors
  2026-10-24 12:04 ` [PATCH v2 1/4] ethdev: fix missing MACsec flow item descriptor Anatoly Burakov
@ 2026-10-24 12:04   ` Anatoly Burakov
  2026-10-24 12:04   ` [PATCH v2 3/4] test/ethdev: add flow conversion tests Anatoly Burakov
  2026-10-24 12:04   ` [PATCH v2 4/4] ethdev: add flow item structure size query Anatoly Burakov
  2 siblings, 0 replies; 20+ messages in thread
From: Anatoly Burakov @ 2026-10-24 12:04 UTC (permalink / raw)
  To: dev, Ori Kam, Thomas Monjalon, Andrew Rybchenko, Ajit Khaparde,
	Andrey Vesnovaty, Ray Kinsella, Matan Azrad, Cristian Dumitrescu,
	Haifei Luo, Rakesh Kudurumalla, Jerin Jacob

RTE_FLOW_ACTION_TYPE_SHARED, RTE_FLOW_ACTION_TYPE_METER_COLOR and
RTE_FLOW_ACTION_TYPE_SKIP_CMAN were never added to
rte_flow_desc_action[]. The table uses designated initializers, so
these slots are zero filled: their name is NULL and their size is
zero.

As a result, RTE_FLOW_CONV_OP_ACTION_NAME on any of them passes a
NULL source to strlcpy(), and rte_flow_copy() as well as the action
conversion operations reject any action list containing one of them
as an unknown action type.

Add the missing descriptor entries.

Fixes: 4d9fd85fb545 ("ethdev: add shared actions to flow API")
Fixes: 5f0d54f372f0 ("ethdev: add pre-defined meter policy API")
Fixes: 6807e8b52a18 ("ethdev: skip congestion management configuration")
Cc: stable@dpdk.org

Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
 lib/ethdev/rte_flow.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lib/ethdev/rte_flow.c b/lib/ethdev/rte_flow.c
index eeab79aa5d9..8a4833695c0 100644
--- a/lib/ethdev/rte_flow.c
+++ b/lib/ethdev/rte_flow.c
@@ -324,6 +324,8 @@ static const struct rte_flow_desc_data rte_flow_desc_action[] = {
 	MK_FLOW_ACTION(SET_IPV6_DSCP, sizeof(struct rte_flow_action_set_dscp)),
 	MK_FLOW_ACTION(AGE, sizeof(struct rte_flow_action_age)),
 	MK_FLOW_ACTION(SAMPLE, sizeof(struct rte_flow_action_sample)),
+	/* Deprecated; handle stored in conf as for INDIRECT. */
+	MK_FLOW_ACTION(SHARED, 0),
 	MK_FLOW_ACTION(MODIFY_FIELD,
 		       sizeof(struct rte_flow_action_modify_field)),
 	/**
@@ -334,11 +336,13 @@ static const struct rte_flow_desc_data rte_flow_desc_action[] = {
 	 */
 	MK_FLOW_ACTION(INDIRECT, 0),
 	MK_FLOW_ACTION(CONNTRACK, sizeof(struct rte_flow_action_conntrack)),
+	MK_FLOW_ACTION(METER_COLOR, sizeof(struct rte_flow_action_meter_color)),
 	MK_FLOW_ACTION(PORT_REPRESENTOR, sizeof(struct rte_flow_action_ethdev)),
 	MK_FLOW_ACTION(REPRESENTED_PORT, sizeof(struct rte_flow_action_ethdev)),
 	MK_FLOW_ACTION(METER_MARK, sizeof(struct rte_flow_action_meter_mark)),
 	MK_FLOW_ACTION(SEND_TO_KERNEL, 0),
 	MK_FLOW_ACTION(QUOTA, sizeof(struct rte_flow_action_quota)),
+	MK_FLOW_ACTION(SKIP_CMAN, 0),
 	MK_FLOW_ACTION(IPV6_EXT_PUSH, sizeof(struct rte_flow_action_ipv6_ext_push)),
 	MK_FLOW_ACTION(IPV6_EXT_REMOVE, sizeof(struct rte_flow_action_ipv6_ext_remove)),
 	MK_FLOW_ACTION(INDIRECT_LIST,
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v2 3/4] test/ethdev: add flow conversion tests
  2026-10-24 12:04 ` [PATCH v2 1/4] ethdev: fix missing MACsec flow item descriptor Anatoly Burakov
  2026-10-24 12:04   ` [PATCH v2 2/4] ethdev: fix missing flow action descriptors Anatoly Burakov
@ 2026-10-24 12:04   ` Anatoly Burakov
  2026-10-24 12:04   ` [PATCH v2 4/4] ethdev: add flow item structure size query Anatoly Burakov
  2 siblings, 0 replies; 20+ messages in thread
From: Anatoly Burakov @ 2026-10-24 12:04 UTC (permalink / raw)
  To: dev, Thomas Monjalon, Andrew Rybchenko

Add a unit test for rte_flow_conv(), which so far had no coverage.
Cover the item and action name lookups, the item default mask lookup,
pattern and action list conversion with deep copied specification
data, and check that every public item and action type has an entry
in the conversion descriptor tables.

Note that this test is fragile in the sense that because item type list
does not provide a "max" value, each time a new item is added, the max item
value must be updated.

Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
 app/test/meson.build             |   1 +
 app/test/test_ethdev_flow_conv.c | 361 +++++++++++++++++++++++++++++++
 2 files changed, 362 insertions(+)
 create mode 100644 app/test/test_ethdev_flow_conv.c

diff --git a/app/test/meson.build b/app/test/meson.build
index 9805cf5bee8..30dd1fcf832 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -77,6 +77,7 @@ source_file_deps = {
     'test_efd_perf.c': ['efd', 'hash'],
     'test_errno.c': [],
     'test_ethdev_api.c': ['ethdev'],
+    'test_ethdev_flow_conv.c': ['ethdev'],
     'test_ethdev_link.c': ['ethdev'],
     'test_ethdev_module_eeprom.c': ['ethdev'],
     'test_event_crypto_adapter.c': ['cryptodev', 'eventdev', 'bus_vdev'],
diff --git a/app/test/test_ethdev_flow_conv.c b/app/test/test_ethdev_flow_conv.c
new file mode 100644
index 00000000000..104b02dbeb6
--- /dev/null
+++ b/app/test/test_ethdev_flow_conv.c
@@ -0,0 +1,361 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Intel Corporation
+ */
+
+#include <stdalign.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_errno.h>
+#include <rte_flow.h>
+
+#include "test.h"
+
+/*
+ * Last known item and action types. Every type up to and including these
+ * must have a descriptor entry in the ethdev flow conversion tables.
+ */
+#define LAST_ITEM_TYPE ((int)RTE_FLOW_ITEM_TYPE_COMPARE)
+#define LAST_ACTION_TYPE ((int)RTE_FLOW_ACTION_TYPE_JUMP_TO_TABLE_INDEX)
+
+/* Guaranteed to be out of range of every descriptor table. */
+#define BOGUS_TYPE INT32_MAX
+
+static int
+test_flow_conv_item_name(void)
+{
+	struct rte_flow_error error;
+	const char *name;
+	char buf[8];
+	int ret;
+
+	/* Name copy. */
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME, buf, sizeof(buf),
+			    RTE_CAST_PTR(const void *, RTE_FLOW_ITEM_TYPE_ETH), &error);
+	TEST_ASSERT_EQUAL(ret, (int)strlen("ETH"), "unexpected return %d",
+			  ret);
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(buf, "ETH", strlen("ETH") + 1,
+				      "name mismatch");
+
+	/* Truncated copy reports the full length. */
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME, buf, 3,
+			    RTE_CAST_PTR(const void *, RTE_FLOW_ITEM_TYPE_MACSEC), &error);
+	TEST_ASSERT_EQUAL(ret, (int)strlen("MACSEC"), "unexpected return %d",
+			  ret);
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(buf, "MA", 3, "truncated name mismatch");
+
+	/* Name pointer. */
+	name = NULL;
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME_PTR, &name,
+			    sizeof(name),
+			    RTE_CAST_PTR(const void *, RTE_FLOW_ITEM_TYPE_MACSEC), &error);
+	TEST_ASSERT_EQUAL(ret, (int)sizeof(const char **),
+			  "unexpected return %d", ret);
+	TEST_ASSERT_NOT_NULL(name, "name pointer not set");
+	TEST_ASSERT(strcmp(name, "MACSEC") == 0, "name mismatch: %s", name);
+
+	/* Out of range type. */
+	rte_errno = 0;
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME_PTR, &name,
+			    sizeof(name), RTE_CAST_PTR(const void *, BOGUS_TYPE), &error);
+	TEST_ASSERT_EQUAL(ret, -EINVAL, "unexpected return %d", ret);
+	TEST_ASSERT_EQUAL(rte_errno, EINVAL, "rte_errno %d", rte_errno);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_flow_conv_action_name(void)
+{
+	struct rte_flow_error error;
+	const char *name;
+	int ret;
+
+	struct {
+		int type;
+		const char *name;
+	} const known[] = {
+		{ RTE_FLOW_ACTION_TYPE_END, "END" },
+		{ RTE_FLOW_ACTION_TYPE_QUEUE, "QUEUE" },
+		{ RTE_FLOW_ACTION_TYPE_SHARED, "SHARED" },
+		{ RTE_FLOW_ACTION_TYPE_METER_COLOR, "METER_COLOR" },
+		{ RTE_FLOW_ACTION_TYPE_SKIP_CMAN, "SKIP_CMAN" },
+		{ RTE_FLOW_ACTION_TYPE_JUMP_TO_TABLE_INDEX,
+		  "JUMP_TO_TABLE_INDEX" },
+	};
+
+	for (unsigned int i = 0; i < RTE_DIM(known); i++) {
+		name = NULL;
+		ret = rte_flow_conv(RTE_FLOW_CONV_OP_ACTION_NAME_PTR, &name,
+				    sizeof(name), RTE_CAST_PTR(const void *, known[i].type),
+				    &error);
+		TEST_ASSERT_EQUAL(ret, (int)sizeof(const char **),
+				  "action type %d: unexpected return %d",
+				  known[i].type, ret);
+		TEST_ASSERT_NOT_NULL(name, "action type %d: name not set",
+				     known[i].type);
+		TEST_ASSERT(strcmp(name, known[i].name) == 0,
+			    "action type %d: name %s, expected %s",
+			    known[i].type, name, known[i].name);
+	}
+
+	rte_errno = 0;
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ACTION_NAME_PTR, &name,
+			    sizeof(name), RTE_CAST_PTR(const void *, BOGUS_TYPE), &error);
+	TEST_ASSERT_EQUAL(ret, -EINVAL, "unexpected return %d", ret);
+	TEST_ASSERT_EQUAL(rte_errno, EINVAL, "rte_errno %d", rte_errno);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_flow_conv_item_default_mask(void)
+{
+	struct rte_flow_error error;
+	const void *mask;
+	int ret;
+
+	mask = NULL;
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR, &mask,
+			    sizeof(mask), RTE_CAST_PTR(const void *, RTE_FLOW_ITEM_TYPE_ETH),
+			    &error);
+	TEST_ASSERT_EQUAL(ret, (int)sizeof(const void **),
+			  "unexpected return %d", ret);
+	/*
+	 * Default masks are static const in rte_flow.h, so each translation
+	 * unit has its own copy: compare contents, not addresses.
+	 */
+	TEST_ASSERT_NOT_NULL(mask, "ETH default mask not set");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(mask, &rte_flow_item_eth_mask,
+				      sizeof(rte_flow_item_eth_mask),
+				      "ETH default mask mismatch");
+
+	/* Known type without a default mask. */
+	mask = &error;
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR, &mask,
+			    sizeof(mask), RTE_CAST_PTR(const void *, RTE_FLOW_ITEM_TYPE_END),
+			    &error);
+	TEST_ASSERT_EQUAL(ret, (int)sizeof(const void **),
+			  "unexpected return %d", ret);
+	TEST_ASSERT_NULL(mask, "END must not have a default mask");
+
+	rte_errno = 0;
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR, &mask,
+			    sizeof(mask), RTE_CAST_PTR(const void *, BOGUS_TYPE), &error);
+	TEST_ASSERT_EQUAL(ret, -EINVAL, "unexpected return %d", ret);
+	TEST_ASSERT_EQUAL(rte_errno, EINVAL, "rte_errno %d", rte_errno);
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Every public item and action type must have a descriptor entry.
+ * A missing entry would otherwise make the type unknown to all
+ * conversion operations.
+ */
+static int
+test_flow_conv_tables_complete(void)
+{
+	struct rte_flow_error error;
+	const char *name;
+	int ret;
+
+	for (int type = 0; type <= LAST_ITEM_TYPE; type++) {
+		name = NULL;
+		ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME_PTR, &name,
+				    sizeof(name), RTE_CAST_PTR(const void *, type), &error);
+		TEST_ASSERT_EQUAL(ret, (int)sizeof(const char **),
+				  "item type %d has no descriptor", type);
+		TEST_ASSERT_NOT_NULL(name, "item type %d has no name", type);
+	}
+
+	for (int type = 0; type <= LAST_ACTION_TYPE; type++) {
+		name = NULL;
+		ret = rte_flow_conv(RTE_FLOW_CONV_OP_ACTION_NAME_PTR, &name,
+				    sizeof(name), RTE_CAST_PTR(const void *, type), &error);
+		TEST_ASSERT_EQUAL(ret, (int)sizeof(const char **),
+				  "action type %d has no descriptor", type);
+		TEST_ASSERT_NOT_NULL(name, "action type %d has no name", type);
+	}
+
+	return TEST_SUCCESS;
+}
+
+/* Convert a pattern and check spec/mask are deep copied using the table. */
+static int
+test_flow_conv_pattern(void)
+{
+	struct rte_flow_error error;
+	const struct rte_flow_item_eth eth_spec = {
+		.hdr.ether_type = RTE_BE16(RTE_ETHER_TYPE_IPV4),
+	};
+	const struct rte_flow_item_macsec macsec_spec = {
+		.macsec_hdr.tci_an = RTE_MACSEC_TCI_E | 0x1,
+		.macsec_hdr.short_length = 0x2a,
+		.macsec_hdr.packet_number = RTE_BE32(0x01020304),
+	};
+	const struct rte_flow_item_macsec macsec_mask = {
+		.macsec_hdr.tci_an = UINT8_MAX,
+		.macsec_hdr.short_length = 0x3f,
+		.macsec_hdr.packet_number = RTE_BE32(UINT32_MAX),
+	};
+	const struct rte_flow_item pattern[] = {
+		{
+			.type = RTE_FLOW_ITEM_TYPE_ETH,
+			.spec = &eth_spec,
+			.mask = &rte_flow_item_eth_mask,
+		},
+		{
+			.type = RTE_FLOW_ITEM_TYPE_MACSEC,
+			.spec = &macsec_spec,
+			.mask = &macsec_mask,
+		},
+		{
+			.type = RTE_FLOW_ITEM_TYPE_END,
+		},
+	};
+	alignas(double) uint8_t buf[512];
+	struct rte_flow_item *out = (struct rte_flow_item *)buf;
+	size_t needed;
+	int ret;
+
+	/* Size query. */
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN, NULL, 0, pattern,
+			    &error);
+	TEST_ASSERT(ret > 0, "size query failed: %d (%s)", ret,
+		    error.message);
+	needed = ret;
+	TEST_ASSERT(needed >= RTE_DIM(pattern) * sizeof(*out) +
+		    sizeof(eth_spec) + sizeof(rte_flow_item_eth_mask) +
+		    sizeof(macsec_spec) + sizeof(macsec_mask),
+		    "size %zu too small to hold all items", needed);
+	TEST_ASSERT(needed <= sizeof(buf), "buffer too small: %zu", needed);
+
+	memset(buf, 0xff, sizeof(buf));
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN, out, sizeof(buf),
+			    pattern, &error);
+	TEST_ASSERT_EQUAL(ret, (int)needed, "unexpected return %d", ret);
+
+	for (unsigned int i = 0; i < RTE_DIM(pattern); i++)
+		TEST_ASSERT_EQUAL(out[i].type, pattern[i].type,
+				  "item %u: type mismatch", i);
+
+	TEST_ASSERT_NOT_NULL(out[0].spec, "ETH spec not copied");
+	TEST_ASSERT(out[0].spec != pattern[0].spec, "ETH spec not deep copied");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(out[0].spec, &eth_spec, sizeof(eth_spec),
+				      "ETH spec mismatch");
+	TEST_ASSERT_NOT_NULL(out[0].mask, "ETH mask not copied");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(out[0].mask, &rte_flow_item_eth_mask,
+				      sizeof(rte_flow_item_eth_mask),
+				      "ETH mask mismatch");
+	TEST_ASSERT_NULL(out[0].last, "ETH last unexpectedly set");
+
+	TEST_ASSERT_NOT_NULL(out[1].spec, "MACSEC spec not copied");
+	TEST_ASSERT(out[1].spec != pattern[1].spec,
+		    "MACSEC spec not deep copied");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(out[1].spec, &macsec_spec,
+				      sizeof(macsec_spec),
+				      "MACSEC spec mismatch");
+	TEST_ASSERT_NOT_NULL(out[1].mask, "MACSEC mask not copied");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(out[1].mask, &macsec_mask,
+				      sizeof(macsec_mask),
+				      "MACSEC mask mismatch");
+
+	TEST_ASSERT_NULL(out[2].spec, "END spec unexpectedly set");
+	TEST_ASSERT_NULL(out[2].mask, "END mask unexpectedly set");
+
+	return TEST_SUCCESS;
+}
+
+/* Convert actions and check configuration is deep copied using the table. */
+static int
+test_flow_conv_actions(void)
+{
+	struct rte_flow_error error;
+	const struct rte_flow_action_meter_color meter_color = {
+		.color = RTE_COLOR_YELLOW,
+	};
+	const struct rte_flow_action_queue queue = {
+		.index = 7,
+	};
+	const struct rte_flow_action actions[] = {
+		{
+			.type = RTE_FLOW_ACTION_TYPE_SKIP_CMAN,
+		},
+		{
+			.type = RTE_FLOW_ACTION_TYPE_METER_COLOR,
+			.conf = &meter_color,
+		},
+		{
+			.type = RTE_FLOW_ACTION_TYPE_QUEUE,
+			.conf = &queue,
+		},
+		{
+			.type = RTE_FLOW_ACTION_TYPE_END,
+		},
+	};
+	alignas(double) uint8_t buf[512];
+	struct rte_flow_action *out = (struct rte_flow_action *)buf;
+	size_t needed;
+	int ret;
+
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ACTIONS, NULL, 0, actions,
+			    &error);
+	TEST_ASSERT(ret > 0, "size query failed: %d (%s)", ret,
+		    error.message);
+	needed = ret;
+	TEST_ASSERT(needed >= RTE_DIM(actions) * sizeof(*out) +
+		    sizeof(meter_color) + sizeof(queue),
+		    "size %zu too small to hold all actions", needed);
+	TEST_ASSERT(needed <= sizeof(buf), "buffer too small: %zu", needed);
+
+	memset(buf, 0xff, sizeof(buf));
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ACTIONS, out, sizeof(buf),
+			    actions, &error);
+	TEST_ASSERT_EQUAL(ret, (int)needed, "unexpected return %d", ret);
+
+	for (unsigned int i = 0; i < RTE_DIM(actions); i++)
+		TEST_ASSERT_EQUAL(out[i].type, actions[i].type,
+				  "action %u: type mismatch", i);
+
+	TEST_ASSERT_NULL(out[0].conf, "SKIP_CMAN conf unexpectedly set");
+
+	TEST_ASSERT_NOT_NULL(out[1].conf, "METER_COLOR conf not copied");
+	TEST_ASSERT(out[1].conf != actions[1].conf,
+		    "METER_COLOR conf not deep copied");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(out[1].conf, &meter_color,
+				      sizeof(meter_color),
+				      "METER_COLOR conf mismatch");
+
+	TEST_ASSERT_NOT_NULL(out[2].conf, "QUEUE conf not copied");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(out[2].conf, &queue, sizeof(queue),
+				      "QUEUE conf mismatch");
+
+	TEST_ASSERT_NULL(out[3].conf, "END conf unexpectedly set");
+
+	return TEST_SUCCESS;
+}
+
+static struct unit_test_suite flow_conv_testsuite = {
+	.suite_name = "flow conversion",
+	.setup = NULL,
+	.teardown = NULL,
+	.unit_test_cases = {
+		TEST_CASE(test_flow_conv_item_name),
+		TEST_CASE(test_flow_conv_action_name),
+		TEST_CASE(test_flow_conv_item_default_mask),
+		TEST_CASE(test_flow_conv_tables_complete),
+		TEST_CASE(test_flow_conv_pattern),
+		TEST_CASE(test_flow_conv_actions),
+		TEST_CASES_END()
+	}
+};
+
+static int
+test_flow_conv(void)
+{
+	return unit_test_suite_runner(&flow_conv_testsuite);
+}
+
+REGISTER_FAST_TEST(ethdev_flow_conv_autotest, NOHUGE_OK, ASAN_OK,
+		   test_flow_conv);
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v2 4/4] ethdev: add flow item structure size query
  2026-10-24 12:04 ` [PATCH v2 1/4] ethdev: fix missing MACsec flow item descriptor Anatoly Burakov
  2026-10-24 12:04   ` [PATCH v2 2/4] ethdev: fix missing flow action descriptors Anatoly Burakov
  2026-10-24 12:04   ` [PATCH v2 3/4] test/ethdev: add flow conversion tests Anatoly Burakov
@ 2026-10-24 12:04   ` Anatoly Burakov
  2 siblings, 0 replies; 20+ messages in thread
From: Anatoly Burakov @ 2026-10-24 12:04 UTC (permalink / raw)
  To: dev, Thomas Monjalon, Andrew Rybchenko, Ori Kam

Add RTE_FLOW_CONV_OP_ITEM_SIZE to retrieve the fixed specification
structure size for a flow item type using the existing descriptor table.

Lookups by type now also reject item types that have no descriptor
entry, instead of returning a NULL name or passing NULL to strlcpy().

Extend the flow conversion unit test to cover the new operation.

Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
 app/test/test_ethdev_flow_conv.c       | 92 ++++++++++++++++++++++++++
 doc/guides/rel_notes/release_26_11.rst |  8 ++-
 lib/ethdev/rte_flow.c                  | 27 +++++++-
 lib/ethdev/rte_flow.h                  | 18 +++++
 4 files changed, 140 insertions(+), 5 deletions(-)

diff --git a/app/test/test_ethdev_flow_conv.c b/app/test/test_ethdev_flow_conv.c
index 104b02dbeb6..79fded8c55f 100644
--- a/app/test/test_ethdev_flow_conv.c
+++ b/app/test/test_ethdev_flow_conv.c
@@ -21,6 +21,92 @@
 /* Guaranteed to be out of range of every descriptor table. */
 #define BOGUS_TYPE INT32_MAX
 
+static int
+item_size(int type, size_t *size, struct rte_flow_error *error)
+{
+	return rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_SIZE, size, sizeof(*size),
+			     RTE_CAST_PTR(const void *, type), error);
+}
+
+static int
+test_flow_conv_item_size(void)
+{
+	struct rte_flow_error error;
+	size_t size;
+	int ret;
+
+	struct {
+		int type;
+		size_t expected;
+	} const known[] = {
+		{ RTE_FLOW_ITEM_TYPE_END, 0 },
+		{ RTE_FLOW_ITEM_TYPE_VOID, 0 },
+		{ RTE_FLOW_ITEM_TYPE_INVERT, 0 },
+		{ RTE_FLOW_ITEM_TYPE_ETH, sizeof(struct rte_flow_item_eth) },
+		{ RTE_FLOW_ITEM_TYPE_IPV4, sizeof(struct rte_flow_item_ipv4) },
+		{ RTE_FLOW_ITEM_TYPE_GRE_KEY, sizeof(rte_be32_t) },
+		{ RTE_FLOW_ITEM_TYPE_GTPC, sizeof(struct rte_flow_item_gtp) },
+		{ RTE_FLOW_ITEM_TYPE_MACSEC,
+		  sizeof(struct rte_flow_item_macsec) },
+		{ RTE_FLOW_ITEM_TYPE_COMPARE,
+		  sizeof(struct rte_flow_item_compare) },
+	};
+
+	for (unsigned int i = 0; i < RTE_DIM(known); i++) {
+		size = SIZE_MAX;
+		ret = item_size(known[i].type, &size, &error);
+		TEST_ASSERT_EQUAL(ret, (int)sizeof(size_t),
+				  "item type %d: unexpected return %d",
+				  known[i].type, ret);
+		TEST_ASSERT_EQUAL(size, known[i].expected,
+				  "item type %d: size %zu, expected %zu",
+				  known[i].type, size, known[i].expected);
+	}
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_flow_conv_item_size_errors(void)
+{
+	struct rte_flow_error error;
+	size_t size;
+	int ret;
+
+	/* Out of range type. */
+	memset(&error, 0, sizeof(error));
+	rte_errno = 0;
+	ret = item_size(BOGUS_TYPE, &size, &error);
+	TEST_ASSERT_EQUAL(ret, -EINVAL, "unexpected return %d", ret);
+	TEST_ASSERT_EQUAL(rte_errno, EINVAL, "rte_errno %d", rte_errno);
+	TEST_ASSERT_EQUAL(error.type, RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
+			  "unexpected error type %d", error.type);
+	TEST_ASSERT_NOT_NULL(error.message, "error message not set");
+
+	/* PMD private type. */
+	rte_errno = 0;
+	ret = item_size(-1, &size, &error);
+	TEST_ASSERT_EQUAL(ret, -EINVAL, "unexpected return %d", ret);
+	TEST_ASSERT_EQUAL(rte_errno, EINVAL, "rte_errno %d", rte_errno);
+
+	/* Destination too small: nothing written, size still reported. */
+	size = SIZE_MAX;
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_SIZE, &size,
+			    sizeof(size) - 1,
+			    RTE_CAST_PTR(const void *, RTE_FLOW_ITEM_TYPE_ETH), &error);
+	TEST_ASSERT_EQUAL(ret, (int)sizeof(size_t), "unexpected return %d",
+			  ret);
+	TEST_ASSERT_EQUAL(size, SIZE_MAX, "destination was written");
+
+	/* NULL destination with zero size is a pure size query. */
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_SIZE, NULL, 0,
+			    RTE_CAST_PTR(const void *, RTE_FLOW_ITEM_TYPE_ETH), &error);
+	TEST_ASSERT_EQUAL(ret, (int)sizeof(size_t), "unexpected return %d",
+			  ret);
+
+	return TEST_SUCCESS;
+}
+
 static int
 test_flow_conv_item_name(void)
 {
@@ -158,6 +244,7 @@ test_flow_conv_tables_complete(void)
 {
 	struct rte_flow_error error;
 	const char *name;
+	size_t size;
 	int ret;
 
 	for (int type = 0; type <= LAST_ITEM_TYPE; type++) {
@@ -167,6 +254,9 @@ test_flow_conv_tables_complete(void)
 		TEST_ASSERT_EQUAL(ret, (int)sizeof(const char **),
 				  "item type %d has no descriptor", type);
 		TEST_ASSERT_NOT_NULL(name, "item type %d has no name", type);
+		ret = item_size(type, &size, &error);
+		TEST_ASSERT_EQUAL(ret, (int)sizeof(size_t),
+				  "item type %d (%s) has no size", type, name);
 	}
 
 	for (int type = 0; type <= LAST_ACTION_TYPE; type++) {
@@ -341,6 +431,8 @@ static struct unit_test_suite flow_conv_testsuite = {
 	.setup = NULL,
 	.teardown = NULL,
 	.unit_test_cases = {
+		TEST_CASE(test_flow_conv_item_size),
+		TEST_CASE(test_flow_conv_item_size_errors),
 		TEST_CASE(test_flow_conv_item_name),
 		TEST_CASE(test_flow_conv_action_name),
 		TEST_CASE(test_flow_conv_item_default_mask),
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 9259954c0f7..f3755bf7ab5 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -113,10 +113,12 @@ New Features
   loss of lock and Tx fault flags.
   The SFF-8472 decoder also reports the Rx loss of signal and Tx fault state.
 
-* **Added flow item default mask lookup.**
+* **Added flow item default mask and size lookup.**
 
-  Added ``RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR`` conversion operation
-  to ``rte_flow_conv()`` to retrieve the default mask of a flow item type.
+  Added ``RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR`` and
+  ``RTE_FLOW_CONV_OP_ITEM_SIZE`` conversion operations
+  to ``rte_flow_conv()`` to retrieve the default mask
+  and the specification structure size of a flow item type.
 
 * **Added internal ethdev flow graph parser helper API.**
 
diff --git a/lib/ethdev/rte_flow.c b/lib/ethdev/rte_flow.c
index 8a4833695c0..d9ee5277480 100644
--- a/lib/ethdev/rte_flow.c
+++ b/lib/ethdev/rte_flow.c
@@ -1178,7 +1178,7 @@ rte_flow_conv_name(int is_action,
 	const struct desc_info *const info = &info_rep[!!is_action];
 	unsigned int type = (uintptr_t)src;
 
-	if (type >= info->num)
+	if (type >= info->num || info->data[type].name == NULL)
 		return rte_flow_error_set
 			(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
 			 "unknown object type to retrieve the name of");
@@ -1198,7 +1198,8 @@ rte_flow_conv_item_default_mask(void *dst,
 {
 	unsigned int type = (uintptr_t)src;
 
-	if (type >= RTE_DIM(rte_flow_desc_item))
+	if (type >= RTE_DIM(rte_flow_desc_item) ||
+	    rte_flow_desc_item[type].name == NULL)
 		return rte_flow_error_set
 			(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
 			 "unknown item type to retrieve the default mask of");
@@ -1207,6 +1208,25 @@ rte_flow_conv_item_default_mask(void *dst,
 	return sizeof(const void **);
 }
 
+/** Retrieve the specification structure size of a flow item type. */
+static int
+rte_flow_conv_item_size(void *dst,
+			const size_t size,
+			const void *src,
+			struct rte_flow_error *error)
+{
+	unsigned int type = (uintptr_t)src;
+
+	if (type >= RTE_DIM(rte_flow_desc_item) ||
+	    rte_flow_desc_item[type].name == NULL)
+		return rte_flow_error_set
+			(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+			 "unknown item type to retrieve the size of");
+	if (size >= sizeof(size_t))
+		*(size_t *)dst = rte_flow_desc_item[type].size;
+	return sizeof(size_t);
+}
+
 /** Helper function to convert flow API objects. */
 RTE_EXPORT_SYMBOL(rte_flow_conv)
 int
@@ -1271,6 +1291,9 @@ rte_flow_conv(enum rte_flow_conv_op op,
 	case RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR:
 		ret = rte_flow_conv_item_default_mask(dst, size, src, error);
 		break;
+	case RTE_FLOW_CONV_OP_ITEM_SIZE:
+		ret = rte_flow_conv_item_size(dst, size, src, error);
+		break;
 	default:
 		ret = rte_flow_error_set
 		(error, ENOTSUP, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
diff --git a/lib/ethdev/rte_flow.h b/lib/ethdev/rte_flow.h
index df8101e4395..ff1ffd03e73 100644
--- a/lib/ethdev/rte_flow.h
+++ b/lib/ethdev/rte_flow.h
@@ -4426,6 +4426,24 @@ enum rte_flow_conv_op {
 	 *   @code const void ** @endcode
 	 */
 	RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR,
+
+	/**
+	 * Retrieve the fixed size of an item specification structure.
+	 *
+	 * Writes the size of the item specification structure to @p dst,
+	 * or zero for known item types without one. Does not include
+	 * variable-length or externally referenced data (for example, RAW
+	 * pattern bytes). Unknown and PMD-private item types return an error.
+	 *
+	 * No output is written if @p size is smaller than sizeof(size_t).
+	 * The return value is sizeof(size_t), not the item structure size.
+	 *
+	 * - @p src type:
+	 *   @code (const void *)(uintptr_t)enum rte_flow_item_type @endcode
+	 * - @p dst type:
+	 *   @code size_t * @endcode
+	 */
+	RTE_FLOW_CONV_OP_ITEM_SIZE,
 };
 
 /**
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v3 1/5] ethdev: fix flow conversion with null buffer
  2026-10-23 12:15 [PATCH v1 1/1] ethdev: add flow item structure size query Anatoly Burakov
  2026-10-23 16:04 ` Stephen Hemminger
  2026-10-24 12:04 ` [PATCH v2 1/4] ethdev: fix missing MACsec flow item descriptor Anatoly Burakov
@ 2026-10-24 13:50 ` Anatoly Burakov
  2026-10-24 13:50   ` [PATCH v3 2/5] ethdev: fix missing MACsec flow item descriptor Anatoly Burakov
                     ` (4 more replies)
  2026-10-27  9:50 ` [PATCH v4 1/6] " Anatoly Burakov
  3 siblings, 5 replies; 20+ messages in thread
From: Anatoly Burakov @ 2026-10-24 13:50 UTC (permalink / raw)
  To: dev

rte_flow_conv_pattern() and rte_flow_conv_actions() document that the
destination buffer may be NULL when the size is zero, which is how
callers query the number of bytes required before allocating. Both
functions nonetheless walked the destination with ++dst in the first
pass and dst -= num before the second, regardless of size.

Pointer arithmetic on a NULL pointer is undefined behavior, and is
reported by UBSan as "applying non-zero offset to null pointer" for
every RTE_FLOW_CONV_OP_PATTERN and RTE_FLOW_CONV_OP_ACTIONS size query.

Keep the destination pointer fixed and index it only inside the
size-guarded writes, which are never reached when size is zero.

Fixes: 063911ee1df4 ("ethdev: add flow API object converter")
Cc: stable@dpdk.org

Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
 lib/ethdev/rte_flow.c | 42 +++++++++++++++++++++++-------------------
 1 file changed, 23 insertions(+), 19 deletions(-)

diff --git a/lib/ethdev/rte_flow.c b/lib/ethdev/rte_flow.c
index 0ebf59165d4..704ed663c5f 100644
--- a/lib/ethdev/rte_flow.c
+++ b/lib/ethdev/rte_flow.c
@@ -921,7 +921,12 @@ rte_flow_conv_pattern(struct rte_flow_item *dst,
 	size_t ret;
 	unsigned int i;
 
-	for (i = 0, off = 0; !num || i != num; ++i, ++src, ++dst) {
+	/*
+	 * dst may be NULL when size is zero, so it is only ever indexed
+	 * inside the size-guarded writes below; pointer arithmetic on a
+	 * NULL pointer is undefined behavior.
+	 */
+	for (i = 0, off = 0; !num || i != num; ++i, ++src) {
 		/**
 		 * allow PMD private flow item
 		 */
@@ -932,7 +937,7 @@ rte_flow_conv_pattern(struct rte_flow_item *dst,
 				(error, ENOTSUP, RTE_FLOW_ERROR_TYPE_ITEM, src,
 				 "cannot convert unknown item type");
 		if (size >= off + sizeof(*dst))
-			*dst = (struct rte_flow_item){
+			dst[i] = (struct rte_flow_item){
 				.type = src->type,
 			};
 		off += sizeof(*dst);
@@ -941,8 +946,7 @@ rte_flow_conv_pattern(struct rte_flow_item *dst,
 	}
 	num = i;
 	src -= num;
-	dst -= num;
-	do {
+	for (i = 0; i != num; ++i, ++src) {
 		if (src->spec) {
 			off = RTE_ALIGN_CEIL(off, sizeof(double));
 			ret = rte_flow_conv_item_spec
@@ -950,7 +954,7 @@ rte_flow_conv_pattern(struct rte_flow_item *dst,
 				 size > off ? size - off : 0, src,
 				 RTE_FLOW_CONV_ITEM_SPEC);
 			if (size && size >= off + ret)
-				dst->spec = (void *)(data + off);
+				dst[i].spec = (void *)(data + off);
 			off += ret;
 
 		}
@@ -961,7 +965,7 @@ rte_flow_conv_pattern(struct rte_flow_item *dst,
 				 size > off ? size - off : 0, src,
 				 RTE_FLOW_CONV_ITEM_LAST);
 			if (size && size >= off + ret)
-				dst->last = (void *)(data + off);
+				dst[i].last = (void *)(data + off);
 			off += ret;
 		}
 		if (src->mask) {
@@ -971,12 +975,10 @@ rte_flow_conv_pattern(struct rte_flow_item *dst,
 				 size > off ? size - off : 0, src,
 				 RTE_FLOW_CONV_ITEM_MASK);
 			if (size && size >= off + ret)
-				dst->mask = (void *)(data + off);
+				dst[i].mask = (void *)(data + off);
 			off += ret;
 		}
-		++src;
-		++dst;
-	} while (--num);
+	}
 	return off;
 }
 
@@ -1014,7 +1016,12 @@ rte_flow_conv_actions(struct rte_flow_action *dst,
 	size_t ret;
 	unsigned int i;
 
-	for (i = 0, off = 0; !num || i != num; ++i, ++src, ++dst) {
+	/*
+	 * dst may be NULL when size is zero, so it is only ever indexed
+	 * inside the size-guarded writes below; pointer arithmetic on a
+	 * NULL pointer is undefined behavior.
+	 */
+	for (i = 0, off = 0; !num || i != num; ++i, ++src) {
 		/**
 		 * allow PMD private flow action
 		 */
@@ -1025,7 +1032,7 @@ rte_flow_conv_actions(struct rte_flow_action *dst,
 				(error, ENOTSUP, RTE_FLOW_ERROR_TYPE_ACTION,
 				 src, "cannot convert unknown action type");
 		if (size >= off + sizeof(*dst))
-			*dst = (struct rte_flow_action){
+			dst[i] = (struct rte_flow_action){
 				.type = src->type,
 			};
 		off += sizeof(*dst);
@@ -1034,8 +1041,7 @@ rte_flow_conv_actions(struct rte_flow_action *dst,
 	}
 	num = i;
 	src -= num;
-	dst -= num;
-	do {
+	for (i = 0; i != num; ++i, ++src) {
 		if (src->type == RTE_FLOW_ACTION_TYPE_INDIRECT) {
 			/*
 			 * Indirect action conf fills the indirect action
@@ -1043,19 +1049,17 @@ rte_flow_conv_actions(struct rte_flow_action *dst,
 			 * of duplicating the pointer memory.
 			 */
 			if (size)
-				dst->conf = src->conf;
+				dst[i].conf = src->conf;
 		} else if (src->conf) {
 			off = RTE_ALIGN_CEIL(off, sizeof(double));
 			ret = rte_flow_conv_action_conf
 				((void *)(data + off),
 				 size > off ? size - off : 0, src);
 			if (size && size >= off + ret)
-				dst->conf = (void *)(data + off);
+				dst[i].conf = (void *)(data + off);
 			off += ret;
 		}
-		++src;
-		++dst;
-	} while (--num);
+	}
 	return off;
 }
 
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v3 2/5] ethdev: fix missing MACsec flow item descriptor
  2026-10-24 13:50 ` [PATCH v3 1/5] ethdev: fix flow conversion with null buffer Anatoly Burakov
@ 2026-10-24 13:50   ` Anatoly Burakov
  2026-10-24 13:50   ` [PATCH v3 3/5] ethdev: fix missing flow action descriptors Anatoly Burakov
                     ` (3 subsequent siblings)
  4 siblings, 0 replies; 20+ messages in thread
From: Anatoly Burakov @ 2026-10-24 13:50 UTC (permalink / raw)
  To: dev

RTE_FLOW_ITEM_TYPE_MACSEC was never added to rte_flow_desc_item[].
The table uses designated initializers, so the MACSEC slot is zero
filled: its name is NULL and its size is zero.

As a result, RTE_FLOW_CONV_OP_ITEM_NAME on MACSEC passes a NULL
source to strlcpy(), and rte_flow_copy() as well as the pattern
conversion operations reject any pattern containing a MACSEC item
as an unknown item type.

Add the missing descriptor entry.

Fixes: 0aaf097585a6 ("ethdev: add MACsec flow item")
Cc: stable@dpdk.org

Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
 lib/ethdev/rte_flow.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lib/ethdev/rte_flow.c b/lib/ethdev/rte_flow.c
index 704ed663c5f..6e544f4bc91 100644
--- a/lib/ethdev/rte_flow.c
+++ b/lib/ethdev/rte_flow.c
@@ -229,6 +229,7 @@ static const struct rte_flow_desc_data rte_flow_desc_item[] = {
 			rte_flow_item_l2tpv2_mask),
 	MK_FLOW_ITEM_MASK(PPP, sizeof(struct rte_flow_item_ppp),
 			rte_flow_item_ppp_mask),
+	MK_FLOW_ITEM(MACSEC, sizeof(struct rte_flow_item_macsec)),
 	MK_FLOW_ITEM_MASK(METER_COLOR, sizeof(struct rte_flow_item_meter_color),
 			rte_flow_item_meter_color_mask),
 	MK_FLOW_ITEM(IPV6_ROUTING_EXT, sizeof(struct rte_flow_item_ipv6_routing_ext)),
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v3 3/5] ethdev: fix missing flow action descriptors
  2026-10-24 13:50 ` [PATCH v3 1/5] ethdev: fix flow conversion with null buffer Anatoly Burakov
  2026-10-24 13:50   ` [PATCH v3 2/5] ethdev: fix missing MACsec flow item descriptor Anatoly Burakov
@ 2026-10-24 13:50   ` Anatoly Burakov
  2026-10-24 13:50   ` [PATCH v3 4/5] test/ethdev: add flow conversion tests Anatoly Burakov
                     ` (2 subsequent siblings)
  4 siblings, 0 replies; 20+ messages in thread
From: Anatoly Burakov @ 2026-10-24 13:50 UTC (permalink / raw)
  To: dev

RTE_FLOW_ACTION_TYPE_SHARED, RTE_FLOW_ACTION_TYPE_METER_COLOR and
RTE_FLOW_ACTION_TYPE_SKIP_CMAN were never added to
rte_flow_desc_action[]. The table uses designated initializers, so
these slots are zero filled: their name is NULL and their size is
zero.

As a result, RTE_FLOW_CONV_OP_ACTION_NAME on any of them passes a
NULL source to strlcpy(), and rte_flow_copy() as well as the action
conversion operations reject any action list containing one of them
as an unknown action type.

Add the missing descriptor entries.

Fixes: 4d9fd85fb545 ("ethdev: add shared actions to flow API")
Fixes: 5f0d54f372f0 ("ethdev: add pre-defined meter policy API")
Fixes: 6807e8b52a18 ("ethdev: skip congestion management configuration")
Cc: stable@dpdk.org

Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
 lib/ethdev/rte_flow.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lib/ethdev/rte_flow.c b/lib/ethdev/rte_flow.c
index 6e544f4bc91..65d57717a05 100644
--- a/lib/ethdev/rte_flow.c
+++ b/lib/ethdev/rte_flow.c
@@ -324,6 +324,8 @@ static const struct rte_flow_desc_data rte_flow_desc_action[] = {
 	MK_FLOW_ACTION(SET_IPV6_DSCP, sizeof(struct rte_flow_action_set_dscp)),
 	MK_FLOW_ACTION(AGE, sizeof(struct rte_flow_action_age)),
 	MK_FLOW_ACTION(SAMPLE, sizeof(struct rte_flow_action_sample)),
+	/* Deprecated; handle stored in conf as for INDIRECT. */
+	MK_FLOW_ACTION(SHARED, 0),
 	MK_FLOW_ACTION(MODIFY_FIELD,
 		       sizeof(struct rte_flow_action_modify_field)),
 	/**
@@ -334,11 +336,13 @@ static const struct rte_flow_desc_data rte_flow_desc_action[] = {
 	 */
 	MK_FLOW_ACTION(INDIRECT, 0),
 	MK_FLOW_ACTION(CONNTRACK, sizeof(struct rte_flow_action_conntrack)),
+	MK_FLOW_ACTION(METER_COLOR, sizeof(struct rte_flow_action_meter_color)),
 	MK_FLOW_ACTION(PORT_REPRESENTOR, sizeof(struct rte_flow_action_ethdev)),
 	MK_FLOW_ACTION(REPRESENTED_PORT, sizeof(struct rte_flow_action_ethdev)),
 	MK_FLOW_ACTION(METER_MARK, sizeof(struct rte_flow_action_meter_mark)),
 	MK_FLOW_ACTION(SEND_TO_KERNEL, 0),
 	MK_FLOW_ACTION(QUOTA, sizeof(struct rte_flow_action_quota)),
+	MK_FLOW_ACTION(SKIP_CMAN, 0),
 	MK_FLOW_ACTION(IPV6_EXT_PUSH, sizeof(struct rte_flow_action_ipv6_ext_push)),
 	MK_FLOW_ACTION(IPV6_EXT_REMOVE, sizeof(struct rte_flow_action_ipv6_ext_remove)),
 	MK_FLOW_ACTION(INDIRECT_LIST,
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v3 4/5] test/ethdev: add flow conversion tests
  2026-10-24 13:50 ` [PATCH v3 1/5] ethdev: fix flow conversion with null buffer Anatoly Burakov
  2026-10-24 13:50   ` [PATCH v3 2/5] ethdev: fix missing MACsec flow item descriptor Anatoly Burakov
  2026-10-24 13:50   ` [PATCH v3 3/5] ethdev: fix missing flow action descriptors Anatoly Burakov
@ 2026-10-24 13:50   ` Anatoly Burakov
  2026-10-24 13:50   ` [PATCH v3 5/5] ethdev: add flow item structure size query Anatoly Burakov
  2026-10-26 16:51   ` [PATCH v3 1/5] ethdev: fix flow conversion with null buffer Stephen Hemminger
  4 siblings, 0 replies; 20+ messages in thread
From: Anatoly Burakov @ 2026-10-24 13:50 UTC (permalink / raw)
  To: dev

Add a unit test for rte_flow_conv(), which so far had no coverage.
Cover the item and action name lookups, the item default mask lookup,
pattern and action list conversion with deep copied specification
data, and check that every public item and action type has an entry
in the conversion descriptor tables.

Note that this test is fragile in the sense that because item type list
does not provide a "max" value, each time a new item is added, the max item
value must be updated.

Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
 app/test/meson.build             |   1 +
 app/test/test_ethdev_flow_conv.c | 361 +++++++++++++++++++++++++++++++
 2 files changed, 362 insertions(+)
 create mode 100644 app/test/test_ethdev_flow_conv.c

diff --git a/app/test/meson.build b/app/test/meson.build
index 9805cf5bee8..30dd1fcf832 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -77,6 +77,7 @@ source_file_deps = {
     'test_efd_perf.c': ['efd', 'hash'],
     'test_errno.c': [],
     'test_ethdev_api.c': ['ethdev'],
+    'test_ethdev_flow_conv.c': ['ethdev'],
     'test_ethdev_link.c': ['ethdev'],
     'test_ethdev_module_eeprom.c': ['ethdev'],
     'test_event_crypto_adapter.c': ['cryptodev', 'eventdev', 'bus_vdev'],
diff --git a/app/test/test_ethdev_flow_conv.c b/app/test/test_ethdev_flow_conv.c
new file mode 100644
index 00000000000..104b02dbeb6
--- /dev/null
+++ b/app/test/test_ethdev_flow_conv.c
@@ -0,0 +1,361 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Intel Corporation
+ */
+
+#include <stdalign.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_errno.h>
+#include <rte_flow.h>
+
+#include "test.h"
+
+/*
+ * Last known item and action types. Every type up to and including these
+ * must have a descriptor entry in the ethdev flow conversion tables.
+ */
+#define LAST_ITEM_TYPE ((int)RTE_FLOW_ITEM_TYPE_COMPARE)
+#define LAST_ACTION_TYPE ((int)RTE_FLOW_ACTION_TYPE_JUMP_TO_TABLE_INDEX)
+
+/* Guaranteed to be out of range of every descriptor table. */
+#define BOGUS_TYPE INT32_MAX
+
+static int
+test_flow_conv_item_name(void)
+{
+	struct rte_flow_error error;
+	const char *name;
+	char buf[8];
+	int ret;
+
+	/* Name copy. */
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME, buf, sizeof(buf),
+			    RTE_CAST_PTR(const void *, RTE_FLOW_ITEM_TYPE_ETH), &error);
+	TEST_ASSERT_EQUAL(ret, (int)strlen("ETH"), "unexpected return %d",
+			  ret);
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(buf, "ETH", strlen("ETH") + 1,
+				      "name mismatch");
+
+	/* Truncated copy reports the full length. */
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME, buf, 3,
+			    RTE_CAST_PTR(const void *, RTE_FLOW_ITEM_TYPE_MACSEC), &error);
+	TEST_ASSERT_EQUAL(ret, (int)strlen("MACSEC"), "unexpected return %d",
+			  ret);
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(buf, "MA", 3, "truncated name mismatch");
+
+	/* Name pointer. */
+	name = NULL;
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME_PTR, &name,
+			    sizeof(name),
+			    RTE_CAST_PTR(const void *, RTE_FLOW_ITEM_TYPE_MACSEC), &error);
+	TEST_ASSERT_EQUAL(ret, (int)sizeof(const char **),
+			  "unexpected return %d", ret);
+	TEST_ASSERT_NOT_NULL(name, "name pointer not set");
+	TEST_ASSERT(strcmp(name, "MACSEC") == 0, "name mismatch: %s", name);
+
+	/* Out of range type. */
+	rte_errno = 0;
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME_PTR, &name,
+			    sizeof(name), RTE_CAST_PTR(const void *, BOGUS_TYPE), &error);
+	TEST_ASSERT_EQUAL(ret, -EINVAL, "unexpected return %d", ret);
+	TEST_ASSERT_EQUAL(rte_errno, EINVAL, "rte_errno %d", rte_errno);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_flow_conv_action_name(void)
+{
+	struct rte_flow_error error;
+	const char *name;
+	int ret;
+
+	struct {
+		int type;
+		const char *name;
+	} const known[] = {
+		{ RTE_FLOW_ACTION_TYPE_END, "END" },
+		{ RTE_FLOW_ACTION_TYPE_QUEUE, "QUEUE" },
+		{ RTE_FLOW_ACTION_TYPE_SHARED, "SHARED" },
+		{ RTE_FLOW_ACTION_TYPE_METER_COLOR, "METER_COLOR" },
+		{ RTE_FLOW_ACTION_TYPE_SKIP_CMAN, "SKIP_CMAN" },
+		{ RTE_FLOW_ACTION_TYPE_JUMP_TO_TABLE_INDEX,
+		  "JUMP_TO_TABLE_INDEX" },
+	};
+
+	for (unsigned int i = 0; i < RTE_DIM(known); i++) {
+		name = NULL;
+		ret = rte_flow_conv(RTE_FLOW_CONV_OP_ACTION_NAME_PTR, &name,
+				    sizeof(name), RTE_CAST_PTR(const void *, known[i].type),
+				    &error);
+		TEST_ASSERT_EQUAL(ret, (int)sizeof(const char **),
+				  "action type %d: unexpected return %d",
+				  known[i].type, ret);
+		TEST_ASSERT_NOT_NULL(name, "action type %d: name not set",
+				     known[i].type);
+		TEST_ASSERT(strcmp(name, known[i].name) == 0,
+			    "action type %d: name %s, expected %s",
+			    known[i].type, name, known[i].name);
+	}
+
+	rte_errno = 0;
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ACTION_NAME_PTR, &name,
+			    sizeof(name), RTE_CAST_PTR(const void *, BOGUS_TYPE), &error);
+	TEST_ASSERT_EQUAL(ret, -EINVAL, "unexpected return %d", ret);
+	TEST_ASSERT_EQUAL(rte_errno, EINVAL, "rte_errno %d", rte_errno);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_flow_conv_item_default_mask(void)
+{
+	struct rte_flow_error error;
+	const void *mask;
+	int ret;
+
+	mask = NULL;
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR, &mask,
+			    sizeof(mask), RTE_CAST_PTR(const void *, RTE_FLOW_ITEM_TYPE_ETH),
+			    &error);
+	TEST_ASSERT_EQUAL(ret, (int)sizeof(const void **),
+			  "unexpected return %d", ret);
+	/*
+	 * Default masks are static const in rte_flow.h, so each translation
+	 * unit has its own copy: compare contents, not addresses.
+	 */
+	TEST_ASSERT_NOT_NULL(mask, "ETH default mask not set");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(mask, &rte_flow_item_eth_mask,
+				      sizeof(rte_flow_item_eth_mask),
+				      "ETH default mask mismatch");
+
+	/* Known type without a default mask. */
+	mask = &error;
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR, &mask,
+			    sizeof(mask), RTE_CAST_PTR(const void *, RTE_FLOW_ITEM_TYPE_END),
+			    &error);
+	TEST_ASSERT_EQUAL(ret, (int)sizeof(const void **),
+			  "unexpected return %d", ret);
+	TEST_ASSERT_NULL(mask, "END must not have a default mask");
+
+	rte_errno = 0;
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR, &mask,
+			    sizeof(mask), RTE_CAST_PTR(const void *, BOGUS_TYPE), &error);
+	TEST_ASSERT_EQUAL(ret, -EINVAL, "unexpected return %d", ret);
+	TEST_ASSERT_EQUAL(rte_errno, EINVAL, "rte_errno %d", rte_errno);
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Every public item and action type must have a descriptor entry.
+ * A missing entry would otherwise make the type unknown to all
+ * conversion operations.
+ */
+static int
+test_flow_conv_tables_complete(void)
+{
+	struct rte_flow_error error;
+	const char *name;
+	int ret;
+
+	for (int type = 0; type <= LAST_ITEM_TYPE; type++) {
+		name = NULL;
+		ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME_PTR, &name,
+				    sizeof(name), RTE_CAST_PTR(const void *, type), &error);
+		TEST_ASSERT_EQUAL(ret, (int)sizeof(const char **),
+				  "item type %d has no descriptor", type);
+		TEST_ASSERT_NOT_NULL(name, "item type %d has no name", type);
+	}
+
+	for (int type = 0; type <= LAST_ACTION_TYPE; type++) {
+		name = NULL;
+		ret = rte_flow_conv(RTE_FLOW_CONV_OP_ACTION_NAME_PTR, &name,
+				    sizeof(name), RTE_CAST_PTR(const void *, type), &error);
+		TEST_ASSERT_EQUAL(ret, (int)sizeof(const char **),
+				  "action type %d has no descriptor", type);
+		TEST_ASSERT_NOT_NULL(name, "action type %d has no name", type);
+	}
+
+	return TEST_SUCCESS;
+}
+
+/* Convert a pattern and check spec/mask are deep copied using the table. */
+static int
+test_flow_conv_pattern(void)
+{
+	struct rte_flow_error error;
+	const struct rte_flow_item_eth eth_spec = {
+		.hdr.ether_type = RTE_BE16(RTE_ETHER_TYPE_IPV4),
+	};
+	const struct rte_flow_item_macsec macsec_spec = {
+		.macsec_hdr.tci_an = RTE_MACSEC_TCI_E | 0x1,
+		.macsec_hdr.short_length = 0x2a,
+		.macsec_hdr.packet_number = RTE_BE32(0x01020304),
+	};
+	const struct rte_flow_item_macsec macsec_mask = {
+		.macsec_hdr.tci_an = UINT8_MAX,
+		.macsec_hdr.short_length = 0x3f,
+		.macsec_hdr.packet_number = RTE_BE32(UINT32_MAX),
+	};
+	const struct rte_flow_item pattern[] = {
+		{
+			.type = RTE_FLOW_ITEM_TYPE_ETH,
+			.spec = &eth_spec,
+			.mask = &rte_flow_item_eth_mask,
+		},
+		{
+			.type = RTE_FLOW_ITEM_TYPE_MACSEC,
+			.spec = &macsec_spec,
+			.mask = &macsec_mask,
+		},
+		{
+			.type = RTE_FLOW_ITEM_TYPE_END,
+		},
+	};
+	alignas(double) uint8_t buf[512];
+	struct rte_flow_item *out = (struct rte_flow_item *)buf;
+	size_t needed;
+	int ret;
+
+	/* Size query. */
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN, NULL, 0, pattern,
+			    &error);
+	TEST_ASSERT(ret > 0, "size query failed: %d (%s)", ret,
+		    error.message);
+	needed = ret;
+	TEST_ASSERT(needed >= RTE_DIM(pattern) * sizeof(*out) +
+		    sizeof(eth_spec) + sizeof(rte_flow_item_eth_mask) +
+		    sizeof(macsec_spec) + sizeof(macsec_mask),
+		    "size %zu too small to hold all items", needed);
+	TEST_ASSERT(needed <= sizeof(buf), "buffer too small: %zu", needed);
+
+	memset(buf, 0xff, sizeof(buf));
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN, out, sizeof(buf),
+			    pattern, &error);
+	TEST_ASSERT_EQUAL(ret, (int)needed, "unexpected return %d", ret);
+
+	for (unsigned int i = 0; i < RTE_DIM(pattern); i++)
+		TEST_ASSERT_EQUAL(out[i].type, pattern[i].type,
+				  "item %u: type mismatch", i);
+
+	TEST_ASSERT_NOT_NULL(out[0].spec, "ETH spec not copied");
+	TEST_ASSERT(out[0].spec != pattern[0].spec, "ETH spec not deep copied");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(out[0].spec, &eth_spec, sizeof(eth_spec),
+				      "ETH spec mismatch");
+	TEST_ASSERT_NOT_NULL(out[0].mask, "ETH mask not copied");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(out[0].mask, &rte_flow_item_eth_mask,
+				      sizeof(rte_flow_item_eth_mask),
+				      "ETH mask mismatch");
+	TEST_ASSERT_NULL(out[0].last, "ETH last unexpectedly set");
+
+	TEST_ASSERT_NOT_NULL(out[1].spec, "MACSEC spec not copied");
+	TEST_ASSERT(out[1].spec != pattern[1].spec,
+		    "MACSEC spec not deep copied");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(out[1].spec, &macsec_spec,
+				      sizeof(macsec_spec),
+				      "MACSEC spec mismatch");
+	TEST_ASSERT_NOT_NULL(out[1].mask, "MACSEC mask not copied");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(out[1].mask, &macsec_mask,
+				      sizeof(macsec_mask),
+				      "MACSEC mask mismatch");
+
+	TEST_ASSERT_NULL(out[2].spec, "END spec unexpectedly set");
+	TEST_ASSERT_NULL(out[2].mask, "END mask unexpectedly set");
+
+	return TEST_SUCCESS;
+}
+
+/* Convert actions and check configuration is deep copied using the table. */
+static int
+test_flow_conv_actions(void)
+{
+	struct rte_flow_error error;
+	const struct rte_flow_action_meter_color meter_color = {
+		.color = RTE_COLOR_YELLOW,
+	};
+	const struct rte_flow_action_queue queue = {
+		.index = 7,
+	};
+	const struct rte_flow_action actions[] = {
+		{
+			.type = RTE_FLOW_ACTION_TYPE_SKIP_CMAN,
+		},
+		{
+			.type = RTE_FLOW_ACTION_TYPE_METER_COLOR,
+			.conf = &meter_color,
+		},
+		{
+			.type = RTE_FLOW_ACTION_TYPE_QUEUE,
+			.conf = &queue,
+		},
+		{
+			.type = RTE_FLOW_ACTION_TYPE_END,
+		},
+	};
+	alignas(double) uint8_t buf[512];
+	struct rte_flow_action *out = (struct rte_flow_action *)buf;
+	size_t needed;
+	int ret;
+
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ACTIONS, NULL, 0, actions,
+			    &error);
+	TEST_ASSERT(ret > 0, "size query failed: %d (%s)", ret,
+		    error.message);
+	needed = ret;
+	TEST_ASSERT(needed >= RTE_DIM(actions) * sizeof(*out) +
+		    sizeof(meter_color) + sizeof(queue),
+		    "size %zu too small to hold all actions", needed);
+	TEST_ASSERT(needed <= sizeof(buf), "buffer too small: %zu", needed);
+
+	memset(buf, 0xff, sizeof(buf));
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ACTIONS, out, sizeof(buf),
+			    actions, &error);
+	TEST_ASSERT_EQUAL(ret, (int)needed, "unexpected return %d", ret);
+
+	for (unsigned int i = 0; i < RTE_DIM(actions); i++)
+		TEST_ASSERT_EQUAL(out[i].type, actions[i].type,
+				  "action %u: type mismatch", i);
+
+	TEST_ASSERT_NULL(out[0].conf, "SKIP_CMAN conf unexpectedly set");
+
+	TEST_ASSERT_NOT_NULL(out[1].conf, "METER_COLOR conf not copied");
+	TEST_ASSERT(out[1].conf != actions[1].conf,
+		    "METER_COLOR conf not deep copied");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(out[1].conf, &meter_color,
+				      sizeof(meter_color),
+				      "METER_COLOR conf mismatch");
+
+	TEST_ASSERT_NOT_NULL(out[2].conf, "QUEUE conf not copied");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(out[2].conf, &queue, sizeof(queue),
+				      "QUEUE conf mismatch");
+
+	TEST_ASSERT_NULL(out[3].conf, "END conf unexpectedly set");
+
+	return TEST_SUCCESS;
+}
+
+static struct unit_test_suite flow_conv_testsuite = {
+	.suite_name = "flow conversion",
+	.setup = NULL,
+	.teardown = NULL,
+	.unit_test_cases = {
+		TEST_CASE(test_flow_conv_item_name),
+		TEST_CASE(test_flow_conv_action_name),
+		TEST_CASE(test_flow_conv_item_default_mask),
+		TEST_CASE(test_flow_conv_tables_complete),
+		TEST_CASE(test_flow_conv_pattern),
+		TEST_CASE(test_flow_conv_actions),
+		TEST_CASES_END()
+	}
+};
+
+static int
+test_flow_conv(void)
+{
+	return unit_test_suite_runner(&flow_conv_testsuite);
+}
+
+REGISTER_FAST_TEST(ethdev_flow_conv_autotest, NOHUGE_OK, ASAN_OK,
+		   test_flow_conv);
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v3 5/5] ethdev: add flow item structure size query
  2026-10-24 13:50 ` [PATCH v3 1/5] ethdev: fix flow conversion with null buffer Anatoly Burakov
                     ` (2 preceding siblings ...)
  2026-10-24 13:50   ` [PATCH v3 4/5] test/ethdev: add flow conversion tests Anatoly Burakov
@ 2026-10-24 13:50   ` Anatoly Burakov
  2026-10-26 16:51   ` [PATCH v3 1/5] ethdev: fix flow conversion with null buffer Stephen Hemminger
  4 siblings, 0 replies; 20+ messages in thread
From: Anatoly Burakov @ 2026-10-24 13:50 UTC (permalink / raw)
  To: dev

Add RTE_FLOW_CONV_OP_ITEM_SIZE to retrieve the fixed specification
structure size for a flow item type using the existing descriptor table.

Lookups by type now also reject item types that have no descriptor
entry, instead of returning a NULL name or passing NULL to strlcpy().

Extend the flow conversion unit test to cover the new operation.

Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
 app/test/test_ethdev_flow_conv.c       | 92 ++++++++++++++++++++++++++
 doc/guides/rel_notes/release_26_11.rst |  8 ++-
 lib/ethdev/rte_flow.c                  | 27 +++++++-
 lib/ethdev/rte_flow.h                  | 18 +++++
 4 files changed, 140 insertions(+), 5 deletions(-)

diff --git a/app/test/test_ethdev_flow_conv.c b/app/test/test_ethdev_flow_conv.c
index 104b02dbeb6..79fded8c55f 100644
--- a/app/test/test_ethdev_flow_conv.c
+++ b/app/test/test_ethdev_flow_conv.c
@@ -21,6 +21,92 @@
 /* Guaranteed to be out of range of every descriptor table. */
 #define BOGUS_TYPE INT32_MAX
 
+static int
+item_size(int type, size_t *size, struct rte_flow_error *error)
+{
+	return rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_SIZE, size, sizeof(*size),
+			     RTE_CAST_PTR(const void *, type), error);
+}
+
+static int
+test_flow_conv_item_size(void)
+{
+	struct rte_flow_error error;
+	size_t size;
+	int ret;
+
+	struct {
+		int type;
+		size_t expected;
+	} const known[] = {
+		{ RTE_FLOW_ITEM_TYPE_END, 0 },
+		{ RTE_FLOW_ITEM_TYPE_VOID, 0 },
+		{ RTE_FLOW_ITEM_TYPE_INVERT, 0 },
+		{ RTE_FLOW_ITEM_TYPE_ETH, sizeof(struct rte_flow_item_eth) },
+		{ RTE_FLOW_ITEM_TYPE_IPV4, sizeof(struct rte_flow_item_ipv4) },
+		{ RTE_FLOW_ITEM_TYPE_GRE_KEY, sizeof(rte_be32_t) },
+		{ RTE_FLOW_ITEM_TYPE_GTPC, sizeof(struct rte_flow_item_gtp) },
+		{ RTE_FLOW_ITEM_TYPE_MACSEC,
+		  sizeof(struct rte_flow_item_macsec) },
+		{ RTE_FLOW_ITEM_TYPE_COMPARE,
+		  sizeof(struct rte_flow_item_compare) },
+	};
+
+	for (unsigned int i = 0; i < RTE_DIM(known); i++) {
+		size = SIZE_MAX;
+		ret = item_size(known[i].type, &size, &error);
+		TEST_ASSERT_EQUAL(ret, (int)sizeof(size_t),
+				  "item type %d: unexpected return %d",
+				  known[i].type, ret);
+		TEST_ASSERT_EQUAL(size, known[i].expected,
+				  "item type %d: size %zu, expected %zu",
+				  known[i].type, size, known[i].expected);
+	}
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_flow_conv_item_size_errors(void)
+{
+	struct rte_flow_error error;
+	size_t size;
+	int ret;
+
+	/* Out of range type. */
+	memset(&error, 0, sizeof(error));
+	rte_errno = 0;
+	ret = item_size(BOGUS_TYPE, &size, &error);
+	TEST_ASSERT_EQUAL(ret, -EINVAL, "unexpected return %d", ret);
+	TEST_ASSERT_EQUAL(rte_errno, EINVAL, "rte_errno %d", rte_errno);
+	TEST_ASSERT_EQUAL(error.type, RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
+			  "unexpected error type %d", error.type);
+	TEST_ASSERT_NOT_NULL(error.message, "error message not set");
+
+	/* PMD private type. */
+	rte_errno = 0;
+	ret = item_size(-1, &size, &error);
+	TEST_ASSERT_EQUAL(ret, -EINVAL, "unexpected return %d", ret);
+	TEST_ASSERT_EQUAL(rte_errno, EINVAL, "rte_errno %d", rte_errno);
+
+	/* Destination too small: nothing written, size still reported. */
+	size = SIZE_MAX;
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_SIZE, &size,
+			    sizeof(size) - 1,
+			    RTE_CAST_PTR(const void *, RTE_FLOW_ITEM_TYPE_ETH), &error);
+	TEST_ASSERT_EQUAL(ret, (int)sizeof(size_t), "unexpected return %d",
+			  ret);
+	TEST_ASSERT_EQUAL(size, SIZE_MAX, "destination was written");
+
+	/* NULL destination with zero size is a pure size query. */
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_SIZE, NULL, 0,
+			    RTE_CAST_PTR(const void *, RTE_FLOW_ITEM_TYPE_ETH), &error);
+	TEST_ASSERT_EQUAL(ret, (int)sizeof(size_t), "unexpected return %d",
+			  ret);
+
+	return TEST_SUCCESS;
+}
+
 static int
 test_flow_conv_item_name(void)
 {
@@ -158,6 +244,7 @@ test_flow_conv_tables_complete(void)
 {
 	struct rte_flow_error error;
 	const char *name;
+	size_t size;
 	int ret;
 
 	for (int type = 0; type <= LAST_ITEM_TYPE; type++) {
@@ -167,6 +254,9 @@ test_flow_conv_tables_complete(void)
 		TEST_ASSERT_EQUAL(ret, (int)sizeof(const char **),
 				  "item type %d has no descriptor", type);
 		TEST_ASSERT_NOT_NULL(name, "item type %d has no name", type);
+		ret = item_size(type, &size, &error);
+		TEST_ASSERT_EQUAL(ret, (int)sizeof(size_t),
+				  "item type %d (%s) has no size", type, name);
 	}
 
 	for (int type = 0; type <= LAST_ACTION_TYPE; type++) {
@@ -341,6 +431,8 @@ static struct unit_test_suite flow_conv_testsuite = {
 	.setup = NULL,
 	.teardown = NULL,
 	.unit_test_cases = {
+		TEST_CASE(test_flow_conv_item_size),
+		TEST_CASE(test_flow_conv_item_size_errors),
 		TEST_CASE(test_flow_conv_item_name),
 		TEST_CASE(test_flow_conv_action_name),
 		TEST_CASE(test_flow_conv_item_default_mask),
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 9259954c0f7..f3755bf7ab5 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -113,10 +113,12 @@ New Features
   loss of lock and Tx fault flags.
   The SFF-8472 decoder also reports the Rx loss of signal and Tx fault state.
 
-* **Added flow item default mask lookup.**
+* **Added flow item default mask and size lookup.**
 
-  Added ``RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR`` conversion operation
-  to ``rte_flow_conv()`` to retrieve the default mask of a flow item type.
+  Added ``RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR`` and
+  ``RTE_FLOW_CONV_OP_ITEM_SIZE`` conversion operations
+  to ``rte_flow_conv()`` to retrieve the default mask
+  and the specification structure size of a flow item type.
 
 * **Added internal ethdev flow graph parser helper API.**
 
diff --git a/lib/ethdev/rte_flow.c b/lib/ethdev/rte_flow.c
index 65d57717a05..4102bbe5f2a 100644
--- a/lib/ethdev/rte_flow.c
+++ b/lib/ethdev/rte_flow.c
@@ -1182,7 +1182,7 @@ rte_flow_conv_name(int is_action,
 	const struct desc_info *const info = &info_rep[!!is_action];
 	unsigned int type = (uintptr_t)src;
 
-	if (type >= info->num)
+	if (type >= info->num || info->data[type].name == NULL)
 		return rte_flow_error_set
 			(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
 			 "unknown object type to retrieve the name of");
@@ -1202,7 +1202,8 @@ rte_flow_conv_item_default_mask(void *dst,
 {
 	unsigned int type = (uintptr_t)src;
 
-	if (type >= RTE_DIM(rte_flow_desc_item))
+	if (type >= RTE_DIM(rte_flow_desc_item) ||
+	    rte_flow_desc_item[type].name == NULL)
 		return rte_flow_error_set
 			(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
 			 "unknown item type to retrieve the default mask of");
@@ -1211,6 +1212,25 @@ rte_flow_conv_item_default_mask(void *dst,
 	return sizeof(const void **);
 }
 
+/** Retrieve the specification structure size of a flow item type. */
+static int
+rte_flow_conv_item_size(void *dst,
+			const size_t size,
+			const void *src,
+			struct rte_flow_error *error)
+{
+	unsigned int type = (uintptr_t)src;
+
+	if (type >= RTE_DIM(rte_flow_desc_item) ||
+	    rte_flow_desc_item[type].name == NULL)
+		return rte_flow_error_set
+			(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+			 "unknown item type to retrieve the size of");
+	if (size >= sizeof(size_t))
+		*(size_t *)dst = rte_flow_desc_item[type].size;
+	return sizeof(size_t);
+}
+
 /** Helper function to convert flow API objects. */
 RTE_EXPORT_SYMBOL(rte_flow_conv)
 int
@@ -1275,6 +1295,9 @@ rte_flow_conv(enum rte_flow_conv_op op,
 	case RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR:
 		ret = rte_flow_conv_item_default_mask(dst, size, src, error);
 		break;
+	case RTE_FLOW_CONV_OP_ITEM_SIZE:
+		ret = rte_flow_conv_item_size(dst, size, src, error);
+		break;
 	default:
 		ret = rte_flow_error_set
 		(error, ENOTSUP, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
diff --git a/lib/ethdev/rte_flow.h b/lib/ethdev/rte_flow.h
index df8101e4395..ff1ffd03e73 100644
--- a/lib/ethdev/rte_flow.h
+++ b/lib/ethdev/rte_flow.h
@@ -4426,6 +4426,24 @@ enum rte_flow_conv_op {
 	 *   @code const void ** @endcode
 	 */
 	RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR,
+
+	/**
+	 * Retrieve the fixed size of an item specification structure.
+	 *
+	 * Writes the size of the item specification structure to @p dst,
+	 * or zero for known item types without one. Does not include
+	 * variable-length or externally referenced data (for example, RAW
+	 * pattern bytes). Unknown and PMD-private item types return an error.
+	 *
+	 * No output is written if @p size is smaller than sizeof(size_t).
+	 * The return value is sizeof(size_t), not the item structure size.
+	 *
+	 * - @p src type:
+	 *   @code (const void *)(uintptr_t)enum rte_flow_item_type @endcode
+	 * - @p dst type:
+	 *   @code size_t * @endcode
+	 */
+	RTE_FLOW_CONV_OP_ITEM_SIZE,
 };
 
 /**
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* Re: [PATCH v3 1/5] ethdev: fix flow conversion with null buffer
  2026-10-24 13:50 ` [PATCH v3 1/5] ethdev: fix flow conversion with null buffer Anatoly Burakov
                     ` (3 preceding siblings ...)
  2026-10-24 13:50   ` [PATCH v3 5/5] ethdev: add flow item structure size query Anatoly Burakov
@ 2026-10-26 16:51   ` Stephen Hemminger
  2026-10-27 10:56     ` Burakov, Anatoly
  4 siblings, 1 reply; 20+ messages in thread
From: Stephen Hemminger @ 2026-10-26 16:51 UTC (permalink / raw)
  To: Anatoly Burakov; +Cc: dev

On Sat, 24 Oct 2026 14:50:28 +0100
Anatoly Burakov <anatoly.burakov@intel.com> wrote:

> rte_flow_conv_pattern() and rte_flow_conv_actions() document that the
> destination buffer may be NULL when the size is zero, which is how
> callers query the number of bytes required before allocating. Both
> functions nonetheless walked the destination with ++dst in the first
> pass and dst -= num before the second, regardless of size.
> 
> Pointer arithmetic on a NULL pointer is undefined behavior, and is
> reported by UBSan as "applying non-zero offset to null pointer" for
> every RTE_FLOW_CONV_OP_PATTERN and RTE_FLOW_CONV_OP_ACTIONS size query.
> 
> Keep the destination pointer fixed and index it only inside the
> size-guarded writes, which are never reached when size is zero.
> 
> Fixes: 063911ee1df4 ("ethdev: add flow API object converter")
> Cc: stable@dpdk.org
> 
> Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
> ---

Manually run AI review sees some problems.

Subject: Re: [PATCH v3 0/5] ethdev: flow conversion fixes

Series applies cleanly to main and each commit builds with
-Dwerror=true. Descriptor tables now cover every item type through
COMPARE and every action type through JUMP_TO_TABLE_INDEX.

One correctness problem in 3/5, plus a pre-existing out-of-bounds
write on the same code path that should be fixed while here.


[PATCH v3 3/5] ethdev: fix missing flow action descriptors

Error: SHARED is now accepted but its handle is not preserved.

The new entry is MK_FLOW_ACTION(SHARED, 0) with a comment saying the
handle is stored in conf as for INDIRECT. rte_flow_conv_actions()
only special-cases RTE_FLOW_ACTION_TYPE_INDIRECT. SHARED with a
non-NULL conf falls into the generic branch, rte_flow_conv_copy()
copies zero bytes (desc size 0), and dst[i].conf is set to
data + off, a pointer into the output buffer instead of the handle.

Before this patch SHARED was rejected with ENOTSUP. After it,
rte_flow_conv() and rte_flow_copy() succeed and return a corrupted
action. Handle it the same way as INDIRECT:

-		if (src->type == RTE_FLOW_ACTION_TYPE_INDIRECT) {
+		if (src->type == RTE_FLOW_ACTION_TYPE_INDIRECT ||
+		    src->type == RTE_FLOW_ACTION_TYPE_SHARED) {

Either that, or leave SHARED out of the table since it is deprecated;
but then the completeness test in 4/5 needs an exception for it.

Please add a SHARED action with a non-NULL conf to
test_flow_conv_actions() and check out[i].conf == actions[i].conf.
That would have caught this.


[PATCH v3 1/5] ethdev: fix flow conversion with null buffer

Warning: INDIRECT conf write is not bounded by the buffer size.

Pre-existing, but this patch rewrites the line. In the second pass:

	if (size)
		dst[i].conf = src->conf;

Every other write into dst[] is guarded by size covering the entry.
With actions { QUEUE, INDIRECT, END } and size equal to
sizeof(struct rte_flow_action), the first pass writes only dst[0],
then the second pass writes dst[1].conf past the end of the caller's
buffer. Suggest:

-			if (size)
+			if (size >= (i + 1) * sizeof(*dst))
 				dst[i].conf = src->conf;

This also covers SHARED once 3/5 routes it through the same branch.


[PATCH v3 5/5] ethdev: add flow item structure size query

Info: the NULL name checks added to rte_flow_conv_name() and
rte_flow_conv_item_default_mask() are hardening for the strlcpy(NULL)
case described in 2/5 and 3/5. With those two patches applied there
are no gaps left, so this is fine in a feature patch. If you want the
check backported, move it into 2/5 instead.

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v4 1/6] ethdev: fix flow conversion with null buffer
  2026-10-23 12:15 [PATCH v1 1/1] ethdev: add flow item structure size query Anatoly Burakov
                   ` (2 preceding siblings ...)
  2026-10-24 13:50 ` [PATCH v3 1/5] ethdev: fix flow conversion with null buffer Anatoly Burakov
@ 2026-10-27  9:50 ` Anatoly Burakov
  2026-10-27  9:50   ` [PATCH v4 2/6] ethdev: fix missing MACsec flow item descriptor Anatoly Burakov
                     ` (4 more replies)
  3 siblings, 5 replies; 20+ messages in thread
From: Anatoly Burakov @ 2026-10-27  9:50 UTC (permalink / raw)
  To: dev, Ori Kam, Thomas Monjalon, Andrew Rybchenko, Adrien Mazarguil,
	Nelio Laranjeiro

rte_flow_conv_pattern() and rte_flow_conv_actions() document that the
destination buffer may be NULL when the size is zero, which is how
callers query the number of bytes required before allocating. Both
functions nonetheless walked the destination with ++dst in the first
pass and dst -= num before the second, regardless of size.

Pointer arithmetic on a NULL pointer is undefined behavior, and is
reported by UBSan as "applying non-zero offset to null pointer" for
every RTE_FLOW_CONV_OP_PATTERN and RTE_FLOW_CONV_OP_ACTIONS size query.

Keep the destination pointer fixed and index it only inside the
size-guarded writes, which are never reached when size is zero.

Fixes: 063911ee1df4 ("ethdev: add flow API object converter")
Cc: stable@dpdk.org

Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
 lib/ethdev/rte_flow.c | 42 +++++++++++++++++++++++-------------------
 1 file changed, 23 insertions(+), 19 deletions(-)

diff --git a/lib/ethdev/rte_flow.c b/lib/ethdev/rte_flow.c
index 0ebf59165d4..704ed663c5f 100644
--- a/lib/ethdev/rte_flow.c
+++ b/lib/ethdev/rte_flow.c
@@ -921,7 +921,12 @@ rte_flow_conv_pattern(struct rte_flow_item *dst,
 	size_t ret;
 	unsigned int i;
 
-	for (i = 0, off = 0; !num || i != num; ++i, ++src, ++dst) {
+	/*
+	 * dst may be NULL when size is zero, so it is only ever indexed
+	 * inside the size-guarded writes below; pointer arithmetic on a
+	 * NULL pointer is undefined behavior.
+	 */
+	for (i = 0, off = 0; !num || i != num; ++i, ++src) {
 		/**
 		 * allow PMD private flow item
 		 */
@@ -932,7 +937,7 @@ rte_flow_conv_pattern(struct rte_flow_item *dst,
 				(error, ENOTSUP, RTE_FLOW_ERROR_TYPE_ITEM, src,
 				 "cannot convert unknown item type");
 		if (size >= off + sizeof(*dst))
-			*dst = (struct rte_flow_item){
+			dst[i] = (struct rte_flow_item){
 				.type = src->type,
 			};
 		off += sizeof(*dst);
@@ -941,8 +946,7 @@ rte_flow_conv_pattern(struct rte_flow_item *dst,
 	}
 	num = i;
 	src -= num;
-	dst -= num;
-	do {
+	for (i = 0; i != num; ++i, ++src) {
 		if (src->spec) {
 			off = RTE_ALIGN_CEIL(off, sizeof(double));
 			ret = rte_flow_conv_item_spec
@@ -950,7 +954,7 @@ rte_flow_conv_pattern(struct rte_flow_item *dst,
 				 size > off ? size - off : 0, src,
 				 RTE_FLOW_CONV_ITEM_SPEC);
 			if (size && size >= off + ret)
-				dst->spec = (void *)(data + off);
+				dst[i].spec = (void *)(data + off);
 			off += ret;
 
 		}
@@ -961,7 +965,7 @@ rte_flow_conv_pattern(struct rte_flow_item *dst,
 				 size > off ? size - off : 0, src,
 				 RTE_FLOW_CONV_ITEM_LAST);
 			if (size && size >= off + ret)
-				dst->last = (void *)(data + off);
+				dst[i].last = (void *)(data + off);
 			off += ret;
 		}
 		if (src->mask) {
@@ -971,12 +975,10 @@ rte_flow_conv_pattern(struct rte_flow_item *dst,
 				 size > off ? size - off : 0, src,
 				 RTE_FLOW_CONV_ITEM_MASK);
 			if (size && size >= off + ret)
-				dst->mask = (void *)(data + off);
+				dst[i].mask = (void *)(data + off);
 			off += ret;
 		}
-		++src;
-		++dst;
-	} while (--num);
+	}
 	return off;
 }
 
@@ -1014,7 +1016,12 @@ rte_flow_conv_actions(struct rte_flow_action *dst,
 	size_t ret;
 	unsigned int i;
 
-	for (i = 0, off = 0; !num || i != num; ++i, ++src, ++dst) {
+	/*
+	 * dst may be NULL when size is zero, so it is only ever indexed
+	 * inside the size-guarded writes below; pointer arithmetic on a
+	 * NULL pointer is undefined behavior.
+	 */
+	for (i = 0, off = 0; !num || i != num; ++i, ++src) {
 		/**
 		 * allow PMD private flow action
 		 */
@@ -1025,7 +1032,7 @@ rte_flow_conv_actions(struct rte_flow_action *dst,
 				(error, ENOTSUP, RTE_FLOW_ERROR_TYPE_ACTION,
 				 src, "cannot convert unknown action type");
 		if (size >= off + sizeof(*dst))
-			*dst = (struct rte_flow_action){
+			dst[i] = (struct rte_flow_action){
 				.type = src->type,
 			};
 		off += sizeof(*dst);
@@ -1034,8 +1041,7 @@ rte_flow_conv_actions(struct rte_flow_action *dst,
 	}
 	num = i;
 	src -= num;
-	dst -= num;
-	do {
+	for (i = 0; i != num; ++i, ++src) {
 		if (src->type == RTE_FLOW_ACTION_TYPE_INDIRECT) {
 			/*
 			 * Indirect action conf fills the indirect action
@@ -1043,19 +1049,17 @@ rte_flow_conv_actions(struct rte_flow_action *dst,
 			 * of duplicating the pointer memory.
 			 */
 			if (size)
-				dst->conf = src->conf;
+				dst[i].conf = src->conf;
 		} else if (src->conf) {
 			off = RTE_ALIGN_CEIL(off, sizeof(double));
 			ret = rte_flow_conv_action_conf
 				((void *)(data + off),
 				 size > off ? size - off : 0, src);
 			if (size && size >= off + ret)
-				dst->conf = (void *)(data + off);
+				dst[i].conf = (void *)(data + off);
 			off += ret;
 		}
-		++src;
-		++dst;
-	} while (--num);
+	}
 	return off;
 }
 
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v4 2/6] ethdev: fix missing MACsec flow item descriptor
  2026-10-27  9:50 ` [PATCH v4 1/6] " Anatoly Burakov
@ 2026-10-27  9:50   ` Anatoly Burakov
  2026-10-27  9:50   ` [PATCH v4 3/6] ethdev: fix missing flow action descriptors Anatoly Burakov
                     ` (3 subsequent siblings)
  4 siblings, 0 replies; 20+ messages in thread
From: Anatoly Burakov @ 2026-10-27  9:50 UTC (permalink / raw)
  To: dev, Ori Kam, Thomas Monjalon, Andrew Rybchenko, Akhil Goyal

RTE_FLOW_ITEM_TYPE_MACSEC was never added to rte_flow_desc_item[].
The table uses designated initializers, so the MACSEC slot is zero
filled: its name is NULL and its size is zero.

As a result, RTE_FLOW_CONV_OP_ITEM_NAME on MACSEC passes a NULL
source to strlcpy(), and rte_flow_copy() as well as the pattern
conversion operations reject any pattern containing a MACSEC item
as an unknown item type.

Add the missing descriptor entry.

Fixes: 0aaf097585a6 ("ethdev: add MACsec flow item")
Cc: stable@dpdk.org

Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
 lib/ethdev/rte_flow.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lib/ethdev/rte_flow.c b/lib/ethdev/rte_flow.c
index 704ed663c5f..6e544f4bc91 100644
--- a/lib/ethdev/rte_flow.c
+++ b/lib/ethdev/rte_flow.c
@@ -229,6 +229,7 @@ static const struct rte_flow_desc_data rte_flow_desc_item[] = {
 			rte_flow_item_l2tpv2_mask),
 	MK_FLOW_ITEM_MASK(PPP, sizeof(struct rte_flow_item_ppp),
 			rte_flow_item_ppp_mask),
+	MK_FLOW_ITEM(MACSEC, sizeof(struct rte_flow_item_macsec)),
 	MK_FLOW_ITEM_MASK(METER_COLOR, sizeof(struct rte_flow_item_meter_color),
 			rte_flow_item_meter_color_mask),
 	MK_FLOW_ITEM(IPV6_ROUTING_EXT, sizeof(struct rte_flow_item_ipv6_routing_ext)),
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v4 3/6] ethdev: fix missing flow action descriptors
  2026-10-27  9:50 ` [PATCH v4 1/6] " Anatoly Burakov
  2026-10-27  9:50   ` [PATCH v4 2/6] ethdev: fix missing MACsec flow item descriptor Anatoly Burakov
@ 2026-10-27  9:50   ` Anatoly Burakov
  2026-10-27 13:59     ` Stephen Hemminger
  2026-10-27  9:50   ` [PATCH v4 4/6] ethdev: fix indirect action conversion with short buffer Anatoly Burakov
                     ` (2 subsequent siblings)
  4 siblings, 1 reply; 20+ messages in thread
From: Anatoly Burakov @ 2026-10-27  9:50 UTC (permalink / raw)
  To: dev, Ori Kam, Thomas Monjalon, Andrew Rybchenko, Ajit Khaparde,
	Andrey Vesnovaty, Ray Kinsella, Cristian Dumitrescu, Jiawei Wang,
	Haifei Luo, Rakesh Kudurumalla, Jerin Jacob

RTE_FLOW_ACTION_TYPE_SHARED, RTE_FLOW_ACTION_TYPE_METER_COLOR and
RTE_FLOW_ACTION_TYPE_SKIP_CMAN were never added to
rte_flow_desc_action[]. The table uses designated initializers, so
these slots are zero filled: their name is NULL and their size is
zero.

As a result, RTE_FLOW_CONV_OP_ACTION_NAME on any of them passes a
NULL source to strlcpy(), and rte_flow_copy() as well as the action
conversion operations reject any action list containing one of them
as an unknown action type.

Add the missing descriptor entries. Preserve the deprecated SHARED
action handle directly, as for INDIRECT, instead of treating it as
configuration data to copy into the destination buffer.

Fixes: 4d9fd85fb545 ("ethdev: add shared actions to flow API")
Fixes: 5f0d54f372f0 ("ethdev: add pre-defined meter policy API")
Fixes: 6807e8b52a18 ("ethdev: skip congestion management configuration")
Cc: stable@dpdk.org

Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
 lib/ethdev/rte_flow.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/lib/ethdev/rte_flow.c b/lib/ethdev/rte_flow.c
index 6e544f4bc91..e29bcefab0e 100644
--- a/lib/ethdev/rte_flow.c
+++ b/lib/ethdev/rte_flow.c
@@ -324,6 +324,8 @@ static const struct rte_flow_desc_data rte_flow_desc_action[] = {
 	MK_FLOW_ACTION(SET_IPV6_DSCP, sizeof(struct rte_flow_action_set_dscp)),
 	MK_FLOW_ACTION(AGE, sizeof(struct rte_flow_action_age)),
 	MK_FLOW_ACTION(SAMPLE, sizeof(struct rte_flow_action_sample)),
+	/* Deprecated; handle stored in conf as for INDIRECT. */
+	MK_FLOW_ACTION(SHARED, 0),
 	MK_FLOW_ACTION(MODIFY_FIELD,
 		       sizeof(struct rte_flow_action_modify_field)),
 	/**
@@ -334,11 +336,13 @@ static const struct rte_flow_desc_data rte_flow_desc_action[] = {
 	 */
 	MK_FLOW_ACTION(INDIRECT, 0),
 	MK_FLOW_ACTION(CONNTRACK, sizeof(struct rte_flow_action_conntrack)),
+	MK_FLOW_ACTION(METER_COLOR, sizeof(struct rte_flow_action_meter_color)),
 	MK_FLOW_ACTION(PORT_REPRESENTOR, sizeof(struct rte_flow_action_ethdev)),
 	MK_FLOW_ACTION(REPRESENTED_PORT, sizeof(struct rte_flow_action_ethdev)),
 	MK_FLOW_ACTION(METER_MARK, sizeof(struct rte_flow_action_meter_mark)),
 	MK_FLOW_ACTION(SEND_TO_KERNEL, 0),
 	MK_FLOW_ACTION(QUOTA, sizeof(struct rte_flow_action_quota)),
+	MK_FLOW_ACTION(SKIP_CMAN, 0),
 	MK_FLOW_ACTION(IPV6_EXT_PUSH, sizeof(struct rte_flow_action_ipv6_ext_push)),
 	MK_FLOW_ACTION(IPV6_EXT_REMOVE, sizeof(struct rte_flow_action_ipv6_ext_remove)),
 	MK_FLOW_ACTION(INDIRECT_LIST,
@@ -1043,7 +1047,8 @@ rte_flow_conv_actions(struct rte_flow_action *dst,
 	num = i;
 	src -= num;
 	for (i = 0; i != num; ++i, ++src) {
-		if (src->type == RTE_FLOW_ACTION_TYPE_INDIRECT) {
+		if (src->type == RTE_FLOW_ACTION_TYPE_INDIRECT ||
+		    src->type == RTE_FLOW_ACTION_TYPE_SHARED) {
 			/*
 			 * Indirect action conf fills the indirect action
 			 * handler. Copy the action handle directly instead
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v4 4/6] ethdev: fix indirect action conversion with short buffer
  2026-10-27  9:50 ` [PATCH v4 1/6] " Anatoly Burakov
  2026-10-27  9:50   ` [PATCH v4 2/6] ethdev: fix missing MACsec flow item descriptor Anatoly Burakov
  2026-10-27  9:50   ` [PATCH v4 3/6] ethdev: fix missing flow action descriptors Anatoly Burakov
@ 2026-10-27  9:50   ` Anatoly Burakov
  2026-10-27  9:50   ` [PATCH v4 5/6] test/ethdev: add flow conversion tests Anatoly Burakov
  2026-10-27  9:50   ` [PATCH v4 6/6] ethdev: add flow item structure size query Anatoly Burakov
  4 siblings, 0 replies; 20+ messages in thread
From: Anatoly Burakov @ 2026-10-27  9:50 UTC (permalink / raw)
  To: dev, Ori Kam, Thomas Monjalon, Andrew Rybchenko, Suanming Mou

The second pass of rte_flow_conv_actions() copies indirect action
handles whenever the destination size is nonzero, even if the buffer
does not cover the corresponding action entry. This writes beyond
the caller's buffer when an indirect action follows the last entry
that fits. The same applies to deprecated SHARED actions now that
their handles are preserved in the same branch.

Require the destination size to cover the full action entry before
writing its handle.

Fixes: fb131e295ea8 ("ethdev: fix indirect action conversion")
Cc: stable@dpdk.org

Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
 lib/ethdev/rte_flow.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/ethdev/rte_flow.c b/lib/ethdev/rte_flow.c
index e29bcefab0e..cee0acbd568 100644
--- a/lib/ethdev/rte_flow.c
+++ b/lib/ethdev/rte_flow.c
@@ -1054,7 +1054,7 @@ rte_flow_conv_actions(struct rte_flow_action *dst,
 			 * handler. Copy the action handle directly instead
 			 * of duplicating the pointer memory.
 			 */
-			if (size)
+			if (size >= (i + 1) * sizeof(*dst))
 				dst[i].conf = src->conf;
 		} else if (src->conf) {
 			off = RTE_ALIGN_CEIL(off, sizeof(double));
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v4 5/6] test/ethdev: add flow conversion tests
  2026-10-27  9:50 ` [PATCH v4 1/6] " Anatoly Burakov
                     ` (2 preceding siblings ...)
  2026-10-27  9:50   ` [PATCH v4 4/6] ethdev: fix indirect action conversion with short buffer Anatoly Burakov
@ 2026-10-27  9:50   ` Anatoly Burakov
  2026-10-27  9:50   ` [PATCH v4 6/6] ethdev: add flow item structure size query Anatoly Burakov
  4 siblings, 0 replies; 20+ messages in thread
From: Anatoly Burakov @ 2026-10-27  9:50 UTC (permalink / raw)
  To: dev, Thomas Monjalon, Andrew Rybchenko

Add a unit test for rte_flow_conv(), which so far had no coverage.
Cover the item and action name lookups, the item default mask lookup,
pattern and action list conversion with deep copied specification
data, and check that every public item and action type has an entry
in the conversion descriptor tables.

Note that this test is fragile in the sense that because item type list
does not provide a "max" value, each time a new item is added, the max item
value must be updated.

Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
 app/test/meson.build             |   1 +
 app/test/test_ethdev_flow_conv.c | 420 +++++++++++++++++++++++++++++++
 2 files changed, 421 insertions(+)
 create mode 100644 app/test/test_ethdev_flow_conv.c

diff --git a/app/test/meson.build b/app/test/meson.build
index 9805cf5bee8..30dd1fcf832 100644
--- a/app/test/meson.build
+++ b/app/test/meson.build
@@ -77,6 +77,7 @@ source_file_deps = {
     'test_efd_perf.c': ['efd', 'hash'],
     'test_errno.c': [],
     'test_ethdev_api.c': ['ethdev'],
+    'test_ethdev_flow_conv.c': ['ethdev'],
     'test_ethdev_link.c': ['ethdev'],
     'test_ethdev_module_eeprom.c': ['ethdev'],
     'test_event_crypto_adapter.c': ['cryptodev', 'eventdev', 'bus_vdev'],
diff --git a/app/test/test_ethdev_flow_conv.c b/app/test/test_ethdev_flow_conv.c
new file mode 100644
index 00000000000..39a59dd07a1
--- /dev/null
+++ b/app/test/test_ethdev_flow_conv.c
@@ -0,0 +1,420 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2026 Intel Corporation
+ */
+
+#include <stdalign.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_errno.h>
+#include <rte_flow.h>
+
+#include "test.h"
+
+/*
+ * Last known item and action types. Every type up to and including these
+ * must have a descriptor entry in the ethdev flow conversion tables.
+ */
+#define LAST_ITEM_TYPE ((int)RTE_FLOW_ITEM_TYPE_COMPARE)
+#define LAST_ACTION_TYPE ((int)RTE_FLOW_ACTION_TYPE_JUMP_TO_TABLE_INDEX)
+
+/* Guaranteed to be out of range of every descriptor table. */
+#define BOGUS_TYPE INT32_MAX
+
+static int
+test_flow_conv_item_name(void)
+{
+	struct rte_flow_error error;
+	const char *name;
+	char buf[8];
+	int ret;
+
+	/* Name copy. */
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME, buf, sizeof(buf),
+			    RTE_CAST_PTR(const void *, RTE_FLOW_ITEM_TYPE_ETH), &error);
+	TEST_ASSERT_EQUAL(ret, (int)strlen("ETH"), "unexpected return %d",
+			  ret);
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(buf, "ETH", strlen("ETH") + 1,
+				      "name mismatch");
+
+	/* Truncated copy reports the full length. */
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME, buf, 3,
+			    RTE_CAST_PTR(const void *, RTE_FLOW_ITEM_TYPE_MACSEC), &error);
+	TEST_ASSERT_EQUAL(ret, (int)strlen("MACSEC"), "unexpected return %d",
+			  ret);
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(buf, "MA", 3, "truncated name mismatch");
+
+	/* Name pointer. */
+	name = NULL;
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME_PTR, &name,
+			    sizeof(name),
+			    RTE_CAST_PTR(const void *, RTE_FLOW_ITEM_TYPE_MACSEC), &error);
+	TEST_ASSERT_EQUAL(ret, (int)sizeof(const char **),
+			  "unexpected return %d", ret);
+	TEST_ASSERT_NOT_NULL(name, "name pointer not set");
+	TEST_ASSERT(strcmp(name, "MACSEC") == 0, "name mismatch: %s", name);
+
+	/* Out of range type. */
+	rte_errno = 0;
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME_PTR, &name,
+			    sizeof(name), RTE_CAST_PTR(const void *, BOGUS_TYPE), &error);
+	TEST_ASSERT_EQUAL(ret, -EINVAL, "unexpected return %d", ret);
+	TEST_ASSERT_EQUAL(rte_errno, EINVAL, "rte_errno %d", rte_errno);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_flow_conv_action_name(void)
+{
+	struct rte_flow_error error;
+	const char *name;
+	int ret;
+
+	struct {
+		int type;
+		const char *name;
+	} const known[] = {
+		{ RTE_FLOW_ACTION_TYPE_END, "END" },
+		{ RTE_FLOW_ACTION_TYPE_QUEUE, "QUEUE" },
+		{ RTE_FLOW_ACTION_TYPE_SHARED, "SHARED" },
+		{ RTE_FLOW_ACTION_TYPE_METER_COLOR, "METER_COLOR" },
+		{ RTE_FLOW_ACTION_TYPE_SKIP_CMAN, "SKIP_CMAN" },
+		{ RTE_FLOW_ACTION_TYPE_JUMP_TO_TABLE_INDEX,
+		  "JUMP_TO_TABLE_INDEX" },
+	};
+
+	for (unsigned int i = 0; i < RTE_DIM(known); i++) {
+		name = NULL;
+		ret = rte_flow_conv(RTE_FLOW_CONV_OP_ACTION_NAME_PTR, &name,
+				    sizeof(name), RTE_CAST_PTR(const void *, known[i].type),
+				    &error);
+		TEST_ASSERT_EQUAL(ret, (int)sizeof(const char **),
+				  "action type %d: unexpected return %d",
+				  known[i].type, ret);
+		TEST_ASSERT_NOT_NULL(name, "action type %d: name not set",
+				     known[i].type);
+		TEST_ASSERT(strcmp(name, known[i].name) == 0,
+			    "action type %d: name %s, expected %s",
+			    known[i].type, name, known[i].name);
+	}
+
+	rte_errno = 0;
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ACTION_NAME_PTR, &name,
+			    sizeof(name), RTE_CAST_PTR(const void *, BOGUS_TYPE), &error);
+	TEST_ASSERT_EQUAL(ret, -EINVAL, "unexpected return %d", ret);
+	TEST_ASSERT_EQUAL(rte_errno, EINVAL, "rte_errno %d", rte_errno);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_flow_conv_item_default_mask(void)
+{
+	struct rte_flow_error error;
+	const void *mask;
+	int ret;
+
+	mask = NULL;
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR, &mask,
+			    sizeof(mask), RTE_CAST_PTR(const void *, RTE_FLOW_ITEM_TYPE_ETH),
+			    &error);
+	TEST_ASSERT_EQUAL(ret, (int)sizeof(const void **),
+			  "unexpected return %d", ret);
+	/*
+	 * Default masks are static const in rte_flow.h, so each translation
+	 * unit has its own copy: compare contents, not addresses.
+	 */
+	TEST_ASSERT_NOT_NULL(mask, "ETH default mask not set");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(mask, &rte_flow_item_eth_mask,
+				      sizeof(rte_flow_item_eth_mask),
+				      "ETH default mask mismatch");
+
+	/* Known type without a default mask. */
+	mask = &error;
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR, &mask,
+			    sizeof(mask), RTE_CAST_PTR(const void *, RTE_FLOW_ITEM_TYPE_END),
+			    &error);
+	TEST_ASSERT_EQUAL(ret, (int)sizeof(const void **),
+			  "unexpected return %d", ret);
+	TEST_ASSERT_NULL(mask, "END must not have a default mask");
+
+	rte_errno = 0;
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR, &mask,
+			    sizeof(mask), RTE_CAST_PTR(const void *, BOGUS_TYPE), &error);
+	TEST_ASSERT_EQUAL(ret, -EINVAL, "unexpected return %d", ret);
+	TEST_ASSERT_EQUAL(rte_errno, EINVAL, "rte_errno %d", rte_errno);
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * Every public item and action type must have a descriptor entry.
+ * A missing entry would otherwise make the type unknown to all
+ * conversion operations.
+ */
+static int
+test_flow_conv_tables_complete(void)
+{
+	struct rte_flow_error error;
+	const char *name;
+	int ret;
+
+	for (int type = 0; type <= LAST_ITEM_TYPE; type++) {
+		name = NULL;
+		ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME_PTR, &name,
+				    sizeof(name), RTE_CAST_PTR(const void *, type), &error);
+		TEST_ASSERT_EQUAL(ret, (int)sizeof(const char **),
+				  "item type %d has no descriptor", type);
+		TEST_ASSERT_NOT_NULL(name, "item type %d has no name", type);
+	}
+
+	for (int type = 0; type <= LAST_ACTION_TYPE; type++) {
+		name = NULL;
+		ret = rte_flow_conv(RTE_FLOW_CONV_OP_ACTION_NAME_PTR, &name,
+				    sizeof(name), RTE_CAST_PTR(const void *, type), &error);
+		TEST_ASSERT_EQUAL(ret, (int)sizeof(const char **),
+				  "action type %d has no descriptor", type);
+		TEST_ASSERT_NOT_NULL(name, "action type %d has no name", type);
+	}
+
+	return TEST_SUCCESS;
+}
+
+/* Convert a pattern and check spec/mask are deep copied using the table. */
+static int
+test_flow_conv_pattern(void)
+{
+	struct rte_flow_error error;
+	const struct rte_flow_item_eth eth_spec = {
+		.hdr.ether_type = RTE_BE16(RTE_ETHER_TYPE_IPV4),
+	};
+	const struct rte_flow_item_macsec macsec_spec = {
+		.macsec_hdr.tci_an = RTE_MACSEC_TCI_E | 0x1,
+		.macsec_hdr.short_length = 0x2a,
+		.macsec_hdr.packet_number = RTE_BE32(0x01020304),
+	};
+	const struct rte_flow_item_macsec macsec_mask = {
+		.macsec_hdr.tci_an = UINT8_MAX,
+		.macsec_hdr.short_length = 0x3f,
+		.macsec_hdr.packet_number = RTE_BE32(UINT32_MAX),
+	};
+	const struct rte_flow_item pattern[] = {
+		{
+			.type = RTE_FLOW_ITEM_TYPE_ETH,
+			.spec = &eth_spec,
+			.mask = &rte_flow_item_eth_mask,
+		},
+		{
+			.type = RTE_FLOW_ITEM_TYPE_MACSEC,
+			.spec = &macsec_spec,
+			.mask = &macsec_mask,
+		},
+		{
+			.type = RTE_FLOW_ITEM_TYPE_END,
+		},
+	};
+	alignas(double) uint8_t buf[512];
+	struct rte_flow_item *out = (struct rte_flow_item *)buf;
+	size_t needed;
+	int ret;
+
+	/* Size query. */
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN, NULL, 0, pattern,
+			    &error);
+	TEST_ASSERT(ret > 0, "size query failed: %d (%s)", ret,
+		    error.message);
+	needed = ret;
+	TEST_ASSERT(needed >= RTE_DIM(pattern) * sizeof(*out) +
+		    sizeof(eth_spec) + sizeof(rte_flow_item_eth_mask) +
+		    sizeof(macsec_spec) + sizeof(macsec_mask),
+		    "size %zu too small to hold all items", needed);
+	TEST_ASSERT(needed <= sizeof(buf), "buffer too small: %zu", needed);
+
+	memset(buf, 0xff, sizeof(buf));
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_PATTERN, out, sizeof(buf),
+			    pattern, &error);
+	TEST_ASSERT_EQUAL(ret, (int)needed, "unexpected return %d", ret);
+
+	for (unsigned int i = 0; i < RTE_DIM(pattern); i++)
+		TEST_ASSERT_EQUAL(out[i].type, pattern[i].type,
+				  "item %u: type mismatch", i);
+
+	TEST_ASSERT_NOT_NULL(out[0].spec, "ETH spec not copied");
+	TEST_ASSERT(out[0].spec != pattern[0].spec, "ETH spec not deep copied");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(out[0].spec, &eth_spec, sizeof(eth_spec),
+				      "ETH spec mismatch");
+	TEST_ASSERT_NOT_NULL(out[0].mask, "ETH mask not copied");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(out[0].mask, &rte_flow_item_eth_mask,
+				      sizeof(rte_flow_item_eth_mask),
+				      "ETH mask mismatch");
+	TEST_ASSERT_NULL(out[0].last, "ETH last unexpectedly set");
+
+	TEST_ASSERT_NOT_NULL(out[1].spec, "MACSEC spec not copied");
+	TEST_ASSERT(out[1].spec != pattern[1].spec,
+		    "MACSEC spec not deep copied");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(out[1].spec, &macsec_spec,
+				      sizeof(macsec_spec),
+				      "MACSEC spec mismatch");
+	TEST_ASSERT_NOT_NULL(out[1].mask, "MACSEC mask not copied");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(out[1].mask, &macsec_mask,
+				      sizeof(macsec_mask),
+				      "MACSEC mask mismatch");
+
+	TEST_ASSERT_NULL(out[2].spec, "END spec unexpectedly set");
+	TEST_ASSERT_NULL(out[2].mask, "END mask unexpectedly set");
+
+	return TEST_SUCCESS;
+}
+
+/* Convert actions and check configuration is deep copied using the table. */
+static int
+test_flow_conv_actions(void)
+{
+	struct rte_flow_error error;
+	const struct rte_flow_action_meter_color meter_color = {
+		.color = RTE_COLOR_YELLOW,
+	};
+	const struct rte_flow_action_queue queue = {
+		.index = 7,
+	};
+	const struct rte_flow_action actions[] = {
+		{
+			.type = RTE_FLOW_ACTION_TYPE_SKIP_CMAN,
+		},
+		{
+			.type = RTE_FLOW_ACTION_TYPE_METER_COLOR,
+			.conf = &meter_color,
+		},
+		{
+			.type = RTE_FLOW_ACTION_TYPE_QUEUE,
+			.conf = &queue,
+		},
+		{
+			.type = RTE_FLOW_ACTION_TYPE_SHARED,
+			.conf = &queue,
+		},
+		{
+			.type = RTE_FLOW_ACTION_TYPE_END,
+		},
+	};
+	alignas(double) uint8_t buf[512];
+	struct rte_flow_action *out = (struct rte_flow_action *)buf;
+	size_t needed;
+	int ret;
+
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ACTIONS, NULL, 0, actions,
+			    &error);
+	TEST_ASSERT(ret > 0, "size query failed: %d (%s)", ret,
+		    error.message);
+	needed = ret;
+	TEST_ASSERT(needed >= RTE_DIM(actions) * sizeof(*out) +
+		    sizeof(meter_color) + sizeof(queue),
+		    "size %zu too small to hold all actions", needed);
+	TEST_ASSERT(needed <= sizeof(buf), "buffer too small: %zu", needed);
+
+	memset(buf, 0xff, sizeof(buf));
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ACTIONS, out, sizeof(buf),
+			    actions, &error);
+	TEST_ASSERT_EQUAL(ret, (int)needed, "unexpected return %d", ret);
+
+	for (unsigned int i = 0; i < RTE_DIM(actions); i++)
+		TEST_ASSERT_EQUAL(out[i].type, actions[i].type,
+				  "action %u: type mismatch", i);
+
+	TEST_ASSERT_NULL(out[0].conf, "SKIP_CMAN conf unexpectedly set");
+
+	TEST_ASSERT_NOT_NULL(out[1].conf, "METER_COLOR conf not copied");
+	TEST_ASSERT(out[1].conf != actions[1].conf,
+		    "METER_COLOR conf not deep copied");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(out[1].conf, &meter_color,
+				      sizeof(meter_color),
+				      "METER_COLOR conf mismatch");
+
+	TEST_ASSERT_NOT_NULL(out[2].conf, "QUEUE conf not copied");
+	TEST_ASSERT_BUFFERS_ARE_EQUAL(out[2].conf, &queue, sizeof(queue),
+				      "QUEUE conf mismatch");
+
+	TEST_ASSERT(out[3].conf == actions[3].conf,
+		    "SHARED handle not preserved");
+	TEST_ASSERT_NULL(out[4].conf, "END conf unexpectedly set");
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_flow_conv_actions_truncated(void)
+{
+	struct rte_flow_error error;
+	const struct rte_flow_action_queue queue = {
+		.index = 7,
+	};
+	const enum rte_flow_action_type types[] = {
+		RTE_FLOW_ACTION_TYPE_INDIRECT,
+		RTE_FLOW_ACTION_TYPE_SHARED,
+	};
+	struct rte_flow_action actions[] = {
+		{
+			.type = RTE_FLOW_ACTION_TYPE_QUEUE,
+			.conf = &queue,
+		},
+		{
+			.type = RTE_FLOW_ACTION_TYPE_INDIRECT,
+			.conf = &queue,
+		},
+		{
+			.type = RTE_FLOW_ACTION_TYPE_END,
+		},
+	};
+	alignas(double) uint8_t buf[256];
+	size_t needed;
+	int ret;
+
+	for (unsigned int type = 0; type < RTE_DIM(types); type++) {
+		actions[1].type = types[type];
+		ret = rte_flow_conv(RTE_FLOW_CONV_OP_ACTIONS, NULL, 0,
+				    actions, &error);
+		TEST_ASSERT(ret > 0, "size query failed: %d", ret);
+		needed = ret;
+		TEST_ASSERT(needed <= sizeof(buf), "buffer too small: %zu", needed);
+
+		for (size_t size = 0; size <= needed; size++) {
+			memset(buf, 0xa5, sizeof(buf));
+			ret = rte_flow_conv(RTE_FLOW_CONV_OP_ACTIONS, buf, size,
+					    actions, &error);
+			TEST_ASSERT_EQUAL(ret, (int)needed,
+					  "unexpected return %d", ret);
+			for (size_t byte = size; byte < sizeof(buf); byte++)
+				TEST_ASSERT_EQUAL(buf[byte], 0xa5,
+						  "action %d: write at byte %zu past size %zu",
+						  types[type], byte, size);
+		}
+	}
+
+	return TEST_SUCCESS;
+}
+
+static struct unit_test_suite flow_conv_testsuite = {
+	.suite_name = "flow conversion",
+	.setup = NULL,
+	.teardown = NULL,
+	.unit_test_cases = {
+		TEST_CASE(test_flow_conv_item_name),
+		TEST_CASE(test_flow_conv_action_name),
+		TEST_CASE(test_flow_conv_item_default_mask),
+		TEST_CASE(test_flow_conv_tables_complete),
+		TEST_CASE(test_flow_conv_pattern),
+		TEST_CASE(test_flow_conv_actions),
+		TEST_CASE(test_flow_conv_actions_truncated),
+		TEST_CASES_END()
+	}
+};
+
+static int
+test_flow_conv(void)
+{
+	return unit_test_suite_runner(&flow_conv_testsuite);
+}
+
+REGISTER_FAST_TEST(ethdev_flow_conv_autotest, NOHUGE_OK, ASAN_OK,
+		   test_flow_conv);
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v4 6/6] ethdev: add flow item structure size query
  2026-10-27  9:50 ` [PATCH v4 1/6] " Anatoly Burakov
                     ` (3 preceding siblings ...)
  2026-10-27  9:50   ` [PATCH v4 5/6] test/ethdev: add flow conversion tests Anatoly Burakov
@ 2026-10-27  9:50   ` Anatoly Burakov
  4 siblings, 0 replies; 20+ messages in thread
From: Anatoly Burakov @ 2026-10-27  9:50 UTC (permalink / raw)
  To: dev, Thomas Monjalon, Andrew Rybchenko, Ori Kam

Add RTE_FLOW_CONV_OP_ITEM_SIZE to retrieve the fixed specification
structure size for a flow item type using the existing descriptor table.

Also defensively reject descriptor entries without names in item and
action name lookups and in item default-mask lookups. The preceding
descriptor fixes fill the existing gaps; these checks keep future gaps
from returning NULL names or passing NULL to strlcpy().

Extend the flow conversion unit test to cover the new operation.

Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
---
 app/test/test_ethdev_flow_conv.c       | 92 ++++++++++++++++++++++++++
 doc/guides/rel_notes/release_26_11.rst |  8 ++-
 lib/ethdev/rte_flow.c                  | 27 +++++++-
 lib/ethdev/rte_flow.h                  | 18 +++++
 4 files changed, 140 insertions(+), 5 deletions(-)

diff --git a/app/test/test_ethdev_flow_conv.c b/app/test/test_ethdev_flow_conv.c
index 39a59dd07a1..fcf483702d5 100644
--- a/app/test/test_ethdev_flow_conv.c
+++ b/app/test/test_ethdev_flow_conv.c
@@ -21,6 +21,92 @@
 /* Guaranteed to be out of range of every descriptor table. */
 #define BOGUS_TYPE INT32_MAX
 
+static int
+item_size(int type, size_t *size, struct rte_flow_error *error)
+{
+	return rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_SIZE, size, sizeof(*size),
+			     RTE_CAST_PTR(const void *, type), error);
+}
+
+static int
+test_flow_conv_item_size(void)
+{
+	struct rte_flow_error error;
+	size_t size;
+	int ret;
+
+	struct {
+		int type;
+		size_t expected;
+	} const known[] = {
+		{ RTE_FLOW_ITEM_TYPE_END, 0 },
+		{ RTE_FLOW_ITEM_TYPE_VOID, 0 },
+		{ RTE_FLOW_ITEM_TYPE_INVERT, 0 },
+		{ RTE_FLOW_ITEM_TYPE_ETH, sizeof(struct rte_flow_item_eth) },
+		{ RTE_FLOW_ITEM_TYPE_IPV4, sizeof(struct rte_flow_item_ipv4) },
+		{ RTE_FLOW_ITEM_TYPE_GRE_KEY, sizeof(rte_be32_t) },
+		{ RTE_FLOW_ITEM_TYPE_GTPC, sizeof(struct rte_flow_item_gtp) },
+		{ RTE_FLOW_ITEM_TYPE_MACSEC,
+		  sizeof(struct rte_flow_item_macsec) },
+		{ RTE_FLOW_ITEM_TYPE_COMPARE,
+		  sizeof(struct rte_flow_item_compare) },
+	};
+
+	for (unsigned int i = 0; i < RTE_DIM(known); i++) {
+		size = SIZE_MAX;
+		ret = item_size(known[i].type, &size, &error);
+		TEST_ASSERT_EQUAL(ret, (int)sizeof(size_t),
+				  "item type %d: unexpected return %d",
+				  known[i].type, ret);
+		TEST_ASSERT_EQUAL(size, known[i].expected,
+				  "item type %d: size %zu, expected %zu",
+				  known[i].type, size, known[i].expected);
+	}
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_flow_conv_item_size_errors(void)
+{
+	struct rte_flow_error error;
+	size_t size;
+	int ret;
+
+	/* Out of range type. */
+	memset(&error, 0, sizeof(error));
+	rte_errno = 0;
+	ret = item_size(BOGUS_TYPE, &size, &error);
+	TEST_ASSERT_EQUAL(ret, -EINVAL, "unexpected return %d", ret);
+	TEST_ASSERT_EQUAL(rte_errno, EINVAL, "rte_errno %d", rte_errno);
+	TEST_ASSERT_EQUAL(error.type, RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
+			  "unexpected error type %d", error.type);
+	TEST_ASSERT_NOT_NULL(error.message, "error message not set");
+
+	/* PMD private type. */
+	rte_errno = 0;
+	ret = item_size(-1, &size, &error);
+	TEST_ASSERT_EQUAL(ret, -EINVAL, "unexpected return %d", ret);
+	TEST_ASSERT_EQUAL(rte_errno, EINVAL, "rte_errno %d", rte_errno);
+
+	/* Destination too small: nothing written, size still reported. */
+	size = SIZE_MAX;
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_SIZE, &size,
+			    sizeof(size) - 1,
+			    RTE_CAST_PTR(const void *, RTE_FLOW_ITEM_TYPE_ETH), &error);
+	TEST_ASSERT_EQUAL(ret, (int)sizeof(size_t), "unexpected return %d",
+			  ret);
+	TEST_ASSERT_EQUAL(size, SIZE_MAX, "destination was written");
+
+	/* NULL destination with zero size is a pure size query. */
+	ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_SIZE, NULL, 0,
+			    RTE_CAST_PTR(const void *, RTE_FLOW_ITEM_TYPE_ETH), &error);
+	TEST_ASSERT_EQUAL(ret, (int)sizeof(size_t), "unexpected return %d",
+			  ret);
+
+	return TEST_SUCCESS;
+}
+
 static int
 test_flow_conv_item_name(void)
 {
@@ -158,6 +244,7 @@ test_flow_conv_tables_complete(void)
 {
 	struct rte_flow_error error;
 	const char *name;
+	size_t size;
 	int ret;
 
 	for (int type = 0; type <= LAST_ITEM_TYPE; type++) {
@@ -167,6 +254,9 @@ test_flow_conv_tables_complete(void)
 		TEST_ASSERT_EQUAL(ret, (int)sizeof(const char **),
 				  "item type %d has no descriptor", type);
 		TEST_ASSERT_NOT_NULL(name, "item type %d has no name", type);
+		ret = item_size(type, &size, &error);
+		TEST_ASSERT_EQUAL(ret, (int)sizeof(size_t),
+				  "item type %d (%s) has no size", type, name);
 	}
 
 	for (int type = 0; type <= LAST_ACTION_TYPE; type++) {
@@ -399,6 +489,8 @@ static struct unit_test_suite flow_conv_testsuite = {
 	.setup = NULL,
 	.teardown = NULL,
 	.unit_test_cases = {
+		TEST_CASE(test_flow_conv_item_size),
+		TEST_CASE(test_flow_conv_item_size_errors),
 		TEST_CASE(test_flow_conv_item_name),
 		TEST_CASE(test_flow_conv_action_name),
 		TEST_CASE(test_flow_conv_item_default_mask),
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 9259954c0f7..f3755bf7ab5 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -113,10 +113,12 @@ New Features
   loss of lock and Tx fault flags.
   The SFF-8472 decoder also reports the Rx loss of signal and Tx fault state.
 
-* **Added flow item default mask lookup.**
+* **Added flow item default mask and size lookup.**
 
-  Added ``RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR`` conversion operation
-  to ``rte_flow_conv()`` to retrieve the default mask of a flow item type.
+  Added ``RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR`` and
+  ``RTE_FLOW_CONV_OP_ITEM_SIZE`` conversion operations
+  to ``rte_flow_conv()`` to retrieve the default mask
+  and the specification structure size of a flow item type.
 
 * **Added internal ethdev flow graph parser helper API.**
 
diff --git a/lib/ethdev/rte_flow.c b/lib/ethdev/rte_flow.c
index cee0acbd568..ae08e100fa2 100644
--- a/lib/ethdev/rte_flow.c
+++ b/lib/ethdev/rte_flow.c
@@ -1183,7 +1183,7 @@ rte_flow_conv_name(int is_action,
 	const struct desc_info *const info = &info_rep[!!is_action];
 	unsigned int type = (uintptr_t)src;
 
-	if (type >= info->num)
+	if (type >= info->num || info->data[type].name == NULL)
 		return rte_flow_error_set
 			(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
 			 "unknown object type to retrieve the name of");
@@ -1203,7 +1203,8 @@ rte_flow_conv_item_default_mask(void *dst,
 {
 	unsigned int type = (uintptr_t)src;
 
-	if (type >= RTE_DIM(rte_flow_desc_item))
+	if (type >= RTE_DIM(rte_flow_desc_item) ||
+	    rte_flow_desc_item[type].name == NULL)
 		return rte_flow_error_set
 			(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
 			 "unknown item type to retrieve the default mask of");
@@ -1212,6 +1213,25 @@ rte_flow_conv_item_default_mask(void *dst,
 	return sizeof(const void **);
 }
 
+/** Retrieve the specification structure size of a flow item type. */
+static int
+rte_flow_conv_item_size(void *dst,
+			const size_t size,
+			const void *src,
+			struct rte_flow_error *error)
+{
+	unsigned int type = (uintptr_t)src;
+
+	if (type >= RTE_DIM(rte_flow_desc_item) ||
+	    rte_flow_desc_item[type].name == NULL)
+		return rte_flow_error_set
+			(error, EINVAL, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+			 "unknown item type to retrieve the size of");
+	if (size >= sizeof(size_t))
+		*(size_t *)dst = rte_flow_desc_item[type].size;
+	return sizeof(size_t);
+}
+
 /** Helper function to convert flow API objects. */
 RTE_EXPORT_SYMBOL(rte_flow_conv)
 int
@@ -1276,6 +1296,9 @@ rte_flow_conv(enum rte_flow_conv_op op,
 	case RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR:
 		ret = rte_flow_conv_item_default_mask(dst, size, src, error);
 		break;
+	case RTE_FLOW_CONV_OP_ITEM_SIZE:
+		ret = rte_flow_conv_item_size(dst, size, src, error);
+		break;
 	default:
 		ret = rte_flow_error_set
 		(error, ENOTSUP, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
diff --git a/lib/ethdev/rte_flow.h b/lib/ethdev/rte_flow.h
index df8101e4395..ff1ffd03e73 100644
--- a/lib/ethdev/rte_flow.h
+++ b/lib/ethdev/rte_flow.h
@@ -4426,6 +4426,24 @@ enum rte_flow_conv_op {
 	 *   @code const void ** @endcode
 	 */
 	RTE_FLOW_CONV_OP_ITEM_DEFAULT_MASK_PTR,
+
+	/**
+	 * Retrieve the fixed size of an item specification structure.
+	 *
+	 * Writes the size of the item specification structure to @p dst,
+	 * or zero for known item types without one. Does not include
+	 * variable-length or externally referenced data (for example, RAW
+	 * pattern bytes). Unknown and PMD-private item types return an error.
+	 *
+	 * No output is written if @p size is smaller than sizeof(size_t).
+	 * The return value is sizeof(size_t), not the item structure size.
+	 *
+	 * - @p src type:
+	 *   @code (const void *)(uintptr_t)enum rte_flow_item_type @endcode
+	 * - @p dst type:
+	 *   @code size_t * @endcode
+	 */
+	RTE_FLOW_CONV_OP_ITEM_SIZE,
 };
 
 /**
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* Re: [PATCH v3 1/5] ethdev: fix flow conversion with null buffer
  2026-10-26 16:51   ` [PATCH v3 1/5] ethdev: fix flow conversion with null buffer Stephen Hemminger
@ 2026-10-27 10:56     ` Burakov, Anatoly
  0 siblings, 0 replies; 20+ messages in thread
From: Burakov, Anatoly @ 2026-10-27 10:56 UTC (permalink / raw)
  To: Stephen Hemminger; +Cc: dev

On 10/26/2026 5:51 PM, Stephen Hemminger wrote:
> On Sat, 24 Oct 2026 14:50:28 +0100
> Anatoly Burakov <anatoly.burakov@intel.com> wrote:
> 
>> rte_flow_conv_pattern() and rte_flow_conv_actions() document that the
>> destination buffer may be NULL when the size is zero, which is how
>> callers query the number of bytes required before allocating. Both
>> functions nonetheless walked the destination with ++dst in the first
>> pass and dst -= num before the second, regardless of size.
>>
>> Pointer arithmetic on a NULL pointer is undefined behavior, and is
>> reported by UBSan as "applying non-zero offset to null pointer" for
>> every RTE_FLOW_CONV_OP_PATTERN and RTE_FLOW_CONV_OP_ACTIONS size query.
>>
>> Keep the destination pointer fixed and index it only inside the
>> size-guarded writes, which are never reached when size is zero.
>>
>> Fixes: 063911ee1df4 ("ethdev: add flow API object converter")
>> Cc: stable@dpdk.org
>>
>> Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
>> ---
> 
> Manually run AI review sees some problems.
> 
> Subject: Re: [PATCH v3 0/5] ethdev: flow conversion fixes
> 
> Series applies cleanly to main and each commit builds with
> -Dwerror=true. Descriptor tables now cover every item type through
> COMPARE and every action type through JUMP_TO_TABLE_INDEX.
> 
> One correctness problem in 3/5, plus a pre-existing out-of-bounds
> write on the same code path that should be fixed while here.
> 
> 
> [PATCH v3 3/5] ethdev: fix missing flow action descriptors
> 
> Error: SHARED is now accepted but its handle is not preserved.
> 
> The new entry is MK_FLOW_ACTION(SHARED, 0) with a comment saying the
> handle is stored in conf as for INDIRECT. rte_flow_conv_actions()
> only special-cases RTE_FLOW_ACTION_TYPE_INDIRECT. SHARED with a
> non-NULL conf falls into the generic branch, rte_flow_conv_copy()
> copies zero bytes (desc size 0), and dst[i].conf is set to
> data + off, a pointer into the output buffer instead of the handle.
> 
> Before this patch SHARED was rejected with ENOTSUP. After it,
> rte_flow_conv() and rte_flow_copy() succeed and return a corrupted
> action. Handle it the same way as INDIRECT:
> 
> -		if (src->type == RTE_FLOW_ACTION_TYPE_INDIRECT) {
> +		if (src->type == RTE_FLOW_ACTION_TYPE_INDIRECT ||
> +		    src->type == RTE_FLOW_ACTION_TYPE_SHARED) {
> 
> Either that, or leave SHARED out of the table since it is deprecated;
> but then the completeness test in 4/5 needs an exception for it.
> 
> Please add a SHARED action with a non-NULL conf to
> test_flow_conv_actions() and check out[i].conf == actions[i].conf.
> That would have caught this.
> 
> 
> [PATCH v3 1/5] ethdev: fix flow conversion with null buffer
> 
> Warning: INDIRECT conf write is not bounded by the buffer size.
> 
> Pre-existing, but this patch rewrites the line. In the second pass:
> 
> 	if (size)
> 		dst[i].conf = src->conf;
> 
> Every other write into dst[] is guarded by size covering the entry.
> With actions { QUEUE, INDIRECT, END } and size equal to
> sizeof(struct rte_flow_action), the first pass writes only dst[0],
> then the second pass writes dst[1].conf past the end of the caller's
> buffer. Suggest:
> 
> -			if (size)
> +			if (size >= (i + 1) * sizeof(*dst))
>   				dst[i].conf = src->conf;
> 
> This also covers SHARED once 3/5 routes it through the same branch.
> 
> 
> [PATCH v3 5/5] ethdev: add flow item structure size query
> 
> Info: the NULL name checks added to rte_flow_conv_name() and
> rte_flow_conv_item_default_mask() are hardening for the strlcpy(NULL)
> case described in 2/5 and 3/5. With those two patches applied there
> are no gaps left, so this is fine in a feature patch. If you want the
> check backported, move it into 2/5 instead.

Thanks, these seem like real issues so I'll take a look.

-- 
Thanks,
Anatoly

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v4 3/6] ethdev: fix missing flow action descriptors
  2026-10-27  9:50   ` [PATCH v4 3/6] ethdev: fix missing flow action descriptors Anatoly Burakov
@ 2026-10-27 13:59     ` Stephen Hemminger
  0 siblings, 0 replies; 20+ messages in thread
From: Stephen Hemminger @ 2026-10-27 13:59 UTC (permalink / raw)
  To: Anatoly Burakov
  Cc: dev, Ori Kam, Thomas Monjalon, Andrew Rybchenko, Ajit Khaparde,
	Andrey Vesnovaty, Ray Kinsella, Cristian Dumitrescu, Jiawei Wang,
	Haifei Luo, Rakesh Kudurumalla, Jerin Jacob

On Tue, 27 Oct 2026 09:50:10 +0000
Anatoly Burakov <anatoly.burakov@intel.com> wrote:

> RTE_FLOW_ACTION_TYPE_SHARED, RTE_FLOW_ACTION_TYPE_METER_COLOR and
> RTE_FLOW_ACTION_TYPE_SKIP_CMAN were never added to
> rte_flow_desc_action[]. The table uses designated initializers, so
> these slots are zero filled: their name is NULL and their size is
> zero.
> 
> As a result, RTE_FLOW_CONV_OP_ACTION_NAME on any of them passes a
> NULL source to strlcpy(), and rte_flow_copy() as well as the action
> conversion operations reject any action list containing one of them
> as an unknown action type.
> 
> Add the missing descriptor entries. Preserve the deprecated SHARED
> action handle directly, as for INDIRECT, instead of treating it as
> configuration data to copy into the destination buffer.
> 
> Fixes: 4d9fd85fb545 ("ethdev: add shared actions to flow API")
> Fixes: 5f0d54f372f0 ("ethdev: add pre-defined meter policy API")
> Fixes: 6807e8b52a18 ("ethdev: skip congestion management configuration")
> Cc: stable@dpdk.org
> 
> Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
> ---

Applied to next-net.

AI saw some sequencing issue here which might make backport harder.

Subject: Re: [PATCH v4 0/6] ethdev: flow conversion fixes

v4 addresses the v3 feedback. SHARED handles are now preserved
alongside INDIRECT (3/6), the INDIRECT/SHARED conf write is bounded
by the destination entry (4/6), and the new truncated-buffer test
walks every size from 0 to the needed length, checking for writes
past the end. Series applies cleanly to main and each commit
builds with -Dwerror=true. Fixes: tags resolve correctly.


[PATCH v4 3/6] ethdev: fix missing flow action descriptors
[PATCH v4 4/6] ethdev: fix indirect action conversion with short buffer

Info: consider swapping these two.

3/6 sends SHARED through the INDIRECT branch while that write is
still unbounded, and 4/6 then fixes it. Both are marked for stable,
so a backport of 3/6 without 4/6 opens the overflow for SHARED as
well. If 4/6 goes first it applies on its own, and 3/6 then simply
extends an already-correct branch. The SHARED sentence in the 4/6
commit message would then move to 3/6.

Not a blocker.

Reviewed-by: Stephen Hemminger <stephen@networkplumber.org>

^ permalink raw reply	[flat|nested] 20+ messages in thread

end of thread, other threads:[~2026-10-27 14:00 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-23 12:15 [PATCH v1 1/1] ethdev: add flow item structure size query Anatoly Burakov
2026-10-23 16:04 ` Stephen Hemminger
2026-10-24 12:04 ` [PATCH v2 1/4] ethdev: fix missing MACsec flow item descriptor Anatoly Burakov
2026-10-24 12:04   ` [PATCH v2 2/4] ethdev: fix missing flow action descriptors Anatoly Burakov
2026-10-24 12:04   ` [PATCH v2 3/4] test/ethdev: add flow conversion tests Anatoly Burakov
2026-10-24 12:04   ` [PATCH v2 4/4] ethdev: add flow item structure size query Anatoly Burakov
2026-10-24 13:50 ` [PATCH v3 1/5] ethdev: fix flow conversion with null buffer Anatoly Burakov
2026-10-24 13:50   ` [PATCH v3 2/5] ethdev: fix missing MACsec flow item descriptor Anatoly Burakov
2026-10-24 13:50   ` [PATCH v3 3/5] ethdev: fix missing flow action descriptors Anatoly Burakov
2026-10-24 13:50   ` [PATCH v3 4/5] test/ethdev: add flow conversion tests Anatoly Burakov
2026-10-24 13:50   ` [PATCH v3 5/5] ethdev: add flow item structure size query Anatoly Burakov
2026-10-26 16:51   ` [PATCH v3 1/5] ethdev: fix flow conversion with null buffer Stephen Hemminger
2026-10-27 10:56     ` Burakov, Anatoly
2026-10-27  9:50 ` [PATCH v4 1/6] " Anatoly Burakov
2026-10-27  9:50   ` [PATCH v4 2/6] ethdev: fix missing MACsec flow item descriptor Anatoly Burakov
2026-10-27  9:50   ` [PATCH v4 3/6] ethdev: fix missing flow action descriptors Anatoly Burakov
2026-10-27 13:59     ` Stephen Hemminger
2026-10-27  9:50   ` [PATCH v4 4/6] ethdev: fix indirect action conversion with short buffer Anatoly Burakov
2026-10-27  9:50   ` [PATCH v4 5/6] test/ethdev: add flow conversion tests Anatoly Burakov
2026-10-27  9:50   ` [PATCH v4 6/6] ethdev: add flow item structure size query Anatoly Burakov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox