Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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/20200416211116.qxqcza5vo2ddnkdq@ast-mbp.dhcp.thefacebook.com/

Fixes: 17a5267067f3 ("bpf: verifier (add verifier core)")
Signed-off-by: Jann Horn <jannh@google.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://lore.kernel.org/bpf/20200417000007.10734-1-jannh@google.com

authored by

Jann Horn and committed by
Alexei Starovoitov
6e7e63cb bc23d0e3

+20 -9
+19 -9
kernel/bpf/verifier.c
··· 2118 2118 return reg->type == SCALAR_VALUE && tnum_is_const(reg->var_off); 2119 2119 } 2120 2120 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 + 2121 2130 static void save_register_state(struct bpf_func_state *state, 2122 2131 int spi, struct bpf_reg_state *reg) 2123 2132 { ··· 2317 2308 * which resets stack/reg liveness for state transitions 2318 2309 */ 2319 2310 state->regs[value_regno].live |= REG_LIVE_WRITTEN; 2311 + } else if (__is_pointer_value(env->allow_ptr_leaks, reg)) { 2312 + /* If value_regno==-1, the caller is asking us whether 2313 + * it is acceptable to use this value as a SCALAR_VALUE 2314 + * (e.g. for XADD). 2315 + * We must not allow unprivileged callers to do that 2316 + * with spilled pointers. 2317 + */ 2318 + verbose(env, "leaking pointer from stack off %d\n", 2319 + off); 2320 + return -EACCES; 2320 2321 } 2321 2322 mark_reg_read(env, reg, reg->parent, REG_LIVE_READ64); 2322 2323 } else { ··· 2690 2671 regno, reg_type_str[reg->type], off, size); 2691 2672 2692 2673 return -EACCES; 2693 - } 2694 - 2695 - static bool __is_pointer_value(bool allow_ptr_leaks, 2696 - const struct bpf_reg_state *reg) 2697 - { 2698 - if (allow_ptr_leaks) 2699 - return false; 2700 - 2701 - return reg->type != SCALAR_VALUE; 2702 2674 } 2703 2675 2704 2676 static struct bpf_reg_state *reg_state(struct bpf_verifier_env *env, int regno)
+1
tools/testing/selftests/bpf/verifier/value_illegal_alu.c
··· 88 88 BPF_EXIT_INSN(), 89 89 }, 90 90 .fixup_map_hash_48b = { 3 }, 91 + .errstr_unpriv = "leaking pointer from stack off -8", 91 92 .errstr = "R0 invalid mem access 'inv'", 92 93 .result = REJECT, 93 94 .flags = F_NEEDS_EFFICIENT_UNALIGNED_ACCESS,