[PATCH net-next v2 04/14] selftests/bpf: Add tests for bpf_dynptr_from_skb_ext
From: Jakub Sitnicki <jakub@cloudflare.com>
Date: 2026-09-10 14:02:51
Also in:
bpf
Subsystem:
bpf [general] (safe dynamic programs and tools), bpf [selftests] (test runners & infrastructure), kernel selftest framework, the rest, xdp (express data path) · Maintainers:
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi, Shuah Khan, Shuah Khan, Linus Torvalds, David S. Miller, Jakub Kicinski, Jesper Dangaard Brouer, John Fastabend
Cover the bpf_dynptr_from_skb_ext() kfunc and the dynptr interface to skb_ext with TC-to-TC tests on a tuntap device: - write/read via bpf_dynptr_read and bpf_dynptr_write - write/read via bpf_dynptr_slice and bpf_dynptr_slice_rdwr - clone read via bpf_dynptr_clone - error paths: no allocation without F_CREATE, invalid flags, read-only enforcement without F_CREATE - double allocation: data from first alloc survives second skb_ext_add Plus, exercise the verifier check for tracing and LSM programs that rejects bpf_dynptr_from_skb_ext(BPF_SKB_EXT_F_CREATE) in these contexts. Signed-off-by: Jakub Sitnicki <jakub@cloudflare.com> --- tools/testing/selftests/bpf/config | 1 + tools/testing/selftests/bpf/prog_tests/verifier.c | 2 + .../bpf/prog_tests/xdp_context_test_run.c | 73 +++++++- tools/testing/selftests/bpf/progs/test_xdp_meta.c | 185 +++++++++++++++++++++ .../testing/selftests/bpf/progs/verifier_skb_ext.c | 77 +++++++++ 5 files changed, 333 insertions(+), 5 deletions(-)
diff --git a/tools/testing/selftests/bpf/config b/tools/testing/selftests/bpf/config
index ea7044f30adc..502f4504bdba 100644
--- a/tools/testing/selftests/bpf/config
+++ b/tools/testing/selftests/bpf/config@@ -7,6 +7,7 @@ CONFIG_BPF_JIT=y CONFIG_BPF_KPROBE_OVERRIDE=y CONFIG_BPF_LIRC_MODE2=y CONFIG_BPF_LSM=y +CONFIG_BPF_SKB_EXT=y CONFIG_BPF_STREAM_PARSER=y CONFIG_BPF_SYSCALL=y # CONFIG_BPF_UNPRIV_DEFAULT_OFF is not set
diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
index 64ac49ad67e6..7724e82fce66 100644
--- a/tools/testing/selftests/bpf/prog_tests/verifier.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c@@ -94,6 +94,7 @@ #include "verifier_scalar_ids.skel.h" #include "verifier_sdiv.skel.h" #include "verifier_search_pruning.skel.h" +#include "verifier_skb_ext.skel.h" #include "verifier_sock.skel.h" #include "verifier_sock_addr.skel.h" #include "verifier_sockmap_mutate.skel.h"
@@ -256,6 +257,7 @@ void test_verifier_runtime_jit(void) { RUN(verifier_runtime_jit); } void test_verifier_scalar_ids(void) { RUN(verifier_scalar_ids); } void test_verifier_sdiv(void) { RUN(verifier_sdiv); } void test_verifier_search_pruning(void) { RUN(verifier_search_pruning); } +void test_verifier_skb_ext(void) { RUN(verifier_skb_ext); } void test_verifier_sock(void) { RUN(verifier_sock); } void test_verifier_sock_addr(void) { RUN(verifier_sock_addr); } void test_verifier_sockmap_mutate(void) { RUN(verifier_sockmap_mutate); }
diff --git a/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c b/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
index 448807676176..252ac02de81b 100644
--- a/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c
+++ b/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c@@ -16,6 +16,7 @@ #define DUMMY_NAME "dum0" #define TAP_NETNS "xdp_context_tuntap" #define LWT_NETNS "xdp_context_lwt" +#define SKB_EXT_NETNS "skb_ext_tuntap" #define TEST_PAYLOAD_LEN 32 static const __u8 test_payload[TEST_PAYLOAD_LEN] = {
@@ -331,10 +332,11 @@ void test_xdp_context_veth(void) netns_free(tx_ns); } -static void test_tuntap(struct bpf_program *xdp_prog, - struct bpf_program *tc_prio_1_prog, - struct bpf_program *tc_prio_2_prog, - bool *test_pass) +static void __test_tuntap(const char *nsname, + struct bpf_program *xdp_prog, + struct bpf_program *tc_prio_1_prog, + struct bpf_program *tc_prio_2_prog, + bool *test_pass) { LIBBPF_OPTS(bpf_tc_hook, tc_hook, .attach_point = BPF_TC_INGRESS); LIBBPF_OPTS(bpf_tc_opts, tc_opts, .handle = 1, .priority = 1);
@@ -345,7 +347,7 @@ static void test_tuntap(struct bpf_program *xdp_prog, *test_pass = false; - ns = netns_new(TAP_NETNS, true); + ns = netns_new(nsname, true); if (!ASSERT_OK_PTR(ns, "create and open ns")) return;
@@ -396,6 +398,15 @@ static void test_tuntap(struct bpf_program *xdp_prog, netns_free(ns); } +static void test_tuntap(struct bpf_program *xdp_prog, + struct bpf_program *tc_prio_1_prog, + struct bpf_program *tc_prio_2_prog, + bool *test_pass) +{ + __test_tuntap(TAP_NETNS, xdp_prog, tc_prio_1_prog, tc_prio_2_prog, + test_pass); +} + /* Write a packet to a tap dev and copy it to ingress of a dummy dev */ static void test_tuntap_mirred(struct bpf_program *xdp_prog, struct bpf_program *tc_prog,
@@ -693,3 +704,55 @@ void test_xdp_context_lwt_encap(void) test_xdp_meta__destroy(skel); } + +static void test_skb_ext_tuntap(struct bpf_program *tc_prio_1_prog, + struct bpf_program *tc_prio_2_prog, + bool *test_pass) +{ + __test_tuntap(SKB_EXT_NETNS, NULL /* xdp */, tc_prio_1_prog, + tc_prio_2_prog, test_pass); +} + +void test_skb_ext_basic(void) +{ + struct test_xdp_meta *skel = NULL; + + skel = test_xdp_meta__open_and_load(); + if (!ASSERT_OK_PTR(skel, "open and load skeleton")) + return; + + if (test__start_subtest("tc_write_read")) + test_skb_ext_tuntap(skel->progs.tc_skb_ext_write, + skel->progs.tc_skb_ext_read, + &skel->bss->test_pass); + if (test__start_subtest("tc_write_clone_read")) + test_skb_ext_tuntap(skel->progs.tc_skb_ext_write, + skel->progs.tc_skb_ext_clone_read, + &skel->bss->test_pass); + if (test__start_subtest("tc_write_slice_read")) + test_skb_ext_tuntap(skel->progs.tc_skb_ext_write, + skel->progs.tc_skb_ext_slice_read, + &skel->bss->test_pass); + if (test__start_subtest("tc_slice_write_read")) + test_skb_ext_tuntap(skel->progs.tc_skb_ext_slice_write, + skel->progs.tc_skb_ext_read, + &skel->bss->test_pass); + if (test__start_subtest("tc_no_alloc")) + test_skb_ext_tuntap(skel->progs.tc_skb_ext_no_alloc, + NULL, /* tc prio 2 */ + &skel->bss->test_pass); + if (test__start_subtest("tc_invalid_flags")) + test_skb_ext_tuntap(skel->progs.tc_skb_ext_invalid_flags, + NULL, /* tc prio 2 */ + &skel->bss->test_pass); + if (test__start_subtest("tc_rdonly")) + test_skb_ext_tuntap(skel->progs.tc_skb_ext_rdonly, + NULL, /* tc prio 2 */ + &skel->bss->test_pass); + if (test__start_subtest("tc_double_alloc")) + test_skb_ext_tuntap(skel->progs.tc_skb_ext_double_alloc, + NULL, /* tc prio 2 */ + &skel->bss->test_pass); + + test_xdp_meta__destroy(skel); +}
diff --git a/tools/testing/selftests/bpf/progs/test_xdp_meta.c b/tools/testing/selftests/bpf/progs/test_xdp_meta.c
index 08b03be0b891..43840ee32d35 100644
--- a/tools/testing/selftests/bpf/progs/test_xdp_meta.c
+++ b/tools/testing/selftests/bpf/progs/test_xdp_meta.c@@ -6,6 +6,7 @@ #include <errno.h> #include "bpf_kfuncs.h" +#include "bpf_misc.h" #include "bpf_tracing_net.h" #define META_SIZE 32
@@ -689,4 +690,188 @@ int helper_skb_change_proto(struct __sk_buff *ctx) return TC_ACT_SHOT; } +/* Write to skb_ext using bpf_dynptr_write helper */ +SEC("tc") +int tc_skb_ext_write(struct __sk_buff *ctx) +{ + struct bpf_dynptr meta; + + if (!is_test_packet_tc(ctx)) + return TC_ACT_SHOT; + if (bpf_dynptr_from_skb_ext(ctx, 0, BPF_SKB_EXT_F_CREATE, &meta)) + return TC_ACT_SHOT; + if (bpf_dynptr_write(&meta, 0, (void *)meta_want, ARRAY_SIZE(meta_want), 0)) + return TC_ACT_SHOT; + + return TC_ACT_UNSPEC; +} + +/* Read from skb-ext metadata using bpf_dynptr_read helper */ +SEC("tc") +int tc_skb_ext_read(struct __sk_buff *ctx) +{ + __u8 meta_have[META_SIZE]; + struct bpf_dynptr meta; + + if (bpf_dynptr_from_skb_ext(ctx, 0, 0, &meta)) + return TC_ACT_SHOT; + if (bpf_dynptr_read(meta_have, ARRAY_SIZE(meta_have), &meta, 0, 0)) + return TC_ACT_SHOT; + if (!check_metadata(meta_have)) + return TC_ACT_SHOT; + + test_pass = true; + return TC_ACT_UNSPEC; +} + +/* Read from a cloned skb_ext dynptr */ +SEC("tc") +int tc_skb_ext_clone_read(struct __sk_buff *ctx) +{ + struct bpf_dynptr meta, clone; + __u8 meta_have[META_SIZE]; + + if (bpf_dynptr_from_skb_ext(ctx, 0, 0, &meta)) + return TC_ACT_SHOT; + if (bpf_dynptr_clone(&meta, &clone)) + return TC_ACT_SHOT; + if (bpf_dynptr_read(meta_have, ARRAY_SIZE(meta_have), &clone, 0, 0)) + return TC_ACT_SHOT; + if (!check_metadata(meta_have)) + return TC_ACT_SHOT; + + test_pass = true; + return TC_ACT_UNSPEC; +} + +/* Read from skb_ext using bpf_dynptr_slice */ +SEC("tc") +int tc_skb_ext_slice_read(struct __sk_buff *ctx) +{ + struct bpf_dynptr meta; + __u8 *meta_have; + + if (bpf_dynptr_from_skb_ext(ctx, 0, 0, &meta)) + return TC_ACT_SHOT; + meta_have = bpf_dynptr_slice(&meta, 0, NULL, META_SIZE); + if (!meta_have) + return TC_ACT_SHOT; + if (!check_metadata(meta_have)) + return TC_ACT_SHOT; + + test_pass = true; + return TC_ACT_UNSPEC; +} + +/* Write to skb_ext using bpf_dynptr_slice_rdwr */ +SEC("tc") +int tc_skb_ext_slice_write(struct __sk_buff *ctx) +{ + struct bpf_dynptr meta; + __u8 *dst; + + if (!is_test_packet_tc(ctx)) + return TC_ACT_SHOT; + if (bpf_dynptr_from_skb_ext(ctx, 0, BPF_SKB_EXT_F_CREATE, &meta)) + return TC_ACT_SHOT; + dst = bpf_dynptr_slice_rdwr(&meta, 0, NULL, META_SIZE); + if (!dst) + return TC_ACT_SHOT; + __builtin_memcpy(dst, meta_want, META_SIZE); + + return TC_ACT_UNSPEC; +} + +/* Opening skb_ext without F_CREATE on a fresh skb should fail */ +SEC("tc") +int tc_skb_ext_no_alloc(struct __sk_buff *ctx) +{ + struct bpf_dynptr meta; + + if (!is_test_packet_tc(ctx)) + return TC_ACT_SHOT; + if (bpf_dynptr_from_skb_ext(ctx, 0, 0, &meta) != -ENOENT) + return TC_ACT_SHOT; + + test_pass = true; + return TC_ACT_UNSPEC; +} + +/* Invalid flags are rejected */ +SEC("tc") +int tc_skb_ext_invalid_flags(struct __sk_buff *ctx) +{ + struct bpf_dynptr meta; + + if (!is_test_packet_tc(ctx)) + return TC_ACT_SHOT; + if (bpf_dynptr_from_skb_ext(ctx, 0, ~0ULL, &meta) != -EINVAL) + return TC_ACT_SHOT; + + test_pass = true; + return TC_ACT_UNSPEC; +} + +/* Without F_CREATE the dynptr is read-only */ +SEC("tc") +int tc_skb_ext_rdonly(struct __sk_buff *ctx) +{ + __u8 meta_have[META_SIZE]; + struct bpf_dynptr meta; + + if (!is_test_packet_tc(ctx)) + return TC_ACT_SHOT; + + /* Create and populate the ext */ + if (bpf_dynptr_from_skb_ext(ctx, 0, BPF_SKB_EXT_F_CREATE, &meta)) + return TC_ACT_SHOT; + if (bpf_dynptr_write(&meta, 0, (void *)meta_want, META_SIZE, 0)) + return TC_ACT_SHOT; + + /* Reopen without F_CREATE -- should be read-only */ + if (bpf_dynptr_from_skb_ext(ctx, 0, 0, &meta)) + return TC_ACT_SHOT; + + /* Verify read-only: writes must fail, reads must work */ + if (!bpf_dynptr_is_rdonly(&meta)) + return TC_ACT_SHOT; + if (!bpf_dynptr_write(&meta, 0, (void *)meta_want, META_SIZE, 0)) + return TC_ACT_SHOT; + if (bpf_dynptr_read(meta_have, META_SIZE, &meta, 0, 0)) + return TC_ACT_SHOT; + if (!check_metadata(meta_have)) + return TC_ACT_SHOT; + + test_pass = true; + return TC_ACT_UNSPEC; +} + +/* Double alloc: data from first alloc survives second skb_ext_add */ +SEC("tc") +int tc_skb_ext_double_alloc(struct __sk_buff *ctx) +{ + __u8 meta_have[META_SIZE]; + struct bpf_dynptr meta; + + if (!is_test_packet_tc(ctx)) + return TC_ACT_SHOT; + + /* First alloc + write */ + if (bpf_dynptr_from_skb_ext(ctx, 0, BPF_SKB_EXT_F_CREATE, &meta)) + return TC_ACT_SHOT; + if (bpf_dynptr_write(&meta, 0, (void *)meta_want, META_SIZE, 0)) + return TC_ACT_SHOT; + + /* Second alloc -- skb_ext_add returns existing ext */ + if (bpf_dynptr_from_skb_ext(ctx, 0, BPF_SKB_EXT_F_CREATE, &meta)) + return TC_ACT_SHOT; + if (bpf_dynptr_read(meta_have, META_SIZE, &meta, 0, 0)) + return TC_ACT_SHOT; + if (!check_metadata(meta_have)) + return TC_ACT_SHOT; + + test_pass = true; + return TC_ACT_UNSPEC; +} + char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/verifier_skb_ext.c b/tools/testing/selftests/bpf/progs/verifier_skb_ext.c
new file mode 100644
index 000000000000..db43f1051400
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_skb_ext.c@@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include "vmlinux.h" +#include <bpf/bpf_helpers.h> +#include <bpf/bpf_tracing.h> +#include "bpf_misc.h" + +__u64 flags; + +SEC("tp_btf/kfree_skb") +__description("F_CREATE is rejected in tracing programs") +__failure __msg("is not allowed in lsm/tracing programs") +int BPF_PROG(tp_skb_ext_create, struct sk_buff *skb) +{ + struct bpf_dynptr meta; + + if (bpf_dynptr_from_skb_ext((struct __sk_buff *)skb, 0, + BPF_SKB_EXT_F_CREATE, &meta)) + return 0; + + return 0; +} + +SEC("tp_btf/kfree_skb") +__description("non-constant flags are rejected in tracing programs") +__failure __msg("must be a known constant") +int BPF_PROG(tp_skb_ext_var_flags, struct sk_buff *skb) +{ + struct bpf_dynptr meta; + + if (bpf_dynptr_from_skb_ext((struct __sk_buff *)skb, 0, flags, &meta)) + return 0; + + return 0; +} + +SEC("tc") +__description("non-constant flags are rejected") +__failure __msg("must be a known constant") +int skb_ext_var_flags(struct __sk_buff *ctx) +{ + struct bpf_dynptr meta; + + if (bpf_dynptr_from_skb_ext(ctx, 0, flags, &meta)) + return 0; + + return 0; +} + +SEC("lsm/inet_conn_established") +__description("F_CREATE is rejected in LSM programs") +__failure __msg("is not allowed in lsm/tracing programs") +int BPF_PROG(lsm_skb_ext_create, struct sock *sk, struct sk_buff *skb) +{ + struct bpf_dynptr meta; + + if (bpf_dynptr_from_skb_ext((struct __sk_buff *)skb, 0, + BPF_SKB_EXT_F_CREATE, &meta)) + return 0; + + return 0; +} + +SEC("lsm/inet_conn_established") +__description("non-constant flags are rejected in LSM programs") +__failure __msg("must be a known constant") +int BPF_PROG(lsm_skb_ext_var_flags, struct sock *sk, struct sk_buff *skb) +{ + struct bpf_dynptr meta; + + if (bpf_dynptr_from_skb_ext((struct __sk_buff *)skb, 0, flags, &meta)) + return 0; + + return 0; +} + +char _license[] SEC("license") = "GPL";
--
2.43.0