Skip to content

Commit 6b4a64b

Browse files
andreimateianakryiko
authored andcommitted
bpf: Fix accesses to uninit stack slots
Privileged programs are supposed to be able to read uninitialized stack memory (ever since 6715df8) but, before this patch, these accesses were permitted inconsistently. In particular, accesses were permitted above state->allocated_stack, but not below it. In other words, if the stack was already "large enough", the access was permitted, but otherwise the access was rejected instead of being allowed to "grow the stack". This undesired rejection was happening in two places: - in check_stack_slot_within_bounds() - in check_stack_range_initialized() This patch arranges for these accesses to be permitted. A bunch of tests that were relying on the old rejection had to change; all of them were changed to add also run unprivileged, in which case the old behavior persists. One tests couldn't be updated - global_func16 - because it can't run unprivileged for other reasons. This patch also fixes the tracking of the stack size for variable-offset reads. This second fix is bundled in the same commit as the first one because they're inter-related. Before this patch, writes to the stack using registers containing a variable offset (as opposed to registers with fixed, known values) were not properly contributing to the function's needed stack size. As a result, it was possible for a program to verify, but then to attempt to read out-of-bounds data at runtime because a too small stack had been allocated for it. Each function tracks the size of the stack it needs in bpf_subprog_info.stack_depth, which is maintained by update_stack_depth(). For regular memory accesses, check_mem_access() was calling update_state_depth() but it was passing in only the fixed part of the offset register, ignoring the variable offset. This was incorrect; the minimum possible value of that register should be used instead. This tracking is now fixed by centralizing the tracking of stack size in grow_stack_state(), and by lifting the calls to grow_stack_state() to check_stack_access_within_bounds() as suggested by Andrii. The code is now simpler and more convincingly tracks the correct maximum stack size. check_stack_range_initialized() can now rely on enough stack having been allocated for the access; this helps with the fix for the first issue. A few tests were changed to also check the stack depth computation. The one that fails without this patch is verifier_var_off:stack_write_priv_vs_unpriv. Fixes: 01f810a ("bpf: Allow variable-offset stack access") Reported-by: Hao Sun <[email protected]> Signed-off-by: Andrei Matei <[email protected]> Signed-off-by: Andrii Nakryiko <[email protected]> Acked-by: Andrii Nakryiko <[email protected]> Link: https://lore.kernel.org/bpf/[email protected] Closes: https://lore.kernel.org/bpf/CABWLsev9g8UP_c3a=1qbuZUi20tGoUXoU07FPf-5FLvhOKOY+Q@mail.gmail.com/
1 parent 92e1567 commit 6b4a64b

File tree

9 files changed

+92
-72
lines changed

9 files changed

+92
-72
lines changed

kernel/bpf/verifier.c

Lines changed: 26 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,7 +1259,10 @@ static int resize_reference_state(struct bpf_func_state *state, size_t n)
12591259
return 0;
12601260
}
12611261

1262-
static int grow_stack_state(struct bpf_func_state *state, int size)
1262+
/* Possibly update state->allocated_stack to be at least size bytes. Also
1263+
* possibly update the function's high-water mark in its bpf_subprog_info.
1264+
*/
1265+
static int grow_stack_state(struct bpf_verifier_env *env, struct bpf_func_state *state, int size)
12631266
{
12641267
size_t old_n = state->allocated_stack / BPF_REG_SIZE, n = size / BPF_REG_SIZE;
12651268

@@ -1271,6 +1274,11 @@ static int grow_stack_state(struct bpf_func_state *state, int size)
12711274
return -ENOMEM;
12721275

12731276
state->allocated_stack = size;
1277+
1278+
/* update known max for given subprogram */
1279+
if (env->subprog_info[state->subprogno].stack_depth < size)
1280+
env->subprog_info[state->subprogno].stack_depth = size;
1281+
12741282
return 0;
12751283
}
12761284

@@ -4440,9 +4448,6 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,
44404448
struct bpf_reg_state *reg = NULL;
44414449
int insn_flags = insn_stack_access_flags(state->frameno, spi);
44424450

4443-
err = grow_stack_state(state, round_up(slot + 1, BPF_REG_SIZE));
4444-
if (err)
4445-
return err;
44464451
/* caller checked that off % size == 0 and -MAX_BPF_STACK <= off < 0,
44474452
* so it's aligned access and [off, off + size) are within stack limits
44484453
*/
@@ -4595,10 +4600,6 @@ static int check_stack_write_var_off(struct bpf_verifier_env *env,
45954600
(!value_reg && is_bpf_st_mem(insn) && insn->imm == 0))
45964601
writing_zero = true;
45974602

