[PATCH v2 3/9] bpf: reject Tx callback installation with MT_LOCKFREE
Konstantin Ananyev
konstantin.v.ananyev at yandex.ru
Mon Oct 19 23:22:50 CEST 2026
> The BPF ethdev packet callback synchronization mechanism expects only
> one dataplane thread per port/queue pair at a time. When
> RTE_ETH_TX_OFFLOAD_MT_LOCKFREE is enabled, multiple threads can invoke
> rte_eth_tx_burst() concurrently on the same queue without locks. This
> breaks the callback parity counter and can lead to use-after-free or
> deadlocks during BPF program unload.
>
> Reject BPF Tx callback installation with -ENOTSUP when
> RTE_ETH_TX_OFFLOAD_MT_LOCKFREE is enabled on the port or queue.
> Add a unit test in test_bpf verifying this rejection.
Should we be more generic here and reject any TX callbacks for
RTE_ETH_TX_OFFLOAD_MT_LOCKFREE devices?
Right now callbacks are not allowed for hairpin queues.
As another thing: AFAIK, ethdev callbacks (both RX and TX) will survive
dev stop/configure/start sequence.
We probably need to handle both scenarios.
> Bugzilla ID: 2051
> Fixes: a93ff62a8938 ("bpf: introduce basic Rx/Tx filters")
> Cc: stable at dpdk.org
>
> Reported-by: Stephen Hemminger <stephen at networkplumber.org>
> Signed-off-by: Marat Khalili <qm2k21 at gmail.com>
> ---
> app/test/test_bpf.c | 103 ++++++++++++++++++++++++++++++++++++++++++++
> lib/bpf/bpf_pkt.c | 46 ++++++++++++++++++++
> 2 files changed, 149 insertions(+)
>
> diff --git a/app/test/test_bpf.c b/app/test/test_bpf.c
> index 3205afaa63..02523984b5 100644
> --- a/app/test/test_bpf.c
> +++ b/app/test/test_bpf.c
> @@ -4617,6 +4617,107 @@ test_bpf_elf_rx_load(load_elf_image_t load_elf_image)
> return ret == 0 ? TEST_SUCCESS : TEST_FAILED;
> }
>
> +static int
> +null_vdev_mt_setup(const char *name, uint16_t *port, struct rte_mempool *pool)
> +{
> + struct rte_eth_conf conf = {
> + .txmode = {
> + .offloads = RTE_ETH_TX_OFFLOAD_MT_LOCKFREE,
> + },
> + };
> + struct rte_eth_txconf txconf = { };
> + struct rte_eth_rxconf rxconf = { };
> + int ret;
> +
> + ret = rte_vdev_init(name, NULL);
> + TEST_ASSERT(ret == 0, "rte_vdev_init(%s) failed: %d", name, ret);
> +
> + ret = rte_eth_dev_get_port_by_name(name, port);
> + TEST_ASSERT(ret == 0, "failed to get port id for %s: %d", name, ret);
> +
> + ret = rte_eth_dev_configure(*port, 1, 1, &conf);
> + TEST_ASSERT(ret == 0, "failed to configure port %u: %d", *port, ret);
> +
> + ret = rte_eth_tx_queue_setup(*port, 0, BPF_TEST_BURST, SOCKET_ID_ANY, &txconf);
> + TEST_ASSERT(ret == 0, "failed to setup tx queue port %u: %d", *port, ret);
> +
> + ret = rte_eth_rx_queue_setup(*port, 0, BPF_TEST_BURST, SOCKET_ID_ANY,
> + &rxconf, pool);
> + TEST_ASSERT(ret == 0, "failed to setup rx queue port %u: %d", *port, ret);
> +
> + ret = rte_eth_dev_start(*port);
> + TEST_ASSERT(ret == 0, "failed to start port %u: %d", *port, ret);
> +
> + return 0;
> +}
> +
> +/* Test that installing a Tx filter is rejected when MT_LOCKFREE is enabled */
> +static int
> +test_bpf_elf_tx_mt_lockfree(load_elf_image_t load_elf_image)
> +{
> + static const struct rte_bpf_arg prog_arg = {
> + .type = RTE_BPF_ARG_PTR,
> + .size = sizeof(struct dummy_net),
> + };
> + static const char null_dev[] = "net_null_bpf_mt";
> + struct rte_mempool *mb_pool = NULL;
> + struct rte_bpf *bpf = NULL;
> + uint16_t port = UINT16_MAX;
> + int ret;
> +
> + mb_pool = rte_pktmbuf_pool_create("bpf_mt_test_pool", BPF_TEST_POOLSIZE,
> + 0, 0, RTE_MBUF_DEFAULT_BUF_SIZE,
> + SOCKET_ID_ANY);
> + TEST_ASSERT_NOT_NULL(mb_pool, "failed to create mempool");
> +
> + ret = null_vdev_mt_setup(null_dev, &port, mb_pool);
> + if (ret != 0)
> + goto fail;
> +
> + bpf = load_elf_image(app_test_bpf_filter_o, app_test_bpf_filter_o_len, "filter",
> + NULL, 0, &prog_arg);
> + if (bpf == NULL) {
> + printf("%s@%d: failed to load BPF filter image\n",
> + __func__, __LINE__);
> + ret = TEST_FAILED;
> + goto fail;
> + }
> +
> + /* Tx installation must fail with -ENOTSUP due to MT_LOCKFREE */
> + ret = rte_bpf_eth_tx_install(port, 0, bpf, 0);
> + if (ret != -ENOTSUP) {
> + printf("%s@%d: expected rte_bpf_eth_tx_install to return -ENOTSUP, got %d\n",
> + __func__, __LINE__, ret);
> + ret = TEST_FAILED;
> + goto fail;
> + }
> +
> + /* Rx installation on the same port must still succeed */
> + ret = rte_bpf_eth_rx_install(port, 0, bpf, 0);
> + if (ret != 0) {
> + printf("%s@%d: expected rte_bpf_eth_rx_install to succeed, got %d\n",
> + __func__, __LINE__, ret);
> + ret = TEST_FAILED;
> + goto fail;
> + }
> + rte_bpf_eth_rx_unload(port, 0);
> + /* bpf is destroyed by rte_bpf_eth_rx_unload */
> + bpf = NULL;
> +
> + ret = TEST_SUCCESS;
> +
> +fail:
> + if (bpf != NULL)
> + rte_bpf_destroy(bpf);
> +
> + if (port != UINT16_MAX) {
> + rte_eth_dev_stop(port);
> + rte_vdev_uninit(null_dev);
> + }
> +
> + rte_mempool_free(mb_pool);
> + return ret;
> +}
>
> static int
> test_bpf_elf(void)
> @@ -4633,6 +4734,8 @@ test_bpf_elf(void)
> ret = test_bpf_elf_load(elf_image_loaders[li]);
> if (ret == TEST_SUCCESS)
> ret = test_bpf_elf_tx_load(elf_image_loaders[li]);
> + if (ret == TEST_SUCCESS)
> + ret = test_bpf_elf_tx_mt_lockfree(elf_image_loaders[li]);
> if (ret == TEST_SUCCESS)
> ret = test_bpf_elf_rx_load(elf_image_loaders[li]);
> }
> diff --git a/lib/bpf/bpf_pkt.c b/lib/bpf/bpf_pkt.c
> index 4223ef624e..eab85f7417 100644
> --- a/lib/bpf/bpf_pkt.c
> +++ b/lib/bpf/bpf_pkt.c
> @@ -552,6 +552,48 @@ rte_bpf_eth_tx_unload(uint16_t port, uint16_t queue)
> rte_spinlock_unlock(&cbh->lock);
> }
>
> +/*
> + * Check if the supplied port/queue pair is suitable for BPF callback execution.
> + */
> +static int
> +bpf_eth_check_mt(const struct bpf_eth_cbh *cbh, uint16_t port, uint16_t queue)
> +{
> + struct rte_eth_txq_info qinfo;
> + struct rte_eth_conf conf;
> + int ret;
> +
> + if (cbh->type == BPF_ETH_TX) {
> + ret = rte_eth_dev_conf_get(port, &conf);
> + if (ret != 0) {
> + RTE_BPF_LOG_LINE(ERR,
> + "%s(%u, %u): failed to get device configuration: %d;",
> + __func__, port, queue, ret);
> + return ret;
> + }
> +
> + if ((conf.txmode.offloads & RTE_ETH_TX_OFFLOAD_MT_LOCKFREE) != 0) {
> + RTE_BPF_LOG_LINE(ERR,
> + "%s(%u, %u): MT_LOCKFREE Tx offload is enabled on port;",
> + __func__, port, queue);
> + return -ENOTSUP;
> + }
> +
> + /*
> + * Ignore errors from tx_queue_info_get: the PMD might not implement
> + * txq_info_get (e.g. net/null), or the queue might not be set up yet.
> + */
> + if (rte_eth_tx_queue_info_get(port, queue, &qinfo) == 0 &&
> + (qinfo.conf.offloads & RTE_ETH_TX_OFFLOAD_MT_LOCKFREE) != 0) {
> + RTE_BPF_LOG_LINE(ERR,
> + "%s(%u, %u): MT_LOCKFREE Tx offload is enabled on queue;",
> + __func__, port, queue);
> + return -ENOTSUP;
> + }
> + }
> +
> + return 0;
> +}
> +
> static int
> bpf_eth_elf_install(struct bpf_eth_cbh *cbh, uint16_t port, uint16_t queue,
> struct rte_bpf *bpf, uint32_t flags)
> @@ -572,6 +614,10 @@ bpf_eth_elf_install(struct bpf_eth_cbh *cbh, uint16_t port, uint16_t queue,
> if (bpf->prm.nb_prog_arg != 1)
> return -EINVAL;
>
> + rc = bpf_eth_check_mt(cbh, port, queue);
> + if (rc != 0)
> + return rc;
> +
> if (cbh->type == BPF_ETH_RX)
> frx = select_rx_callback(bpf->prm.prog_arg[0].type, flags);
> else
More information about the stable
mailing list