patch 'bpf: disallow empty program' has been queued to stable release 23.11.7
Shani Peretz
shperetz at nvidia.com
Tue Mar 31 08:24:22 CEST 2026
Hi,
FYI, your patch has been queued to stable release 23.11.7
Note it hasn't been pushed to http://dpdk.org/browse/dpdk-stable yet.
It will be pushed if I get no objections before 04/05/26. So please
shout if anyone has objections.
Also note that after the patch there's a diff of the upstream commit vs the
patch applied to the branch. This will indicate if there was any rebasing
needed to apply to the stable branch. If there were code changes for rebasing
(ie: not only metadata diffs), please double check that the rebase was
correctly done.
Queued patches are on a temporary branch at:
https://github.com/shanipr/dpdk-stable
This queued commit can be viewed at:
https://github.com/shanipr/dpdk-stable/commit/1fe4cd01149804786a9ff38d3bb3f572a0548f65
Thanks.
Shani
---
>From 1fe4cd01149804786a9ff38d3bb3f572a0548f65 Mon Sep 17 00:00:00 2001
From: Marat Khalili <marat.khalili at huawei.com>
Date: Tue, 27 Jan 2026 11:49:40 +0000
Subject: [PATCH] bpf: disallow empty program
[ upstream commit cee21cc5be82faef74bb1b8f84407cc92de5dee7 ]
Add tests for some simple cases:
* Program with no instructions;
* Program with only EXIT instruction but no return value set;
* Program with return value set but no EXIT instruction;
* Minimal valid program with return value set and an EXIT instruction.
Fix found bugs:
* a program with no instructions was accepted;
* a program with no EXIT instruction read outside the buffer.
Fixes: 6e12ec4c4d6d ("bpf: add more checks")
Signed-off-by: Marat Khalili <marat.khalili at huawei.com>
Acked-by: Konstantin Ananyev <konstantin.ananyev at huawei.com>
Acked-by: Stephen Hemminger <stephen at networkplumber.org>
---
app/test/test_bpf.c | 118 +++++++++++++++++++++++++++++++++++++++++
lib/bpf/bpf_load.c | 2 +-
lib/bpf/bpf_validate.c | 20 +++++--
3 files changed, 135 insertions(+), 5 deletions(-)
diff --git a/app/test/test_bpf.c b/app/test/test_bpf.c
index 9eb44f9928..ea2ac7e7a1 100644
--- a/app/test/test_bpf.c
+++ b/app/test/test_bpf.c
@@ -33,6 +33,124 @@ test_bpf(void)
#include <rte_ip.h>
+/* Tests of most simple BPF programs (no instructions, one instruction etc.) */
+
+/*
+ * Try to load a simple bpf program from the instructions array.
+ *
+ * When `expected_errno` is zero, expect it to load successfully.
+ * When `expected_errno` is non-zero, expect it to fail with this `rte_errno`.
+ *
+ * @param nb_ins
+ * Number of instructions in the `ins` array.
+ * @param ins
+ * BPF instructions array.
+ * @param expected_errno
+ * Expected result.
+ * @return
+ * TEST_SUCCESS on success, error code on failure.
+ */
+static int
+bpf_load_test(uint32_t nb_ins, const struct ebpf_insn *ins, int expected_errno)
+{
+ const struct rte_bpf_prm prm = {
+ .ins = ins,
+ .nb_ins = nb_ins,
+ .prog_arg = {
+ .type = RTE_BPF_ARG_RAW,
+ .size = sizeof(uint64_t),
+ },
+ };
+
+ struct rte_bpf *const bpf = rte_bpf_load(&prm);
+ const int actual_errno = rte_errno;
+ rte_bpf_destroy(bpf);
+
+ if (expected_errno != 0) {
+ RTE_TEST_ASSERT_EQUAL(bpf, NULL,
+ "expect rte_bpf_load() == NULL");
+ RTE_TEST_ASSERT_EQUAL(actual_errno, expected_errno,
+ "expect rte_errno == %d, found %d",
+ expected_errno, actual_errno);
+ } else
+ RTE_TEST_ASSERT_NOT_EQUAL(bpf, NULL,
+ "expect rte_bpf_load() != NULL");
+
+ return TEST_SUCCESS;
+}
+
+/*
+ * Try and load completely empty BPF program.
+ * Should fail because there is no EXIT (and also return value is undefined).
+ */
+static int
+test_no_instructions(void)
+{
+ static const struct ebpf_insn ins[] = {};
+ return bpf_load_test(RTE_DIM(ins), ins, EINVAL);
+}
+
+REGISTER_FAST_TEST(bpf_no_instructions_autotest, NOHUGE_OK, ASAN_OK, test_no_instructions);
+
+/*
+ * Try and load a BPF program comprising single EXIT instruction.
+ * Should fail because the return value is undefined.
+ */
+static int
+test_exit_only(void)
+{
+ static const struct ebpf_insn ins[] = {
+ {
+ .code = (BPF_JMP | EBPF_EXIT),
+ },
+ };
+ return bpf_load_test(RTE_DIM(ins), ins, EINVAL);
+}
+
+REGISTER_FAST_TEST(bpf_exit_only_autotest, NOHUGE_OK, ASAN_OK, test_exit_only);
+
+/*
+ * Try and load a BPF program with no EXIT instruction.
+ * Should fail because of this.
+ */
+static int
+test_no_exit(void)
+{
+ static const struct ebpf_insn ins[] = {
+ {
+ /* Set return value to the program argument. */
+ .code = (EBPF_ALU64 | EBPF_MOV | BPF_X),
+ .src_reg = EBPF_REG_1,
+ .dst_reg = EBPF_REG_0,
+ },
+ };
+ return bpf_load_test(RTE_DIM(ins), ins, EINVAL);
+}
+
+REGISTER_FAST_TEST(bpf_no_exit_autotest, NOHUGE_OK, ASAN_OK, test_no_exit);
+
+/*
+ * Try and load smallest possible valid BPF program.
+ */
+static int
+test_minimal_working(void)
+{
+ static const struct ebpf_insn ins[] = {
+ {
+ /* Set return value to the program argument. */
+ .code = (EBPF_ALU64 | EBPF_MOV | BPF_X),
+ .src_reg = EBPF_REG_1,
+ .dst_reg = EBPF_REG_0,
+ },
+ {
+ .code = (BPF_JMP | EBPF_EXIT),
+ },
+ };
+ return bpf_load_test(RTE_DIM(ins), ins, 0);
+}
+
+REGISTER_FAST_TEST(bpf_minimal_working_autotest, NOHUGE_OK, ASAN_OK, test_minimal_working);
+
/*
* Basic functional tests for librte_bpf.
* The main procedure - load eBPF program, execute it and
diff --git a/lib/bpf/bpf_load.c b/lib/bpf/bpf_load.c
index 45ce9210da..ae5ca44c14 100644
--- a/lib/bpf/bpf_load.c
+++ b/lib/bpf/bpf_load.c
@@ -86,7 +86,7 @@ rte_bpf_load(const struct rte_bpf_prm *prm)
int32_t rc;
uint32_t i;
- if (prm == NULL || prm->ins == NULL ||
+ if (prm == NULL || prm->ins == NULL || prm->nb_ins == 0 ||
(prm->nb_xsym != 0 && prm->xsym == NULL)) {
rte_errno = EINVAL;
return NULL;
diff --git a/lib/bpf/bpf_validate.c b/lib/bpf/bpf_validate.c
index da8d5f3deb..76f08e4df3 100644
--- a/lib/bpf/bpf_validate.c
+++ b/lib/bpf/bpf_validate.c
@@ -1827,7 +1827,7 @@ add_edge(struct bpf_verifier *bvf, struct inst_node *node, uint32_t nidx)
{
uint32_t ne;
- if (nidx > bvf->prm->nb_ins) {
+ if (nidx >= bvf->prm->nb_ins) {
RTE_BPF_LOG(ERR,
"%s: program boundary violation at pc: %u, next pc: %u\n",
__func__, get_node_idx(bvf, node), nidx);
@@ -1886,14 +1886,20 @@ get_prev_node(struct bpf_verifier *bvf, struct inst_node *node)
* Control Flow Graph (CFG).
* Information collected at this path would be used later
* to determine is there any loops, and/or unreachable instructions.
+ * PREREQUISITE: there is at least one node.
*/
static void
dfs(struct bpf_verifier *bvf)
{
struct inst_node *next, *node;
- node = bvf->in;
- while (node != NULL) {
+ RTE_ASSERT(bvf->nb_nodes != 0);
+ /*
+ * Since there is at least one node, node with index 0 always exists;
+ * it is our program entry point.
+ */
+ node = &bvf->in[0];
+ do {
if (node->colour == WHITE)
set_node_colour(bvf, node, GREY);
@@ -1923,7 +1929,7 @@ dfs(struct bpf_verifier *bvf)
}
} else
node = NULL;
- }
+ } while (node != NULL);
}
/*
@@ -2062,6 +2068,12 @@ validate(struct bpf_verifier *bvf)
if (rc != 0)
return rc;
+ if (bvf->nb_nodes == 0) {
+ RTE_BPF_LOG(ERR, "%s(%p) the program is empty\n",
+ __func__, bvf);
+ return -EINVAL;
+ }
+
dfs(bvf);
RTE_BPF_LOG(DEBUG, "%s(%p) stats:\n"
--
2.43.0
---
Diff of the applied patch vs upstream commit (please double-check if non-empty:
---
--- - 2026-03-31 00:32:34.612388805 +0300
+++ 0063-bpf-disallow-empty-program.patch 2026-03-31 00:32:29.148356000 +0300
@@ -1 +1 @@
-From cee21cc5be82faef74bb1b8f84407cc92de5dee7 Mon Sep 17 00:00:00 2001
+From 1fe4cd01149804786a9ff38d3bb3f572a0548f65 Mon Sep 17 00:00:00 2001
@@ -5,0 +6,2 @@
+[ upstream commit cee21cc5be82faef74bb1b8f84407cc92de5dee7 ]
+
@@ -17 +18,0 @@
-Cc: stable at dpdk.org
@@ -29 +30 @@
-index 30dae2b87f..2d34137718 100644
+index 9eb44f9928..ea2ac7e7a1 100644
@@ -32 +33 @@
-@@ -34,6 +34,124 @@ test_bpf(void)
+@@ -33,6 +33,124 @@ test_bpf(void)
@@ -158 +159 @@
-index 556e613762..6983c026af 100644
+index 45ce9210da..ae5ca44c14 100644
@@ -161 +162 @@
-@@ -88,7 +88,7 @@ rte_bpf_load(const struct rte_bpf_prm *prm)
+@@ -86,7 +86,7 @@ rte_bpf_load(const struct rte_bpf_prm *prm)
@@ -171 +172 @@
-index c6f6bfab23..ba03293d17 100644
+index da8d5f3deb..76f08e4df3 100644
@@ -174 +175 @@
-@@ -1837,7 +1837,7 @@ add_edge(struct bpf_verifier *bvf, struct inst_node *node, uint32_t nidx)
+@@ -1827,7 +1827,7 @@ add_edge(struct bpf_verifier *bvf, struct inst_node *node, uint32_t nidx)
@@ -180,2 +181,2 @@
- RTE_BPF_LOG_LINE(ERR,
- "%s: program boundary violation at pc: %u, next pc: %u",
+ RTE_BPF_LOG(ERR,
+ "%s: program boundary violation at pc: %u, next pc: %u\n",
@@ -183 +184 @@
-@@ -1896,14 +1896,20 @@ get_prev_node(struct bpf_verifier *bvf, struct inst_node *node)
+@@ -1886,14 +1886,20 @@ get_prev_node(struct bpf_verifier *bvf, struct inst_node *node)
@@ -206 +207 @@
-@@ -1933,7 +1939,7 @@ dfs(struct bpf_verifier *bvf)
+@@ -1923,7 +1929,7 @@ dfs(struct bpf_verifier *bvf)
@@ -215 +216 @@
-@@ -2072,6 +2078,12 @@ validate(struct bpf_verifier *bvf)
+@@ -2062,6 +2068,12 @@ validate(struct bpf_verifier *bvf)
@@ -220 +221 @@
-+ RTE_BPF_LOG_LINE(ERR, "%s(%p) the program is empty",
++ RTE_BPF_LOG(ERR, "%s(%p) the program is empty\n",
@@ -227 +228 @@
- RTE_LOG(DEBUG, BPF, "%s(%p) stats:\n"
+ RTE_BPF_LOG(DEBUG, "%s(%p) stats:\n"
More information about the stable
mailing list