4598-
err = grow_stack_state(state, round_up(-min_off, BPF_REG_SIZE));
4599-
if (err)
4600-
return err;
4601-
46024603
for (i = min_off; i < max_off; i++) {
46034604
int spi;
46044605

@@ -5774,20 +5775,6 @@ static int check_ptr_alignment(struct bpf_verifier_env *env,
57745775
strict);
57755776
}
57765777

5777-
static int update_stack_depth(struct bpf_verifier_env *env,
5778-
const struct bpf_func_state *func,
5779-
int off)
5780-
{
5781-
u16 stack = env->subprog_info[func->subprogno].stack_depth;
5782-
5783-
if (stack >= -off)
5784-
return 0;
5785-
5786-
/* update known max for given subprogram */
5787-
env->subprog_info[func->subprogno].stack_depth = -off;
5788-
return 0;
5789-
}
5790-
57915778
/* starting from main bpf function walk all instructions of the function
57925779
* and recursively walk all callees that given function can call.
57935780
* Ignore jump and exit insns.
@@ -6577,13 +6564,14 @@ static int check_ptr_to_map_access(struct bpf_verifier_env *env,
65776564
* The minimum valid offset is -MAX_BPF_STACK for writes, and
65786565
* -state->allocated_stack for reads.
65796566
*/
6580-
static int check_stack_slot_within_bounds(s64 off,
6581-
struct bpf_func_state *state,
6582-
enum bpf_access_type t)
6567+
static int check_stack_slot_within_bounds(struct bpf_verifier_env *env,
6568+
s64 off,
6569+
struct bpf_func_state *state,
6570+
enum bpf_access_type t)
65836571
{
65846572
int min_valid_off;
65856573

6586-
if (t == BPF_WRITE)
6574+
if (t == BPF_WRITE || env->allow_uninit_stack)
65876575
min_valid_off = -MAX_BPF_STACK;
65886576
else
65896577
min_valid_off = -state->allocated_stack;
@@ -6632,7 +6620,7 @@ static int check_stack_access_within_bounds(
66326620
max_off = reg->smax_value + off + access_size;
66336621
}
66346622

6635-
err = check_stack_slot_within_bounds(min_off, state, type);
6623+
err = check_stack_slot_within_bounds(env, min_off, state, type);
66366624
if (!err && max_off > 0)
66376625
err = -EINVAL; /* out of stack access into non-negative offsets */
66386626

@@ -6647,8 +6635,10 @@ static int check_stack_access_within_bounds(
66476635
verbose(env, "invalid variable-offset%s stack R%d var_off=%s off=%d size=%d\n",
66486636
err_extra, regno, tn_buf, off, access_size);
66496637
}
6638+
return err;
66506639
}
6651-
return err;
6640+
6641+
return grow_stack_state(env, state, round_up(-min_off, BPF_REG_SIZE));
66526642
}
66536643

66546644
/* check whether memory at (regno + off) is accessible for t = (read | write)
@@ -6663,7 +6653,6 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
66636653
{
66646654
struct bpf_reg_state *regs = cur_regs(env);
66656655
struct bpf_reg_state *reg = regs + regno;
6666-
struct bpf_func_state *state;
66676656
int size, err = 0;
66686657

66696658
size = bpf_size_to_bytes(bpf_size);
@@ -6806,11 +6795,6 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
68066795
if (err)
68076796
return err;
68086797

6809-
state = func(env, reg);
6810-
err = update_stack_depth(env, state, off);
6811-
if (err)
6812-
return err;
6813-
68146798
if (t == BPF_READ)
68156799
err = check_stack_read(env, regno, off, size,
68166800
value_regno);
@@ -7004,7 +6988,8 @@ static int check_atomic(struct bpf_verifier_env *env, int insn_idx, struct bpf_i
70046988

70056989
/* When register 'regno' is used to read the stack (either directly or through
70066990
* a helper function) make sure that it's within stack boundary and, depending
7007-
* on the access type, that all elements of the stack are initialized.
6991+
* on the access type and privileges, that all elements of the stack are
6992+
* initialized.
70086993
*
70096994
* 'off' includes 'regno->off', but not its dynamic part (if any).
70106995
*
@@ -7112,8 +7097,11 @@ static int check_stack_range_initialized(
71127097

71137098
slot = -i - 1;
71147099
spi = slot / BPF_REG_SIZE;
7115-
if (state->allocated_stack <= slot)
7116-
goto err;
7100+
if (state->allocated_stack <= slot) {
7101+
verbose(env, "verifier bug: allocated_stack too small");
7102+
return -EFAULT;
7103+
}
7104+
71177105
stype = &state->stack[spi].slot_type[slot % BPF_REG_SIZE];
71187106
if (*stype == STACK_MISC)
71197107
goto mark;
@@ -7137,7 +7125,6 @@ static int check_stack_range_initialized(
71377125
goto mark;
71387126
}
71397127

7140-
err:
71417128
if (tnum_is_const(reg->var_off)) {
71427129
verbose(env, "invalid%s read from stack R%d off %d+%d size %d\n",
71437130
err_extra, regno, min_off, i - min_off, access_size);
@@ -7162,7 +7149,7 @@ static int check_stack_range_initialized(
71627149
* helper may write to the entire memory range.
71637150
*/
71647151
}
7165-
return update_stack_depth(env, state, min_off);
7152+
return 0;
71667153
}
71677154

