Skip to content

Commit 403fae5

Browse files
jsitnickiMartin KaFai Lau
authored andcommitted
selftests/bpf: Cover metadata access from a modified skb clone
Demonstrate that, when processing an skb clone, the metadata gets truncated if the program contains a direct write to either the payload or the metadata, due to an implicit unclone in the prologue, and otherwise the dynptr to the metadata is limited to being read-only. Signed-off-by: Jakub Sitnicki <[email protected]> Signed-off-by: Martin KaFai Lau <[email protected]> Link: https://patch.msgid.link/20250814-skb-metadata-thru-dynptr-v7-9-8a39e636e0fb@cloudflare.com
1 parent bd1b51b commit 403fae5

File tree

3 files changed

+305
-10
lines changed

3 files changed

+305
-10
lines changed

tools/testing/selftests/bpf/config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ CONFIG_MPLS_IPTUNNEL=y
6161
CONFIG_MPLS_ROUTING=y
6262
CONFIG_MPTCP=y
6363
CONFIG_NET_ACT_GACT=y
64+
CONFIG_NET_ACT_MIRRED=y
6465
CONFIG_NET_ACT_SKBMOD=y
6566
CONFIG_NET_CLS=y
6667
CONFIG_NET_CLS_ACT=y

tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c

Lines changed: 113 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#define TX_NETNS "xdp_context_tx"
1010
#define RX_NETNS "xdp_context_rx"
1111
#define TAP_NAME "tap0"
12+
#define DUMMY_NAME "dum0"
1213
#define TAP_NETNS "xdp_context_tuntap"
1314

1415
#define TEST_PAYLOAD_LEN 32
@@ -156,6 +157,22 @@ static int send_test_packet(int ifindex)
156157
return -1;
157158
}
158159

