Merge branch 'bpf-fix-the-stack-buffer-that-bpf_dynptr_slice_rdwr-returns'

Alexei Starovoitov says:

====================
bpf: Fix the stack buffer that bpf_dynptr_slice_rdwr() returns

From: Alexei Starovoitov <ast@kernel.org>

bpf_dynptr_slice_rdwr() and bpf_dynptr_slice() can return the buffer. When
the buffer is on the stack, the prog has two pointers to the same stack
slot, and the verifier knows only one of them. A store through the slice
changes data that the verifier tracks in the slot: a known scalar, a
spilled pointer, a dynptr, an iterator. Such a prog loads today and can
crash the kernel. A load through the slice reads such data as a scalar.

Patch 1 invalidates the slice when the prog stores to the buffer, or when
the frame of the buffer returns. Patch 2 has the tests.

The selftests that pass a stack buffer to the kfuncs only read the buffer
after the call. They load as before, with the same number of processed
insns. Cilium and sched-ext/scx do not use the kfuncs. test_progs and
test_verifier have no new failure.

v1 -> v2:
 - Read-only slices of bpf_dynptr_slice() follow the same rule (Sashiko).
 - Tests for read-only slices, one of them with a file dynptr. The progs
   end with a load through the slice.
 - Shorter tests: a macro for the callees, less in the run-time test.

v1: https://lore.kernel.org/bpf/20261102055543.3584718-1-alexei.starovoitov@gmail.com/

Signed-off-by: Alexei Starovoitov <ast@kernel.org>
====================

Link: https://patch.msgid.link/20261102170958.3586893-1-alexei.starovoitov@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Alexei Starovoitov 2026-11-02 20:26:02 +00:00
commit 29f85bba33
No known key found for this signature in database
10 changed files with 401 additions and 5 deletions

View File

