Commit 801c6058 authored by Daniel Borkmann's avatar Daniel Borkmann

bpf: Fix leakage of uninitialized bpf stack under speculation

The current implemented mechanisms to mitigate data disclosure under
speculation mainly address stack and map value oob access from the
speculative domain. However, Piotr discovered that uninitialized BPF
stack is not protected yet, and thus old data from the kernel stack,
potentially including addresses of kernel structures, could still be
extracted from that 512 bytes large window. The BPF stack is special
compared to map values since it's not zero initialized for every
program invocation, whereas map values /are/ zero initialized upon
their initial allocation and thus cannot leak any prior data in either
domain. In the non-speculative domain, the verifier ensures that every
stack slot read must have a prior stack slot write by the BPF program
to avoid such data leaking issue.

However, this is not enough: for example, when the pointer arithmetic
operation moves the stack pointer from the last valid stack offset to
the first valid offset, the sanitation logic allows for any intermediate
offsets during speculative execution, which could then be used to
extract any restricted stack content via side-channel.

Given for unprivileged stack pointer arithmetic the use of unknown
but bounded scalars is generally forbidden, we can simply turn the
register-based arithmetic operation into an immediate-based arithmetic
operation without the need for masking. This also gives the benefit
of reducing the needed instructions for the operation. Given after
the work in 7fedb63a ("bpf: Tighten speculative pointer arithmetic
mask"), the aux->alu_limit already holds the final immediate value for
the offset register with the known scalar. Thus, a simple mov of the
immediate to AX register with using AX as the source for the original
instruction is sufficient and possible now in this case.
Reported-by: default avatarPiotr Krysiuk <piotras@gmail.com>
Signed-off-by: default avatarDaniel Borkmann <daniel@iogearbox.net>
Tested-by: default avatarPiotr Krysiuk <piotras@gmail.com>
Reviewed-by: default avatarPiotr Krysiuk <piotras@gmail.com>
Reviewed-by: default avatarJohn Fastabend <john.fastabend@gmail.com>
Acked-by: default avatarAlexei Starovoitov <ast@kernel.org>
parent b9b34ddb
...@@ -302,10 +302,11 @@ struct bpf_verifier_state_list { ...@@ -302,10 +302,11 @@ struct bpf_verifier_state_list {
}; };
/* Possible states for alu_state member. */ /* Possible states for alu_state member. */
#define BPF_ALU_SANITIZE_SRC 1U #define BPF_ALU_SANITIZE_SRC (1U << 0)
#define BPF_ALU_SANITIZE_DST 2U #define BPF_ALU_SANITIZE_DST (1U << 1)
#define BPF_ALU_NEG_VALUE (1U << 2) #define BPF_ALU_NEG_VALUE (1U << 2)
#define BPF_ALU_NON_POINTER (1U << 3) #define BPF_ALU_NON_POINTER (1U << 3)
#define BPF_ALU_IMMEDIATE (1U << 4)
#define BPF_ALU_SANITIZE (BPF_ALU_SANITIZE_SRC | \ #define BPF_ALU_SANITIZE (BPF_ALU_SANITIZE_SRC | \
BPF_ALU_SANITIZE_DST) BPF_ALU_SANITIZE_DST)
......
...@@ -6496,6 +6496,7 @@ static int sanitize_ptr_alu(struct bpf_verifier_env *env, ...@@ -6496,6 +6496,7 @@ static int sanitize_ptr_alu(struct bpf_verifier_env *env,
{ {
struct bpf_insn_aux_data *aux = commit_window ? cur_aux(env) : tmp_aux; struct bpf_insn_aux_data *aux = commit_window ? cur_aux(env) : tmp_aux;
struct bpf_verifier_state *vstate = env->cur_state; struct bpf_verifier_state *vstate = env->cur_state;
bool off_is_imm = tnum_is_const(off_reg->var_off);
bool off_is_neg = off_reg->smin_value < 0; bool off_is_neg = off_reg->smin_value < 0;
bool ptr_is_dst_reg = ptr_reg == dst_reg; bool ptr_is_dst_reg = ptr_reg == dst_reg;
u8 opcode = BPF_OP(insn->code); u8 opcode = BPF_OP(insn->code);
...@@ -6526,6 +6527,7 @@ static int sanitize_ptr_alu(struct bpf_verifier_env *env, ...@@ -6526,6 +6527,7 @@ static int sanitize_ptr_alu(struct bpf_verifier_env *env,
alu_limit = abs(tmp_aux->alu_limit - alu_limit); alu_limit = abs(tmp_aux->alu_limit - alu_limit);
} else { } else {
alu_state = off_is_neg ? BPF_ALU_NEG_VALUE : 0; alu_state = off_is_neg ? BPF_ALU_NEG_VALUE : 0;
alu_state |= off_is_imm ? BPF_ALU_IMMEDIATE : 0;
alu_state |= ptr_is_dst_reg ? alu_state |= ptr_is_dst_reg ?
BPF_ALU_SANITIZE_SRC : BPF_ALU_SANITIZE_DST; BPF_ALU_SANITIZE_SRC : BPF_ALU_SANITIZE_DST;
} }
...@@ -12371,7 +12373,7 @@ static int do_misc_fixups(struct bpf_verifier_env *env) ...@@ -12371,7 +12373,7 @@ static int do_misc_fixups(struct bpf_verifier_env *env)
const u8 code_add = BPF_ALU64 | BPF_ADD | BPF_X; const u8 code_add = BPF_ALU64 | BPF_ADD | BPF_X;
const u8 code_sub = BPF_ALU64 | BPF_SUB | BPF_X; const u8 code_sub = BPF_ALU64 | BPF_SUB | BPF_X;
struct bpf_insn *patch = &insn_buf[0]; struct bpf_insn *patch = &insn_buf[0];
bool issrc, isneg; bool issrc, isneg, isimm;
u32 off_reg; u32 off_reg;
aux = &env->insn_aux_data[i + delta]; aux = &env->insn_aux_data[i + delta];
...@@ -12382,16 +12384,21 @@ static int do_misc_fixups(struct bpf_verifier_env *env) ...@@ -12382,16 +12384,21 @@ static int do_misc_fixups(struct bpf_verifier_env *env)
isneg = aux->alu_state & BPF_ALU_NEG_VALUE; isneg = aux->alu_state & BPF_ALU_NEG_VALUE;
issrc = (aux->alu_state & BPF_ALU_SANITIZE) == issrc = (aux->alu_state & BPF_ALU_SANITIZE) ==
BPF_ALU_SANITIZE_SRC; BPF_ALU_SANITIZE_SRC;
isimm = aux->alu_state & BPF_ALU_IMMEDIATE;
off_reg = issrc ? insn->src_reg : insn->dst_reg; off_reg = issrc ? insn->src_reg : insn->dst_reg;
if (isneg) if (isimm) {
*patch++ = BPF_ALU64_IMM(BPF_MUL, off_reg, -1); *patch++ = BPF_MOV32_IMM(BPF_REG_AX, aux->alu_limit);
*patch++ = BPF_MOV32_IMM(BPF_REG_AX, aux->alu_limit); } else {
*patch++ = BPF_ALU64_REG(BPF_SUB, BPF_REG_AX, off_reg); if (isneg)
*patch++ = BPF_ALU64_REG(BPF_OR, BPF_REG_AX, off_reg); *patch++ = BPF_ALU64_IMM(BPF_MUL, off_reg, -1);
*patch++ = BPF_ALU64_IMM(BPF_NEG, BPF_REG_AX, 0); *patch++ = BPF_MOV32_IMM(BPF_REG_AX, aux->alu_limit);
*patch++ = BPF_ALU64_IMM(BPF_ARSH, BPF_REG_AX, 63); *patch++ = BPF_ALU64_REG(BPF_SUB, BPF_REG_AX, off_reg);
*patch++ = BPF_ALU64_REG(BPF_AND, BPF_REG_AX, off_reg); *patch++ = BPF_ALU64_REG(BPF_OR, BPF_REG_AX, off_reg);
*patch++ = BPF_ALU64_IMM(BPF_NEG, BPF_REG_AX, 0);
*patch++ = BPF_ALU64_IMM(BPF_ARSH, BPF_REG_AX, 63);
*patch++ = BPF_ALU64_REG(BPF_AND, BPF_REG_AX, off_reg);
}
if (!issrc) if (!issrc)
*patch++ = BPF_MOV64_REG(insn->dst_reg, insn->src_reg); *patch++ = BPF_MOV64_REG(insn->dst_reg, insn->src_reg);
insn->src_reg = BPF_REG_AX; insn->src_reg = BPF_REG_AX;
...@@ -12399,7 +12406,7 @@ static int do_misc_fixups(struct bpf_verifier_env *env) ...@@ -12399,7 +12406,7 @@ static int do_misc_fixups(struct bpf_verifier_env *env)
insn->code = insn->code == code_add ? insn->code = insn->code == code_add ?
code_sub : code_add; code_sub : code_add;
*patch++ = *insn; *patch++ = *insn;
if (issrc && isneg) if (issrc && isneg && !isimm)
*patch++ = BPF_ALU64_IMM(BPF_MUL, off_reg, -1); *patch++ = BPF_ALU64_IMM(BPF_MUL, off_reg, -1);
cnt = patch - insn_buf; cnt = patch - insn_buf;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment