bpf: Fix indirect var_off stack access support
Subsystems (6)
v2 cover bpf: Fix indirect var_off stack access support
v1->v2:
- rely on meta = NULL to reject var_off stack access to uninit buffer.
This patch set is a follow-up for discussion [1].
It fixes variable offset stack access handling for raw and unprivileged mode, rejecting both of them.
Patch 1 handles raw (uninitialized) mode.
Patch 2 adds test for raw mode.
Patch 3 handles unprivileged mode.
Patch 4 adds test for unprivileged mode.
Patch 5 is a minor fix in verbose log.
Unprivileged mode is an interesting case since one (and only?) way to come up with variable offset is to use pointer arithmetics. Though pointer arithmetics is already prohibited for unprivileged mode. I'm not sure if it's enough though and it seems like a good idea to still reject variable offset for unpriv in check_stack_boundary(). Please see patches 3 and 4 for more details on this.
[1] https://marc.info/?l=linux-netdev&m=155419526427742&w=2
Andrey Ignatov (5):
bpf: Reject indirect var_off stack access in raw mode selftests/bpf: Test indirect var_off stack access in raw mode
bpf: Reject indirect var_off stack access in unpriv mode selftests/bpf: Test indirect var_off stack access in unpriv mode
bpf: Add missed newline in verifier verbose log
kernel/bpf/verifier.c | 27 +++++++++- .../testing/selftests/bpf/verifier/var_off.c | 54 +++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-)
Signature · 1 line
2.17.1
PATCH v2 1/5 bpf: Reject indirect var_off stack access in raw mode
It's hard to guarantee that whole memory is marked as initialized on helper return if uninitialized stack is accessed with variable offset since specific bounds are unknown to verifier. This may cause uninitialized stack leaking.
Reject such an access in check_stack_boundary to prevent possible leaking.
There are no known use-cases for indirect uninitialized stack access with variable offset so it shouldn't break anything.
--- kernel/bpf/verifier.c | 9 +++++++++ 1 file changed, 9 insertions(+)
kernel/bpf/verifier.c +9 -0
@@ -2213,6 +2213,15 @@ static int check_stack_boundary(struct bpf_verifier_env *env, int regno,if (err)return err; }else { +/* Only initialized buffer on stack is allowed to be accessed +* with variable offset. With uninitialized buffer it's hard to +* guarantee that whole memory is marked as initialized on +* helper return since specific bounds are unknown what may +* cause uninitialized stack leaking. +*/ +if (meta && meta->raw_mode) + meta =NULL ; + min_off = reg->smin_value + reg->off; max_off = reg->umax_value + reg->off; err = __check_stack_boundary(env, regno, min_off, access_size,
Signature · 1 line
2.17.1
PATCH v2 2/5 selftests/bpf: Test indirect var_off stack access in raw mode
In reply to Andrey IgnatovTest that verifier rejects indirect access to uninitialized stack with variable offset.
Example of output:
# ./test_verifier ... #859/p indirect variable-offset stack access, uninitialized OK
--- .../testing/selftests/bpf/verifier/var_off.c | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+)
tools/testing/selftests/bpf/verifier/var_off.c +27 -0
@@ -114,6 +114,33 @@ .result = REJECT, .prog_type = BPF_PROG_TYPE_LWT_IN, }, +{ + "indirect variable-offset stack access, uninitialized", + .insns = { +BPF_MOV64_IMM (BPF_REG_2 ,6 ), +BPF_MOV64_IMM (BPF_REG_3 ,28 ), +/* Fill the top 16 bytes of the stack. */ +BPF_ST_MEM (BPF_W ,BPF_REG_10 ,-16 ,0 ), +BPF_ST_MEM (BPF_DW ,BPF_REG_10 ,-8 ,0 ), +/* Get an unknown value. */ +BPF_LDX_MEM (BPF_W ,BPF_REG_4 ,BPF_REG_1 ,0 ), +/* Make it small and 4-byte aligned. */ +BPF_ALU64_IMM (BPF_AND ,BPF_REG_4 ,4 ), +BPF_ALU64_IMM (BPF_SUB ,BPF_REG_4 ,16 ), +/* Add it to fp. We now have either fp-12 or fp-16, we don't know +* which, but either way it points to initialized stack. +*/ +BPF_ALU64_REG (BPF_ADD ,BPF_REG_4 ,BPF_REG_10 ), +BPF_MOV64_IMM (BPF_REG_5 ,8 ), +/* Dereference it indirectly. */ +BPF_EMIT_CALL (BPF_FUNC_getsockopt), +BPF_MOV64_IMM (BPF_REG_0 ,0 ), + BPF_EXIT_INSN(), + }, + .errstr ="invalid indirect read from stack var_off" , + .result = REJECT, + .prog_type = BPF_PROG_TYPE_SOCK_OPS, +}, {"indirect variable-offset stack access, ok" , .insns = {
Signature · 1 line
2.17.1
PATCH v2 3/5 bpf: Reject indirect var_off stack access in unpriv mode
In reply to Andrey IgnatovProper support of indirect stack access with variable offset in unprivileged mode (!root) requires corresponding support in Spectre masking for stack ALU in retrieve_ptr_limit().
There are no use-case for variable offset in unprivileged mode though so make verifier reject such accesses for simplicity.
Pointer arithmetics is one (and only?) way to cause variable offset and it's already rejected in unpriv mode so that verifier won't even get to helper function whose argument contains variable offset, e.g.:
0: (7a) *(u64 *)(r10 -16) = 0
1: (7a) *(u64 *)(r10 -8) = 0
2: (61) r2 = *(u32 *)(r1 +0)
3: (57) r2 &= 4
4: (17) r2 -= 16
5: (0f) r2 += r10
variable stack access var_off=(0xfffffffffffffff0; 0x4) off=-16 size=1R2 stack pointer arithmetic goes out of range, prohibited for !root
Still it looks like a good idea to reject variable offset indirect stack access for unprivileged mode in check_stack_boundary() explicitly.
--- kernel/bpf/verifier.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+)
kernel/bpf/verifier.c +16 -0
@@ -2213,6 +2213,19 @@ static int check_stack_boundary(struct bpf_verifier_env *env, int regno,if (err)return err; }else { +/* Variable offset is prohibited for unprivileged mode for +* simplicity since it requires corresponding support in +* Spectre masking for stack ALU. +* See also retrieve_ptr_limit(). +*/ +if (!env->allow_ptr_leaks) { +char tn_buf[48 ]; + +tnum_strn (tn_buf,sizeof (tn_buf), reg->var_off); +verbose (env,"R%d indirect variable offset stack access prohibited for !root, var_off=%s\n" , + regno, tn_buf); +return -EACCES ; + }/* Only initialized buffer on stack is allowed to be accessed * with variable offset. With uninitialized buffer it's hard to * guarantee that whole memory is marked as initialized on
@@ -3355,6 +3368,9 @@ static int retrieve_ptr_limit(const struct bpf_reg_state *ptr_reg,switch (ptr_reg->type) {case PTR_TO_STACK : +/* Indirect variable offset stack access is prohibited in +* unprivileged mode so it's not handled here. +*/ off = ptr_reg->off + ptr_reg->var_off.value;if (mask_to_left) *ptr_limit =MAX_BPF_STACK + off;
Signature · 1 line
2.17.1
PATCH v2 4/5 selftests/bpf: Test indirect var_off stack access in unpriv mode
In reply to Andrey IgnatovTest that verifier rejects indirect stack access with variable offset in unprivileged mode and accepts same code in privileged mode.
Since pointer arithmetics is prohibited in unprivileged mode verifier should reject the program even before it gets to helper call that uses variable offset, at the time when that variable offset is trying to be constructed.
Example of output:
# ./test_verifier ... #859/u indirect variable-offset stack access, priv vs unpriv OK #859/p indirect variable-offset stack access, priv vs unpriv OK
--- .../testing/selftests/bpf/verifier/var_off.c | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+)
tools/testing/selftests/bpf/verifier/var_off.c +27 -0
@@ -114,6 +114,33 @@ .result = REJECT, .prog_type = BPF_PROG_TYPE_LWT_IN, }, +{ + "indirect variable-offset stack access, priv vs unpriv", + .insns = { +/* Fill the top 16 bytes of the stack. */ +BPF_ST_MEM (BPF_DW ,BPF_REG_10 ,-16 ,0 ), +BPF_ST_MEM (BPF_DW ,BPF_REG_10 ,-8 ,0 ), +/* Get an unknown value. */ +BPF_LDX_MEM (BPF_W ,BPF_REG_2 ,BPF_REG_1 ,0 ), +/* Make it small and 4-byte aligned. */ +BPF_ALU64_IMM (BPF_AND ,BPF_REG_2 ,4 ), +BPF_ALU64_IMM (BPF_SUB ,BPF_REG_2 ,16 ), +/* Add it to fp. We now have either fp-12 or fp-16, we don't know +* which, but either way it points to initialized stack. +*/ +BPF_ALU64_REG (BPF_ADD ,BPF_REG_2 ,BPF_REG_10 ), +/* Dereference it indirectly. */ +BPF_LD_MAP_FD (BPF_REG_1 ,0 ), +BPF_EMIT_CALL (BPF_FUNC_map_lookup_elem), +BPF_MOV64_IMM (BPF_REG_0 ,0 ), + BPF_EXIT_INSN(), + }, + .fixup_map_hash_8b = { 6 }, + .errstr_unpriv = "R2 stack pointer arithmetic goes out of range, prohibited for !root", + .result_unpriv = REJECT, + .result = ACCEPT, + .prog_type = BPF_PROG_TYPE_CGROUP_SKB, +}, {"indirect variable-offset stack access, uninitialized" , .insns = {
Signature · 1 line
2.17.1
PATCH v2 5/5 bpf: Add missed newline in verifier verbose log
In reply to Andrey Ignatovcheck_stack_access() that prints verbose log is used in adjust_ptr_min_max_vals() that prints its own verbose log and now they stick together, e.g.:
variable stack access var_off=(0xfffffffffffffff0; 0x4) off=-16 size=1R2 stack pointer arithmetic goes out of range, prohibited for !root
Add missing newline so that log is more readable:
variable stack access var_off=(0xfffffffffffffff0; 0x4) off=-16 size=1 R2 stack pointer arithmetic goes out of range, prohibited for !root
--- kernel/bpf/verifier.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
kernel/bpf/verifier.c +1 -1
@@ -1413,7 +1413,7 @@ static int check_stack_access(struct bpf_verifier_env *env,char tn_buf[48 ];tnum_strn (tn_buf,sizeof (tn_buf), reg->var_off); -verbose (env,"variable stack access var_off=%s off=%d size=%d" , +verbose (env,"variable stack access var_off=%s off=%d size=%d\n" , tn_buf, off, size);return -EACCES ; }
Signature · 1 line
2.17.1