@ -50,6 +50,13 @@ enum bpf_iter_state {
BPF_ITER_STATE_DRAINED,
};
/* Bytes [off, off + size) of the stack of frame frameno. Nothing when size is 0. */
struct bpf_stack_range {
u32 frameno;
s32 off;
u32 size;
};
struct bpf_reg_state {
/* Ordering of fields matters. See states_equal() */
enum bpf_reg_type type;
@ -83,6 +90,12 @@ struct bpf_reg_state {
struct { /* for PTR_TO_MEM | PTR_TO_MEM_OR_NULL */
u32 mem_size;
/*
* bpf_dynptr_slice() and bpf_dynptr_slice_rdwr() return the
* buffer when the data is not contiguous: the register may
* point to this part of the stack.
*/
struct bpf_stack_range buf;
};
/* For dynptr stack slots */
@ -1091,6 +1104,8 @@ struct bpf_verifier_env {
bool bypass_spec_v1;
bool bypass_spec_v4;
bool seen_direct_write;
/* a register may point to the stack and is not PTR_TO_STACK, see bpf_reg_state::buf */
bool seen_stack_slice;
/* a global function is verified for BPF_FUNC_CTX_NO_RCU_LOCK */
bool no_rcu_read_lock;
bool seen_exception;
@ -1815,6 +1830,8 @@ struct bpf_call_arg_meta {
struct arg_constant_desc arg_constant;
/* The type of the dynptr that the kfunc writes through */
enum bpf_dynptr_type dynptr_write_type;
/* The buffer of bpf_dynptr_slice() or bpf_dynptr_slice_rdwr() */
struct bpf_stack_range slice_buf;
/* arg_{btf,btf_id,owning_ref} are used by kfunc-specific handling,
* generally to pass info about user-defined local kptr types to later

View File

@ -1561,7 +1561,8 @@ static void diag_record_mod(struct bpf_verifier_env *env, u32 insn_idx,
if (origin) {
event.mod.origin = *origin;
event.mod.origin_valid = true;
} else if (diag_mod_insn_origin(env, insn_idx, &target, &event.mod.origin)) {
} else if (reason != BPF_DIAG_MOD_SLICE_BUF && /* the insn writes the buffer */
diag_mod_insn_origin(env, insn_idx, &target, &event.mod.origin)) {
event.mod.origin_valid = true;
}
if (old_reg && new_reg && reason == BPF_DIAG_MOD_WRITE &&
@ -2237,6 +2238,9 @@ static void diag_print_mod(struct bpf_verifier_env *env, const struct bpf_diag_h
case BPF_DIAG_MOD_PKT_DATA_CHANGE:
reason = "packet data may have moved";
break;
case BPF_DIAG_MOD_SLICE_BUF:
reason = "the stack buffer of this dynptr slice was written, or its frame returned";
break;
case BPF_DIAG_MOD_NON_OWN_REF:
reason = "leaving the protected region invalidated this borrowed pointer";
break;

View File

@ -23,6 +23,7 @@ enum bpf_diag_mod_reason {
BPF_DIAG_MOD_VAR_WRITE,
BPF_DIAG_MOD_REF_RELEASE,
BPF_DIAG_MOD_PKT_DATA_CHANGE,
BPF_DIAG_MOD_SLICE_BUF,
BPF_DIAG_MOD_NON_OWN_REF,
BPF_DIAG_MOD_CALLER_SAVED,
};

View File

@ -3088,6 +3088,10 @@ __bpf_kfunc struct task_struct *bpf_task_from_vpid(s32 vpid)
* bpf_dynptr_slice will not invalidate any ctx->data/data_end pointers in
* the bpf program.
*
* Please also note that when the buffer is on the stack, the verifier invalidates
* the returned pointer when the program stores to the buffer (helpers and kfuncs
* may fill it), or returns from the function that has it.
*
* Return: NULL if the call failed (eg invalid dynptr), pointer to a read-only
* data slice (can be either direct pointer to the data or a pointer to the user
* provided buffer, with its contents containing the data, if unable to obtain
@ -3173,6 +3177,12 @@ __bpf_kfunc void *bpf_dynptr_slice(const struct bpf_dynptr *p, u64 offset,
* Please note that, as in the example above, the user must check that the
* returned pointer is not null before using it.
*
* Please also note that when the buffer is on the stack, the verifier invalidates
* the returned pointer when the program stores to the buffer (helpers and kfuncs
* may fill it), or returns from the function that has it. Change the data through
* the returned pointer, and before the comparison with the buffer, as in the
* example above: after it the compiler may use one pointer in place of the other.
*
* Please also note that in the case of skb and xdp dynptrs, bpf_dynptr_slice_rdwr
* does not change the underlying packet data pointers, so a call to
* bpf_dynptr_slice_rdwr will not invalidate any ctx->data/data_end pointers in

View File

@ -684,6 +684,8 @@ static void print_reg_state(struct bpf_verifier_env *env,
if (base_type(t) == PTR_TO_MEM) {
verbose_a("sz=");
verbose_unum(env, reg->mem_size);
if (reg->buf.size)
verbose_a("buf=fp[%u]%d+%u", reg->buf.frameno, reg->buf.off, reg->buf.size);
}
if (t == CONST_PTR_TO_DYNPTR)
verbose_a("type=%s", dynptr_type_str(reg->dynptr.type));

View File

@ -1831,7 +1831,11 @@ scev_reg_for_read(struct bpf_verifier_env *env, const struct bpf_func_state *st,
return zero ? &env->scev->zero_reg : &env->scev->unknown_reg;
}
/* Materialize a writable value in the current state, growing its stack if needed. */
/*
* Materialize a writable value in the current state, growing its stack if needed.
* This does not go through check_stack_write(). The callers skip is_unbound_scev_reg() slots,
* thus the buffer of a live dynptr slice, see bpf_reg_state::buf, does not become a spill here.
*/
static int scev_reg_for_write(struct bpf_verifier_env *env, struct bpf_func_state *st,
u32 r, struct bpf_reg_state **out)
{

View File

@ -1010,6 +1010,13 @@ static bool dynptr_write_changes_pkt_data(enum bpf_dynptr_type type)
return type == BPF_DYNPTR_TYPE_SKB || type == BPF_DYNPTR_TYPE_SKB_META;
}
/* The data of the other types is contiguous: bpf_dynptr_slice() does not use the buffer. */
static bool dynptr_slice_may_be_buffer(enum bpf_dynptr_type type)
{
return type != BPF_DYNPTR_TYPE_LOCAL && type != BPF_DYNPTR_TYPE_RINGBUF &&
type != BPF_DYNPTR_TYPE_SKB_META;
}
static bool is_dynptr_type_expected(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
enum bpf_arg_type arg_type)
{
@ -4558,6 +4565,31 @@ static int check_stack_read(struct bpf_verifier_env *env,
return err;
}
/*
* An access through a slice that may be the buffer on the stack, see bpf_reg_state::buf, is
* not seen as an access to the stack. That's fine as long as nothing is tracked in the buffer.
* The output of helpers and kfuncs is not tracked. Here an insn stores to [min_off, max_off)
* of frame @frameno, a dynptr, an iterator or irq flags are made there, or the frame is gone.
*/
static void invalidate_stack_slices(struct bpf_verifier_env *env, u32 frameno,
int min_off, int max_off)
{
struct bpf_func_state *state;
struct bpf_reg_state *reg;
if (!env->seen_stack_slice)
return;
bpf_for_each_reg_in_vstate(env->cur_state, state, reg, ({
if (base_type(reg->type) != PTR_TO_MEM || !reg->buf.size ||
reg->buf.frameno != frameno || reg->buf.off >= max_off ||
reg->buf.off + (int)reg->buf.size <= min_off)
continue;
bpf_diag_record_scrub(env, reg, BPF_DIAG_MOD_SLICE_BUF);
mark_reg_invalid(env, reg);
}));
}
/* check_stack_write dispatches to check_stack_write_fixed_off or
* check_stack_write_var_off.
*
@ -4572,6 +4604,8 @@ static int check_stack_write(struct bpf_verifier_env *env,
int value_regno, int insn_idx)
{
struct bpf_func_state *state = bpf_func(env, reg);
int min_off = reg_smin(reg) + off;
int max_off = reg_smax(reg) + off + size;
int err;
if (tnum_is_const(reg->var_off)) {
@ -4586,6 +4620,8 @@ static int check_stack_write(struct bpf_verifier_env *env,
reg, off, size,
value_regno, insn_idx);
}
/* After the write is modelled: the insn may store a slice to its own buffer. */
invalidate_stack_slices(env, state->frameno, min_off, max_off);
return err;
}
@ -10466,15 +10502,18 @@ static int mark_raw_stack(struct bpf_verifier_env *env, struct bpf_call_arg_meta
*/
for (slot = 0; slot < MAX_BPF_FUNC_ARGS; slot++) {
struct bpf_reg_state *reg;
argno_t argno = argno_from_arg(slot + 1);
if (!meta->arg_raw_mem.size[slot])
continue;
reg = get_func_arg_reg(caller, cur_regs(env), slot);
/*
* check_stack_range_initialized() checked the range, its offset is a constant.
* Not check_stack_write(): unknown bytes in its buffer leave a dynptr slice valid.
*/
for (i = 0; i < meta->arg_raw_mem.size[slot]; i++) {
err = check_mem_access(env, insn_idx, reg, argno, i, BPF_B,
BPF_WRITE, -1, false, false);
err = check_stack_write_fixed_off(env, bpf_func(env, reg),
reg->var_off.value + i, 1, -1, insn_idx);
if (err)
return err;
}
@ -12176,6 +12215,7 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)
callee->allocated_stack / BPF_REG_SIZE - 1);
if (err)
return err;
invalidate_stack_slices(env, callee->frameno, INT_MIN, 0);
/* for callbacks like bpf_loop or bpf_for_each_map_elem go back to callsite,
* there function call logic would reschedule callback visit. If iteration
@ -15411,6 +15451,9 @@ static int check_special_kfunc(struct bpf_verifier_env *env, struct bpf_call_arg
return -EFAULT;
}
regs[BPF_REG_0].parent_id = meta->dynptr.id;
regs[BPF_REG_0].buf = meta->slice_buf;
if (meta->slice_buf.size)
env->seen_stack_slice = true;
} else {
return 0;
}
@ -15668,6 +15711,22 @@ static int check_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
ref_convert_owning_non_owning(env, id);
}
if ((is_kfunc_call(&meta, special_kfunc_list[KF_bpf_dynptr_slice]) ||
is_kfunc_call(&meta, special_kfunc_list[KF_bpf_dynptr_slice_rdwr])) &&
dynptr_slice_may_be_buffer(meta.dynptr.type)) {
struct bpf_reg_state *buf = &regs[BPF_REG_3];
if (buf->type == PTR_TO_STACK) {
meta.slice_buf.frameno = buf->frameno;
meta.slice_buf.off = reg_smin(buf);
meta.slice_buf.size = reg_smax(buf) - reg_smin(buf) +
meta.arg_constant.value;
} else if (base_type(buf->type) == PTR_TO_MEM) {
/* a slice is the buffer of the next one */
meta.slice_buf = buf->buf;
}
}
if (meta.func_id == special_kfunc_list[KF_bpf_throw]) {
if (!bpf_jit_supports_exceptions()) {
verbose(env, "JIT does not support calling kfunc %s#%d\n",

View File

@ -37,6 +37,7 @@ static struct {
{"test_skb_readonly", SETUP_SKB_PROG},
{"test_dynptr_skb_data", SETUP_SKB_PROG},
{"test_dynptr_skb_slice_non_linear", SETUP_SKB_PROG_NONLINEAR},
{"test_dynptr_skb_slice_rdwr_buffer", SETUP_SKB_PROG_NONLINEAR},
{"test_dynptr_skb_meta_data", SETUP_SKB_PROG},
{"test_dynptr_skb_meta_flags", SETUP_SKB_PROG},
{"test_adjust", SETUP_SYSCALL_SLEEP},

View File

@ -1277,6 +1277,270 @@ int skb_invalid_data_slice4(struct __sk_buff *skb)
return SK_PASS;
}
/*
* bpf_dynptr_slice() and bpf_dynptr_slice_rdwr() return the buffer when the data
* is not contiguous. For the verifier an access through the slice is not an access
* to the stack. Thus the buffer must not hold data that the verifier tracks: a
* store to the buffer invalidates the slice. fp-32: the dynptr, r7: the slice.
*/
#define SLICE_BUF_DYNPTR(new) \
"r2 = 0;" \
"r3 = r10;" \
"r3 += -32;" \
"call %[" #new "];"
/* r3 is the buffer */
#define SLICE_BUF_CALL(size) \
"r1 = r10;" \
"r1 += -32;" \
"r2 = 0;" \
"r4 = " #size ";" \
"call %[slice];"
#define SLICE_BUF_CHECK \
"if r0 == 0 goto 9f;" \
"r7 = r0;"
#define SLICE_BUF_AT(off, size) \
"r3 = r10;" \
"r3 += -" #off ";" \
SLICE_BUF_CALL(size) \
SLICE_BUF_CHECK
#define SLICE_BUF(off, size) SLICE_BUF_DYNPTR(bpf_dynptr_from_skb) SLICE_BUF_AT(off, size)
struct file {};
extern int bpf_dynptr_from_file(struct file *, __u32, struct bpf_dynptr *) __weak __ksym;
/* clang makes no BTF for a kfunc that only asm calls */
void __kfunc_btf_root(void)
{
bpf_dynptr_from_file(0, 0, 0);
}
/* Each prog ends with a load through the slice */
#define SLICE_BUF_PROG(sec, kfunc, name, expect, ...) \
SEC(sec) \
expect \
__naked int name(void) \
{ \
asm volatile (__VA_ARGS__ \
"r0 = *(u8 *)(r7 + 0);" \
"9:" \
"r0 = 0;" \
"exit;" \
: \
: __imm(bpf_dynptr_from_file), \
__imm(bpf_dynptr_from_skb), \
__imm(bpf_dynptr_from_skb_meta), \
__imm(bpf_dynptr_from_xdp), \
__imm(bpf_get_prandom_u32), \
__imm(bpf_probe_read_kernel), \
__imm_const(slice, kfunc) \
: __clobber_all); \
}
#define SLICE_BUF_TEST(...) SLICE_BUF_PROG("?tc", bpf_dynptr_slice_rdwr, __VA_ARGS__)
/* Not in the line of SLICE_BUF_TEST(): the log of the verifier has a copy of that line */
#define SLICE_BUF_INVALID __failure __msg("R7 invalid mem access 'scalar'")
#define SLICE_BUF_POINTER __failure __msg("invalid size of register spill")
SLICE_BUF_TEST(slice_buf_first_byte, SLICE_BUF_INVALID,
SLICE_BUF(12, 4)
"*(u8 *)(r10 - 12) = 0;")
SLICE_BUF_TEST(slice_buf_last_byte, SLICE_BUF_INVALID,
SLICE_BUF(12, 4)
"*(u8 *)(r10 - 9) = 0;")
/* the store is at fp-16 or at fp-8 */
SLICE_BUF_TEST(slice_buf_store_var_off, SLICE_BUF_INVALID,
SLICE_BUF(8, 8)
"call %[bpf_get_prandom_u32];"
"r0 &= 8;"
"r1 = r10;"
"r1 += -16;"
"r1 += r0;"
"*(u64 *)(r1 + 0) = 0;")
/* the buffer is at fp-16 or at fp-8 */
SLICE_BUF_TEST(slice_buf_var_off, SLICE_BUF_INVALID,
SLICE_BUF_DYNPTR(bpf_dynptr_from_skb)
"call %[bpf_get_prandom_u32];"
"r0 &= 8;"
"r3 = r10;"
"r3 += -16;"
"r3 += r0;"
SLICE_BUF_CALL(8)
SLICE_BUF_CHECK
"*(u64 *)(r10 - 8) = 0;")
/* the slice is a pointer when it is stored to its buffer, and invalid after that */
SLICE_BUF_TEST(slice_buf_narrow_spill, SLICE_BUF_POINTER,
SLICE_BUF(8, 8)
"*(u32 *)(r10 - 8) = r7;")
/* the write to the buffer is the spill of the slice, not yet checked for NULL */
SLICE_BUF_TEST(slice_buf_spilled_or_null, SLICE_BUF_INVALID,
SLICE_BUF_DYNPTR(bpf_dynptr_from_skb)
"r3 = r10;"
"r3 += -8;"
SLICE_BUF_CALL(8)
"*(u64 *)(r10 - 8) = r0;"
"r0 = *(u64 *)(r10 - 8);"
SLICE_BUF_CHECK)
/* a slice that may be the buffer is the buffer of the next slice */
SLICE_BUF_TEST(slice_buf_is_a_slice, SLICE_BUF_INVALID,
SLICE_BUF(8, 8)
"r3 = r7;"
SLICE_BUF_CALL(8)
SLICE_BUF_CHECK
"*(u64 *)(r10 - 8) = 0;")
/* The paths differ only in the buffer. The safe one is first, the other must not be pruned. */
SLICE_BUF_TEST(slice_buf_pruning, SLICE_BUF_INVALID __flag(BPF_F_TEST_STATE_FREQ),
SLICE_BUF_DYNPTR(bpf_dynptr_from_skb)
"call %[bpf_get_prandom_u32];"
"if r0 == 0 goto 1f;"
SLICE_BUF_AT(16, 8)
"goto 2f;"
"1:"
SLICE_BUF_AT(8, 8)
"2:"
"*(u64 *)(r10 - 8) = 0;")
#define SLICE_BUF_FUNC(name, ...) \
static __used __naked void name(void) \
{ \
asm volatile (__VA_ARGS__ \
"exit;" \
: \
: __imm(bpf_dynptr_slice) \
: __clobber_all); \
}
/* r1 is the dynptr of the caller. The buffer of the read-only slice is gone after the return. */
SLICE_BUF_FUNC(slice_buf_in_callee,
"r2 = 0;"
"r3 = r10;"
"r3 += -8;"
"r4 = 8;"
"call %[bpf_dynptr_slice];")
SLICE_BUF_TEST(slice_buf_frame_returned, SLICE_BUF_INVALID,
SLICE_BUF_DYNPTR(bpf_dynptr_from_skb)
"r1 = r10;"
"r1 += -32;"
"call slice_buf_in_callee;"
SLICE_BUF_CHECK)
/* stores to fp-12 of this frame and to r1, a pointer to the stack of the caller */
SLICE_BUF_FUNC(slice_buf_stores,
"*(u32 *)(r10 - 12) = 0;"
"*(u32 *)(r1 + 0) = 0;"
"r0 = 0;")
SLICE_BUF_TEST(slice_buf_written_by_callee, SLICE_BUF_INVALID,
SLICE_BUF(12, 4)
"r1 = r10;"
"r1 += -12;"
"call slice_buf_stores;")
/* r1 is the dynptr and r2 the buffer, both in the frame of the caller */
SLICE_BUF_FUNC(slice_buf_of_caller,
"r3 = r2;"
"r2 = 0;"
"r4 = 4;"
"call %[bpf_dynptr_slice];")
/* stores next to the buffer or to another frame, a return, calls that fill the buffer */
SLICE_BUF_TEST(slice_buf_stays_valid, __success,
SLICE_BUF(12, 4)
"r8 = r7;"
"*(u8 *)(r10 - 13) = 0;"
"*(u8 *)(r10 - 8) = 0;"
"r1 = r10;"
"r1 += -16;"
"call slice_buf_stores;"
"r1 = r10;"
"r1 += -12;"
"r2 = 4;"
"r3 = 0;"
"call %[bpf_probe_read_kernel];"
"r1 = r10;"
"r1 += -32;"
"r2 = r10;"
"r2 += -12;"
"call slice_buf_of_caller;"
SLICE_BUF_CHECK
"*(u8 *)(r8 + 0) = 0;")
/* the slice of a file dynptr is read-only, and it is always the buffer */
SLICE_BUF_PROG("?lsm.s/file_open", bpf_dynptr_slice, slice_buf_file, SLICE_BUF_INVALID,
"r1 = *(u64 *)(r1 + 0);"
SLICE_BUF_DYNPTR(bpf_dynptr_from_file)
SLICE_BUF_AT(8, 8)
"*(u64 *)(r10 - 8) = 0;")
SLICE_BUF_PROG("?xdp", bpf_dynptr_slice_rdwr, slice_buf_xdp, SLICE_BUF_INVALID,
SLICE_BUF_DYNPTR(bpf_dynptr_from_xdp)
SLICE_BUF_AT(8, 8)
"*(u64 *)(r10 - 8) = 0;")
/* the metadata is contiguous: the slice is never the buffer */
SLICE_BUF_TEST(slice_buf_skb_meta, __success,
SLICE_BUF_DYNPTR(bpf_dynptr_from_skb_meta)
SLICE_BUF_AT(8, 8)
"*(u64 *)(r10 - 8) = 0;")
extern void bpf_local_irq_save(unsigned long *) __weak __ksym;
/* a dynptr, an iterator or irq flags are made in the buffer */
#define SLICE_BUF_SPECIAL(name, type, make) \
SEC("?tc") \
__failure __msg("invalid mem access 'scalar'") \
int name(struct __sk_buff *skb) \
{ \
struct bpf_dynptr ptr; \
type buf; \
__u8 *p; \
\
bpf_dynptr_from_skb(skb, 0, &ptr); \
p = bpf_dynptr_slice_rdwr(&ptr, 0, &buf, sizeof(buf)); \
if (!p) \
return 0; \
make; \
return *p; \
}
SLICE_BUF_SPECIAL(slice_buf_dynptr, struct bpf_dynptr, bpf_dynptr_from_skb(skb, 0, &buf))
SLICE_BUF_SPECIAL(slice_buf_iter, struct bpf_iter_num, bpf_iter_num_new(&buf, 0, 1))
SLICE_BUF_SPECIAL(slice_buf_irq_flag, unsigned long, bpf_local_irq_save(&buf))
/* The type of a dynptr argument of a global function is not known: the slice may be the buffer. */
__noinline int slice_buf_global(struct bpf_dynptr *ptr)
{
__u64 buf;
__u8 *p;
p = bpf_dynptr_slice_rdwr(ptr, 0, &buf, sizeof(buf));
if (!p)
return 0;
*(volatile __u64 *)&buf = 0;
return *p;
}
SEC("?tc")
__failure __msg("invalid mem access 'scalar'")
int slice_buf_dynptr_arg(struct __sk_buff *skb)
{
struct bpf_dynptr ptr;
bpf_dynptr_from_skb(skb, 0, &ptr);
return slice_buf_global(&ptr);
}
/* Read-only skb data slice is invalidated on write to skb metadata */
SEC("?tc")
__failure __msg("invalid mem access 'scalar'")

View File

@ -231,6 +231,40 @@ int test_dynptr_skb_slice_non_linear(struct __sk_buff *skb)
return 1;
}
/*
* The tcp header is not in the linear part: the slice is the buffer, and a store
* through the slice is seen in the buffer. As in the documentation, the stores
* come before the compare with the buffer: after it a compiler may use one
* pointer in place of the other.
*/
SEC("?tc")
int test_dynptr_skb_slice_rdwr_buffer(struct __sk_buff *skb)
{
struct tcphdr buffer = {}, *tcp;
struct bpf_dynptr ptr;
bpf_dynptr_from_skb(skb, 0, &ptr);
tcp = bpf_dynptr_slice_rdwr(&ptr, TEST_SKB_LINEAR_SIZE, &buffer, sizeof(buffer));
if (!tcp) {
err = 1;
return 1;
}
tcp->source = 0x1234;
if (buffer.source != 0x1234) {
err = 2;
return 1;
}
/* a read of the buffer does not invalidate the slice */
tcp->dest = 0x5678;
if (tcp != &buffer)
err = 3;
else if (bpf_dynptr_write(&ptr, TEST_SKB_LINEAR_SIZE, &buffer, sizeof(buffer), 0))
err = 4;
return 1;
}
SEC("?tc")
int test_dynptr_skb_meta_data(struct __sk_buff *skb)
{