mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
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:
parent
b65cc81792
commit
b04ba9375a
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
};
|
||||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user