Skip to content

Commit 6e7e63c

Browse files
thejhAlexei Starovoitov
authored andcommitted
bpf: Forbid XADD on spilled pointers for unprivileged users
When check_xadd() verifies an XADD operation on a pointer to a stack slot containing a spilled pointer, check_stack_read() verifies that the read, which is part of XADD, is valid. However, since the placeholder value -1 is passed as `value_regno`, check_stack_read() can only return a binary decision and can't return the type of the value that was read. The intent here is to verify whether the value read from the stack slot may be used as a SCALAR_VALUE; but since check_stack_read() doesn't check the type, and the type information is lost when check_stack_read() returns, this is not enforced, and a malicious user can abuse XADD to leak spilled kernel pointers. Fix it by letting check_stack_read() verify that the value is usable as a SCALAR_VALUE if no type information is passed to the caller. To be able to use __is_pointer_value() in check_stack_read(), move it up. Fix up the expected unprivileged error message for a BPF selftest that, until now, assumed that unprivileged users can use XADD on stack-spilled pointers. This also gives us a test for the behavior introduced in this patch for free. In theory, this could also be fixed by forbidding XADD on stack spills entirely, since XADD is a locked operation (for operations on memory with concurrency) and there can't be any concurrency on the BPF stack; but Alexei has said that he wants to keep XADD on stack slots working to avoid changes to the test suite [1]. The following BPF program demonstrates how to leak a BPF map pointer as an unprivileged user using this bug: // r7 = map_pointer BPF_LD_MAP_FD(BPF_REG_7, small_map), // r8 = launder(map_pointer) BPF_STX_MEM(BPF_DW, BPF_REG_FP, BPF_REG_7, -8), BPF_MOV64_IMM(BPF_REG_1, 0), ((struct bpf_insn) { .code = BPF_STX | BPF_DW | BPF_XADD, .dst_reg = BPF_REG_FP, .src_reg = BPF_REG_1, .off = -8 }), BPF_LDX_MEM(BPF_DW, BPF_REG_8, BPF_REG_FP, -8), // store r8 into map BPF_MOV64_REG(BPF_REG_ARG1, BPF_REG_7), BPF_MOV64_REG(BPF_REG_ARG2, BPF_REG_FP), BPF_ALU64_IMM(BPF_ADD, BPF_REG_ARG2, -4), BPF_ST_MEM(BPF_W, BPF_REG_ARG2, 0, 0), BPF_EMIT_CALL(BPF_FUNC_map_lookup_elem), BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1), BPF_EXIT_INSN(), BPF_STX_MEM(BPF_DW, BPF_REG_0, BPF_REG_8, 0), BPF_MOV64_IMM(BPF_REG_0, 0), BPF_EXIT_INSN() [1] https://lore.kernel.org/bpf/[email protected]/ Fixes: 17a5267 ("bpf: verifier (add verifier core)") Signed-off-by: Jann Horn <[email protected]> Signed-off-by: Alexei Starovoitov <[email protected]> Link: https://lore.kernel.org/bpf/[email protected]
1 parent bc23d0e commit 6e7e63c

File tree

2 files changed

+20
-9
lines changed

2 files changed

+20
-9
lines changed

kernel/bpf/verifier.c

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2118,6 +2118,15 @@ static bool register_is_const(struct bpf_reg_state *reg)
21182118
return reg->type == SCALAR_VALUE && tnum_is_const(reg->var_off);
21192119
}
21202120

2121+
static bool __is_pointer_value(bool allow_ptr_leaks,
2122+
const struct bpf_reg_state *reg)
2123+
{
2124+
if (allow_ptr_leaks)
2125+
return false;
2126+
2127+
return reg->type != SCALAR_VALUE;
2128+
}
2129+
21212130
static void save_register_state(struct bpf_func_state *state,
21222131
int spi, struct bpf_reg_state *reg)
21232132
{
@@ -2308,6 +2317,16 @@ static int check_stack_read(struct bpf_verifier_env *env,
23082317
* which resets stack/reg liveness for state transitions
23092318
*/
23102319
state->regs[value_regno].live |= REG_LIVE_WRITTEN;
2320+
} else if (__is_pointer_value(env->allow_ptr_leaks, reg)) {
2321+
/* If value_regno==-1, the caller is asking us whether
2322+
* it is acceptable to use this value as a SCALAR_VALUE
2323+
* (e.g. for XADD).
2324+
* We must not allow unprivileged callers to do that
2325+
* with spilled pointers.
2326+
*/
2327+
verbose(env, "leaking pointer from stack off %d\n",
2328+
off);
2329+
return -EACCES;
23112330
}
23122331
mark_reg_read(env, reg, reg->parent, REG_LIVE_READ64);
23132332
} else {
@@ -2673,15 +2692,6 @@ static int check_sock_access(struct bpf_verifier_env *env, int insn_idx,
26732692
return -EACCES;
26742693
}
26752694

2676-
static bool __is_pointer_value(bool allow_ptr_leaks,
2677-
const struct bpf_reg_state *reg)
2678-
{
2679-
if (allow_ptr_leaks)
2680-
return false;
2681-
2682-
return reg->type != SCALAR_VALUE;
2683-
}
2684-
26852695
static struct bpf_reg_state *reg_state(struct bpf_verifier_env *env, int regno)
26862696
{
26872697
return cur_regs(env) + regno;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
BPF_EXIT_INSN(),
8989
},
9090
.fixup_map_hash_48b = { 3 },
91+
.errstr_unpriv = "leaking pointer from stack off -8",
9192
.errstr = "R0 invalid mem access 'inv'",
9293
.result = REJECT,
9394
.flags = F_NEEDS_EFFICIENT_UNALIGNED_ACCESS,

0 commit comments

Comments
 (0)