71687155
static int check_helper_mem_access(struct bpf_verifier_env *env, int regno,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -846,7 +846,7 @@ __naked int delayed_precision_mark(void)
846846
"call %[bpf_iter_num_next];"
847847
"if r0 == 0 goto 2f;"
848848
"if r6 != 42 goto 3f;"
849-
"r7 = -32;"
849+
"r7 = -33;"
850850
"call %[bpf_get_prandom_u32];"
851851
"r6 = r0;"
852852
"goto 1b;\n"

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ __noinline int foo(int (*arr)[10])
1313
}
1414

1515
SEC("cgroup_skb/ingress")
16-
__failure __msg("invalid indirect read from stack")
16+
__success
1717
int global_func16(struct __sk_buff *skb)
1818
{
1919
int array[10];

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ __naked void stack_out_of_bounds(void)
2727

2828
SEC("socket")
2929
__description("uninitialized stack1")
30-
__failure __msg("invalid indirect read from stack")
31-
__failure_unpriv
30+
__success __log_level(4) __msg("stack depth 8")
31+
__failure_unpriv __msg_unpriv("invalid indirect read from stack")
3232
__naked void uninitialized_stack1(void)
3333
{
3434
asm volatile (" \
@@ -45,8 +45,8 @@ __naked void uninitialized_stack1(void)
4545

4646
SEC("socket")
4747
__description("uninitialized stack2")
48-
__failure __msg("invalid read from stack")
49-
__failure_unpriv
48+
__success __log_level(4) __msg("stack depth 8")
49+
__failure_unpriv __msg_unpriv("invalid read from stack")
5050
__naked void uninitialized_stack2(void)
5151
{
5252
asm volatile (" \

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
#include <bpf/bpf_helpers.h>
66
#include "bpf_misc.h"
77

8-
SEC("cgroup/sysctl")
8+
SEC("socket")
99
__description("ARG_PTR_TO_LONG uninitialized")
10-
__failure __msg("invalid indirect read from stack R4 off -16+0 size 8")
10+
__success
11+
__failure_unpriv __msg_unpriv("invalid indirect read from stack R4 off -16+0 size 8")
1112
__naked void arg_ptr_to_long_uninitialized(void)
1213
{
1314
asm volatile (" \

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
#include <bpf/bpf_helpers.h>
66
#include "bpf_misc.h"
77

8-
SEC("tc")
8+
SEC("socket")
99
__description("raw_stack: no skb_load_bytes")
10-
__failure __msg("invalid read from stack R6 off=-8 size=8")
10+
__success
11+
__failure_unpriv __msg_unpriv("invalid read from stack R6 off=-8 size=8")
1112
__naked void stack_no_skb_load_bytes(void)
1213
{
1314
asm volatile (" \

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

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,10 @@ __naked void stack_read_priv_vs_unpriv(void)
5959
" ::: __clobber_all);
6060
}
6161

62-
SEC("lwt_in")
62+
SEC("cgroup/skb")
6363
__description("variable-offset stack read, uninitialized")
64-
__failure __msg("invalid variable-offset read from stack R2")
64+
__success
65+
__failure_unpriv __msg_unpriv("R2 variable stack access prohibited for !root")
6566
__naked void variable_offset_stack_read_uninitialized(void)
6667
{
6768
asm volatile (" \
@@ -83,12 +84,55 @@ __naked void variable_offset_stack_read_uninitialized(void)
8384

8485
SEC("socket")
8586
__description("variable-offset stack write, priv vs unpriv")
86-
__success __failure_unpriv
87+
__success
88+
/* Check that the maximum stack depth is correctly maintained according to the
89+
* maximum possible variable offset.
90+
*/
91+
__log_level(4) __msg("stack depth 16")
92+
__failure_unpriv
8793
/* Variable stack access is rejected for unprivileged.
8894
*/
8995
__msg_unpriv("R2 variable stack access prohibited for !root")
9096
__retval(0)
9197
__naked void stack_write_priv_vs_unpriv(void)
98+
{
99+
asm volatile (" \
100+
/* Get an unknown value */ \
101+
r2 = *(u32*)(r1 + 0); \
102+
/* Make it small and 8-byte aligned */ \
103+
r2 &= 8; \
104+
r2 -= 16; \
105+
/* Add it to fp. We now have either fp-8 or \
106+
* fp-16, but we don't know which \
107+
*/ \
108+
r2 += r10; \
109+
/* Dereference it for a stack write */ \
110+
r0 = 0; \
111+
*(u64*)(r2 + 0) = r0; \
112+
exit; \
113+
" ::: __clobber_all);
114+
}
115+
116+
/* Similar to the previous test, but this time also perform a read from the
117+
* address written to with a variable offset. The read is allowed, showing that,
118+
* after a variable-offset write, a priviledged program can read the slots that
119+
* were in the range of that write (even if the verifier doesn't actually know if
120+
* the slot being read was really written to or not.
121+
*
122+
* Despite this test being mostly a superset, the previous test is also kept for
123+
* the sake of it checking the stack depth in the case where there is no read.
124+
*/
125+
SEC("socket")
126+
__description("variable-offset stack write followed by read")
127+
__success
128+
/* Check that the maximum stack depth is correctly maintained according to the
129+
* maximum possible variable offset.
130+
*/
131+
__log_level(4) __msg("stack depth 16")
132+
__failure_unpriv
133+
__msg_unpriv("R2 variable stack access prohibited for !root")
134+
__retval(0)
135+
__naked void stack_write_followed_by_read(void)
92136
{
93137
asm volatile (" \
94138
/* Get an unknown value */ \
@@ -103,12 +147,7 @@ __naked void stack_write_priv_vs_unpriv(void)
103147
/* Dereference it for a stack write */ \
104148
r0 = 0; \
105149
*(u64*)(r2 + 0) = r0; \
106-
/* Now read from the address we just wrote. This shows\
107-
* that, after a variable-offset write, a priviledged\
108-
* program can read the slots that were in the range of\
109-
* that write (even if the verifier doesn't actually know\
110-
* if the slot being read was really written to or not.\
111-
*/ \
150+
/* Now read from the address we just wrote. */ \
112151
r3 = *(u64*)(r2 + 0); \
113152
r0 = 0; \
114153
exit; \
@@ -282,9 +321,10 @@ __naked void access_min_out_of_bound(void)
282321
: __clobber_all);
283322
}
284323

285-
SEC("lwt_in")
324+
SEC("cgroup/skb")
286325
__description("indirect variable-offset stack access, min_off < min_initialized")
287-
__failure __msg("invalid indirect read from stack R2 var_off")
326+
__success
327+
__failure_unpriv __msg_unpriv("R2 variable stack access prohibited for !root")
288328
__naked void access_min_off_min_initialized(void)
289329
{
290330
asm volatile (" \

tools/testing/selftests/bpf/verifier/atomic_cmpxchg.c

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -83,17 +83,6 @@
8383
.result = REJECT,
8484
.errstr = "!read_ok",
8585
},
86-
{
87-
"Can't use cmpxchg on uninit memory",
88-
.insns = {
89-
BPF_MOV64_IMM(BPF_REG_0, 3),
90-
BPF_MOV64_IMM(BPF_REG_2, 4),
91-
BPF_ATOMIC_OP(BPF_DW, BPF_CMPXCHG, BPF_REG_10, BPF_REG_2, -8),
92-
BPF_EXIT_INSN(),
93-
},
94-
.result = REJECT,
95-
.errstr = "invalid read from stack",
96-
},
9786
{
9887
"BPF_W cmpxchg should zero top 32 bits",
9988
.insns = {

tools/testing/selftests/bpf/verifier/calls.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1505,7 +1505,9 @@
15051505
.prog_type = BPF_PROG_TYPE_XDP,
15061506
.fixup_map_hash_8b = { 23 },
15071507
.result = REJECT,
1508-
.errstr = "invalid read from stack R7 off=-16 size=8",
1508+
.errstr = "R0 invalid mem access 'scalar'",
1509+
.result_unpriv = REJECT,
1510+
.errstr_unpriv = "invalid read from stack R7 off=-16 size=8",
15091511
},
15101512
{
15111513
"calls: two calls that receive map_value via arg=ptr_stack_of_caller. test1",

0 commit comments

Comments
 (0)