mirror of
https://github.com/torvalds/linux.git
synced 2026-11-06 21:58:01 +01:00
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:
commit
29f85bba33
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 = ®s[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",
|
||||
|
|
|
|||
|
|
@ -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},
|
||||
|
|
|
|||
|
|
@ -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'")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user