selftests/bpf: Test dynptr teardown on subprog return

Add failure tests for slices that escape a subprogram from a dynptr in the
callee stack. Cover immediate use of a ring buffer clone slice,
release-time invalidation of a caller dynptr slice after it crosses a
subprogram return, and immediate use of an skb dynptr slice in a qdisc
program.

Cover the error path where a subprogram would otherwise lose the last
dynptr capable of releasing a referenced resource.

Also add a success control which derives a slice from a caller-owned dynptr
and creates a separate clone in the callee. Destroying that callee-local
clone on return must not invalidate the caller dynptr's slice.

Signed-off-by: Xu Yunxiang <xyx2021@mail.ustc.edu.cn>
Reviewed-by: Amery Hung <ameryhung@gmail.com>
Link: https://lore.kernel.org/bpf/20260927120422.1042107-3-xyx2021@mail.ustc.edu.cn
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
This commit is contained in:
Xu Yunxiang 2026-09-27 20:04:22 +08:00 committed by Kumar Kartikeya Dwivedi
parent b65cc81792
commit b04ba9375a
No known key found for this signature in database
GPG Key ID: 472D377B63542F83
3 changed files with 173 additions and 0 deletions

View File

@ -11,6 +11,7 @@
#include "bpf_qdisc_fail__invalid_dynptr.skel.h"
#include "bpf_qdisc_fail__invalid_dynptr_slice.skel.h"
#include "bpf_qdisc_fail__invalid_dynptr_cross_frame.skel.h"
#include "bpf_qdisc_fail__invalid_dynptr_returned_slice.skel.h"
#include "bpf_qdisc_fail__untrusted_write.skel.h"
#include "bpf_qdisc_dynptr_use_after_invalidate_clone.skel.h"
@ -230,6 +231,7 @@ void test_ns_bpf_qdisc(void)
test_incompl_ops();
RUN_TESTS(bpf_qdisc_fail__invalid_dynptr);
RUN_TESTS(bpf_qdisc_fail__invalid_dynptr_cross_frame);
RUN_TESTS(bpf_qdisc_fail__invalid_dynptr_returned_slice);
RUN_TESTS(bpf_qdisc_fail__invalid_dynptr_slice);
RUN_TESTS(bpf_qdisc_fail__untrusted_write);
RUN_TESTS(bpf_qdisc_dynptr_use_after_invalidate_clone);

View File

@ -0,0 +1,76 @@
// SPDX-License-Identifier: GPL-2.0
#include <vmlinux.h>
#include "bpf_experimental.h"
#include "bpf_qdisc_common.h"
#include "bpf_misc.h"
char _license[] SEC("license") = "GPL";
int proto;
static __noinline struct ethhdr *slice_in_subprog(struct sk_buff *skb)
{
struct bpf_dynptr ptr;
bpf_dynptr_from_skb((struct __sk_buff *)skb, 0, &ptr);
return bpf_dynptr_slice(&ptr, 0, NULL, sizeof(struct ethhdr));
}
SEC("struct_ops")
__failure __msg("invalid mem access 'scalar'")
int BPF_PROG(invalid_dynptr_returned_slice, struct sk_buff *skb,
struct Qdisc *sch, struct bpf_sk_buff_ptr *to_free)
{
struct ethhdr *hdr;
hdr = slice_in_subprog(skb);
if (!hdr) {
bpf_qdisc_skb_drop(skb, to_free);
return NET_XMIT_DROP;
}
/* this should fail */
proto = hdr->h_proto;
bpf_qdisc_skb_drop(skb, to_free);
return NET_XMIT_DROP;
}
SEC("struct_ops")
__auxiliary
struct sk_buff *BPF_PROG(bpf_qdisc_test_dequeue, struct Qdisc *sch)
{
return NULL;
}
SEC("struct_ops")
__auxiliary
int BPF_PROG(bpf_qdisc_test_init, struct Qdisc *sch, struct nlattr *opt,
struct netlink_ext_ack *extack)
{
return 0;
}
SEC("struct_ops")
__auxiliary
void BPF_PROG(bpf_qdisc_test_reset, struct Qdisc *sch)
{
}
SEC("struct_ops")
__auxiliary
void BPF_PROG(bpf_qdisc_test_destroy, struct Qdisc *sch)
{
}
SEC(".struct_ops")
struct Qdisc_ops test = {
.enqueue = (void *)invalid_dynptr_returned_slice,
.dequeue = (void *)bpf_qdisc_test_dequeue,
.init = (void *)bpf_qdisc_test_init,
.reset = (void *)bpf_qdisc_test_reset,
.destroy = (void *)bpf_qdisc_test_destroy,
.id = "bpf_qdisc_test",
};

View File

@ -1895,6 +1895,101 @@ int clone_invalidate4(void *ctx)
return 0;
}
static __noinline void clone_slice_in_subprog(struct bpf_dynptr *ptr, int **data)
{
struct bpf_dynptr clone;
bpf_dynptr_clone(ptr, &clone);
*data = bpf_dynptr_data(&clone, 0, sizeof(val));
}
static __noinline void caller_slice_in_subprog(struct bpf_dynptr *ptr, int **data)
{
struct bpf_dynptr clone;
*data = bpf_dynptr_data(ptr, 0, sizeof(val));
bpf_dynptr_clone(ptr, &clone);
}
static __noinline void reserve_dynptr_in_subprog(void)
{
struct bpf_dynptr ptr;
bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr);
}
/* A subprogram cannot lose the last dynptr that can release a resource. */
SEC("?raw_tp")
__failure __msg("cannot overwrite referenced dynptr")
int referenced_dynptr_lost_on_subprog_return(void *ctx)
{
reserve_dynptr_in_subprog();
return 0;
}
/*
* Destroying a callee-local clone on return must not invalidate a slice whose
* source dynptr belongs to the caller.
*/
SEC("?raw_tp")
__success
int caller_dynptr_slice_across_subprog_valid(void *ctx)
{
struct bpf_dynptr ptr;
int *data = NULL;
bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr);
caller_slice_in_subprog(&ptr, &data);
if (data)
*data = 123;
bpf_ringbuf_submit_dynptr(&ptr, 0);
return 0;
}
/*
* A slice derived from a callee-local clone is invalid after the subprogram
* returns.
*/
SEC("?raw_tp")
__failure __msg("invalid mem access 'scalar'")
int callee_dynptr_slice_invalid_after_return(void *ctx)
{
struct bpf_dynptr ptr;
int *data = NULL;
bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr);
clone_slice_in_subprog(&ptr, &data);
if (data)
/* this should fail */
*data = 123;
bpf_ringbuf_submit_dynptr(&ptr, 0);
return 0;
}
/*
* A slice from a caller-owned dynptr survives the subprogram return, but
* releasing the shared reservation must invalidate it.
*/
SEC("?raw_tp")
__failure __msg("invalid mem access 'scalar'")
int caller_dynptr_slice_release_after_subprog_invalid(void *ctx)
{
struct bpf_dynptr ptr;
int *data = NULL;
bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr);
caller_slice_in_subprog(&ptr, &data);
bpf_ringbuf_submit_dynptr(&ptr, 0);
if (data)
/* this should fail */
*data = 123;
return 0;
}
/* Invalidating a dynptr should invalidate any data slices
* of its parent
*/