160+
static int write_test_packet(int tap_fd)
161+
{
162+
__u8 packet[sizeof(struct ethhdr) + TEST_PAYLOAD_LEN];
163+
int n;
164+
165+
/* The ethernet header doesn't need to be valid for this test */
166+
memset(packet, 0, sizeof(struct ethhdr));
167+
memcpy(packet + sizeof(struct ethhdr), test_payload, TEST_PAYLOAD_LEN);
168+
169+
n = write(tap_fd, packet, sizeof(packet));
170+
if (!ASSERT_EQ(n, sizeof(packet), "write packet"))
171+
return -1;
172+
173+
return 0;
174+
}
175+
159176
static void assert_test_result(const struct bpf_map *result_map)
160177
{
161178
int err;
@@ -276,7 +293,6 @@ static void test_tuntap(struct bpf_program *xdp_prog,
276293
LIBBPF_OPTS(bpf_tc_hook, tc_hook, .attach_point = BPF_TC_INGRESS);
277294
LIBBPF_OPTS(bpf_tc_opts, tc_opts, .handle = 1, .priority = 1);
278295
struct netns_obj *ns = NULL;
279-
__u8 packet[sizeof(struct ethhdr) + TEST_PAYLOAD_LEN];
280296
int tap_fd = -1;
281297
int tap_ifindex;
282298
int ret;
@@ -322,19 +338,82 @@ static void test_tuntap(struct bpf_program *xdp_prog,
322338
if (!ASSERT_GE(ret, 0, "bpf_xdp_attach"))
323339
goto close;
324340

325-
/* The ethernet header is not relevant for this test and doesn't need to
326-
* be meaningful.
327-
*/
328-
struct ethhdr eth = { 0 };
341+
ret = write_test_packet(tap_fd);
342+
if (!ASSERT_OK(ret, "write_test_packet"))
343+
goto close;
329344

330-
memcpy(packet, &eth, sizeof(eth));
331-
memcpy(packet + sizeof(eth), test_payload, TEST_PAYLOAD_LEN);
345+
assert_test_result(result_map);
346+
347+
close:
348+
if (tap_fd >= 0)
349+
close(tap_fd);
350+
netns_free(ns);
351+
}
352+
353+
/* Write a packet to a tap dev and copy it to ingress of a dummy dev */
354+
static void test_tuntap_mirred(struct bpf_program *xdp_prog,
355+
struct bpf_program *tc_prog,
356+
bool *test_pass)
357+
{
358+
LIBBPF_OPTS(bpf_tc_hook, tc_hook, .attach_point = BPF_TC_INGRESS);
359+
LIBBPF_OPTS(bpf_tc_opts, tc_opts, .handle = 1, .priority = 1);
360+
struct netns_obj *ns = NULL;
361+
int dummy_ifindex;
362+
int tap_fd = -1;
363+
int tap_ifindex;
364+
int ret;
332365

333-
ret = write(tap_fd, packet, sizeof(packet));
334-
if (!ASSERT_EQ(ret, sizeof(packet), "write packet"))
366+
*test_pass = false;
367+
368+
ns = netns_new(TAP_NETNS, true);
369+
if (!ASSERT_OK_PTR(ns, "netns_new"))
370+
return;
371+
372+
/* Setup dummy interface */
373+
SYS(close, "ip link add name " DUMMY_NAME " type dummy");
374+
SYS(close, "ip link set dev " DUMMY_NAME " up");
375+
376+
dummy_ifindex = if_nametoindex(DUMMY_NAME);
377+
if (!ASSERT_GE(dummy_ifindex, 0, "if_nametoindex"))
335378
goto close;
336379

337-
assert_test_result(result_map);
380+
tc_hook.ifindex = dummy_ifindex;
381+
ret = bpf_tc_hook_create(&tc_hook);
382+
if (!ASSERT_OK(ret, "bpf_tc_hook_create"))
383+
goto close;
384+
385+
tc_opts.prog_fd = bpf_program__fd(tc_prog);
386+
ret = bpf_tc_attach(&tc_hook, &tc_opts);
387+
if (!ASSERT_OK(ret, "bpf_tc_attach"))
388+
goto close;
389+
390+
/* Setup TAP interface */
391+
tap_fd = open_tuntap(TAP_NAME, true);
392+
if (!ASSERT_GE(tap_fd, 0, "open_tuntap"))
393+
goto close;
394+
395+
SYS(close, "ip link set dev " TAP_NAME " up");
396+
397+
tap_ifindex = if_nametoindex(TAP_NAME);
398+
if (!ASSERT_GE(tap_ifindex, 0, "if_nametoindex"))
399+
goto close;
400+
401+
ret = bpf_xdp_attach(tap_ifindex, bpf_program__fd(xdp_prog), 0, NULL);
402+
if (!ASSERT_GE(ret, 0, "bpf_xdp_attach"))
403+
goto close;
404+
405+
/* Copy all packets received from TAP to dummy ingress */
406+
SYS(close, "tc qdisc add dev " TAP_NAME " clsact");
407+
SYS(close, "tc filter add dev " TAP_NAME " ingress "
408+
"protocol all matchall "
409+
"action mirred ingress mirror dev " DUMMY_NAME);
410+
411+
/* Receive a packet on TAP */
412+
ret = write_test_packet(tap_fd);
413+
if (!ASSERT_OK(ret, "write_test_packet"))
414+
goto close;
415+
416+
ASSERT_TRUE(*test_pass, "test_pass");
338417

339418
close:
340419
if (tap_fd >= 0)
@@ -385,6 +464,30 @@ void test_xdp_context_tuntap(void)
385464
skel->progs.ing_cls_dynptr_offset_oob,
386465
skel->progs.ing_cls,
387466
skel->maps.test_result);
467+
if (test__start_subtest("clone_data_meta_empty_on_data_write"))
468+
test_tuntap_mirred(skel->progs.ing_xdp,
469+
skel->progs.clone_data_meta_empty_on_data_write,
470+
&skel->bss->test_pass);
471+
if (test__start_subtest("clone_data_meta_empty_on_meta_write"))
472+
test_tuntap_mirred(skel->progs.ing_xdp,
473+
skel->progs.clone_data_meta_empty_on_meta_write,
474+
&skel->bss->test_pass);
475+
if (test__start_subtest("clone_dynptr_empty_on_data_slice_write"))
476+
test_tuntap_mirred(skel->progs.ing_xdp,
477+
skel->progs.clone_dynptr_empty_on_data_slice_write,
478+
&skel->bss->test_pass);
479+
if (test__start_subtest("clone_dynptr_empty_on_meta_slice_write"))
480+
test_tuntap_mirred(skel->progs.ing_xdp,
481+
skel->progs.clone_dynptr_empty_on_meta_slice_write,
482+
&skel->bss->test_pass);
483+
if (test__start_subtest("clone_dynptr_rdonly_before_data_dynptr_write"))
484+
test_tuntap_mirred(skel->progs.ing_xdp,
485+
skel->progs.clone_dynptr_rdonly_before_data_dynptr_write,
486+
&skel->bss->test_pass);
487+
if (test__start_subtest("clone_dynptr_rdonly_before_meta_dynptr_write"))
488+
test_tuntap_mirred(skel->progs.ing_xdp,
489+
skel->progs.clone_dynptr_rdonly_before_meta_dynptr_write,
490+
&skel->bss->test_pass);
388491

389492
test_xdp_meta__destroy(skel);
390493
}

tools/testing/selftests/bpf/progs/test_xdp_meta.c

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ struct {
2626
__uint(value_size, META_SIZE);
2727
} test_result SEC(".maps");
2828

29+
bool test_pass;
30+
2931
SEC("tc")
3032
int ing_cls(struct __sk_buff *ctx)
3133
{
@@ -301,4 +303,193 @@ int ing_xdp(struct xdp_md *ctx)
301303
return XDP_PASS;
302304
}
303305

306+
/*
307+
* Check that skb->data_meta..skb->data is empty if prog writes to packet
308+
* _payload_ using packet pointers. Applies only to cloned skbs.
309+
*/
310+
SEC("tc")
311+
int clone_data_meta_empty_on_data_write(struct __sk_buff *ctx)
312+
{
313+
struct ethhdr *eth = ctx_ptr(ctx, data);
314+
315+
if (eth + 1 > ctx_ptr(ctx, data_end))
316+
goto out;
317+
/* Ignore non-test packets */
318+
if (eth->h_proto != 0)
319+
goto out;
320+
321+
/* Expect no metadata */
322+
if (ctx->data_meta != ctx->data)
323+
goto out;
324+
325+
/* Packet write to trigger unclone in prologue */
326+
eth->h_proto = 42;
327+
328+
test_pass = true;
329+
out:
330+
return TC_ACT_SHOT;
331+
}
332+
333+
/*
334+
* Check that skb->data_meta..skb->data is empty if prog writes to packet
335+
* _metadata_ using packet pointers. Applies only to cloned skbs.
336+
*/
337+
SEC("tc")
338+
int clone_data_meta_empty_on_meta_write(struct __sk_buff *ctx)
339+
{
340+
struct ethhdr *eth = ctx_ptr(ctx, data);
341+
__u8 *md = ctx_ptr(ctx, data_meta);
342+
343+
if (eth + 1 > ctx_ptr(ctx, data_end))
344+
goto out;
345+
/* Ignore non-test packets */
346+
if (eth->h_proto != 0)
347+
goto out;
348+
349+
if (md + 1 > ctx_ptr(ctx, data)) {
350+
/* Expect no metadata */
351+
test_pass = true;
352+
} else {
353+
/* Metadata write to trigger unclone in prologue */
354+
*md = 42;
355+
}
356+
out:
357+
return TC_ACT_SHOT;
358+
}
359+
360+
/*
361+
* Check that skb_meta dynptr is writable but empty if prog writes to packet
362+
* _payload_ using a dynptr slice. Applies only to cloned skbs.
363+
*/
364+
SEC("tc")
365+
int clone_dynptr_empty_on_data_slice_write(struct __sk_buff *ctx)
366+
{
367+
struct bpf_dynptr data, meta;
368+
struct ethhdr *eth;
369+
370+
bpf_dynptr_from_skb(ctx, 0, &data);
371+
eth = bpf_dynptr_slice_rdwr(&data, 0, NULL, sizeof(*eth));
372+
if (!eth)
373+
goto out;
374+
/* Ignore non-test packets */
375+
if (eth->h_proto != 0)
376+
goto out;
377+
378+
/* Expect no metadata */
379+
bpf_dynptr_from_skb_meta(ctx, 0, &meta);
380+
if (bpf_dynptr_is_rdonly(&meta) || bpf_dynptr_size(&meta) > 0)
381+
goto out;
382+
383+
/* Packet write to trigger unclone in prologue */
384+
eth->h_proto = 42;
385+
386+
test_pass = true;
387+
out:
388+
return TC_ACT_SHOT;
389+
}
390+
391+
/*
392+
* Check that skb_meta dynptr is writable but empty if prog writes to packet
393+
* _metadata_ using a dynptr slice. Applies only to cloned skbs.
394+
*/
395+
SEC("tc")
396+
int clone_dynptr_empty_on_meta_slice_write(struct __sk_buff *ctx)
397+
{
398+
struct bpf_dynptr data, meta;
399+
const struct ethhdr *eth;
400+
__u8 *md;
401+
402+
bpf_dynptr_from_skb(ctx, 0, &data);
403+
eth = bpf_dynptr_slice(&data, 0, NULL, sizeof(*eth));
404+
if (!eth)
405+
goto out;
406+
/* Ignore non-test packets */
407+
if (eth->h_proto != 0)
408+
goto out;
409+
410+
/* Expect no metadata */
411+
bpf_dynptr_from_skb_meta(ctx, 0, &meta);
412+
if (bpf_dynptr_is_rdonly(&meta) || bpf_dynptr_size(&meta) > 0)
413+
goto out;
414+
415+
/* Metadata write to trigger unclone in prologue */
416+
bpf_dynptr_from_skb_meta(ctx, 0, &meta);
417+
md = bpf_dynptr_slice_rdwr(&meta, 0, NULL, sizeof(*md));
418+
if (md)
419+
*md = 42;
420+
421+
test_pass = true;
422+
out:
423+
return TC_ACT_SHOT;
424+
}
425+
426+
/*
427+
* Check that skb_meta dynptr is read-only before prog writes to packet payload
428+
* using dynptr_write helper. Applies only to cloned skbs.
429+
*/
430+
SEC("tc")
431+
int clone_dynptr_rdonly_before_data_dynptr_write(struct __sk_buff *ctx)
432+
{
433+
struct bpf_dynptr data, meta;
434+
const struct ethhdr *eth;
435+
436+
bpf_dynptr_from_skb(ctx, 0, &data);
437+
eth = bpf_dynptr_slice(&data, 0, NULL, sizeof(*eth));
438+
if (!eth)
439+
goto out;
440+
/* Ignore non-test packets */
441+
if (eth->h_proto != 0)
442+
goto out;
443+
444+
/* Expect read-only metadata before unclone */
445+
bpf_dynptr_from_skb_meta(ctx, 0, &meta);
446+
if (!bpf_dynptr_is_rdonly(&meta) || bpf_dynptr_size(&meta) != META_SIZE)
447+
goto out;
448+
449+
/* Helper write to payload will unclone the packet */
450+
bpf_dynptr_write(&data, offsetof(struct ethhdr, h_proto), "x", 1, 0);
451+
452+
/* Expect no metadata after unclone */
453+
bpf_dynptr_from_skb_meta(ctx, 0, &meta);
454+
if (bpf_dynptr_is_rdonly(&meta) || bpf_dynptr_size(&meta) != 0)
455+
goto out;
456+
457+
test_pass = true;
458+
out:
459+
return TC_ACT_SHOT;
460+
}
461+
462+
/*
463+
* Check that skb_meta dynptr is read-only if prog writes to packet
464+
* metadata using dynptr_write helper. Applies only to cloned skbs.
465+
*/
466+
SEC("tc")
467+
int clone_dynptr_rdonly_before_meta_dynptr_write(struct __sk_buff *ctx)
468+
{
469+
struct bpf_dynptr data, meta;
470+
const struct ethhdr *eth;
471+
472+
bpf_dynptr_from_skb(ctx, 0, &data);
473+
eth = bpf_dynptr_slice(&data, 0, NULL, sizeof(*eth));
474+
if (!eth)
475+
goto out;
476+
/* Ignore non-test packets */
477+
if (eth->h_proto != 0)
478+
goto out;
479+
480+
/* Expect read-only metadata */
481+
bpf_dynptr_from_skb_meta(ctx, 0, &meta);
482+
if (!bpf_dynptr_is_rdonly(&meta) || bpf_dynptr_size(&meta) != META_SIZE)
483+
goto out;
484+
485+
/* Metadata write. Expect failure. */
486+
bpf_dynptr_from_skb_meta(ctx, 0, &meta);
487+
if (bpf_dynptr_write(&meta, 0, "x", 1, 0) != -EINVAL)
488+
goto out;
489+
490+
test_pass = true;
491+
out:
492+
return TC_ACT_SHOT;
493+
}
494+
304495
char _license[] SEC("license") = "GPL";

0 commit comments

Comments
 (0)