Commit 345e004d authored by Paul Chaignon's avatar Paul Chaignon Committed by Alexei Starovoitov

bpf: Fix incorrect state pruning for <8B spill/fill

Commit 354e8f19 ("bpf: Support <8-byte scalar spill and refill")
introduced support in the verifier to track <8B spill/fills of scalars.
The backtracking logic for the precision bit was however skipping
spill/fills of less than 8B. That could cause state pruning to consider
two states equivalent when they shouldn't be.

As an example, consider the following bytecode snippet:

  0:  r7 = r1
  1:  call bpf_get_prandom_u32
  2:  r6 = 2
  3:  if r0 == 0 goto pc+1
  4:  r6 = 3
  ...
  8: [state pruning point]
  ...
  /* u32 spill/fill */
  10: *(u32 *)(r10 - 8) = r6
  11: r8 = *(u32 *)(r10 - 8)
  12: r0 = 0
  13: if r8 == 3 goto pc+1
  14: r0 = 1
  15: exit

The verifier first walks the path with R6=3. Given the support for <8B
spill/fills, at instruction 13, it knows the condition is true and skips
instruction 14. At that point, the backtracking logic kicks in but stops
at the fill instruction since it only propagates the precision bit for
8B spill/fill. When the verifier then walks the path with R6=2, it will
consider it safe at instruction 8 because R6 is not marked as needing
precision. Instruction 14 is thus never walked and is then incorrectly
removed as 'dead code'.

It's also possible to lead the verifier to accept e.g. an out-of-bound
memory access instead of causing an incorrect dead code elimination.

This regression was found via Cilium's bpf-next CI where it was causing
a conntrack map update to be silently skipped because the code had been
removed by the verifier.

This commit fixes it by enabling support for <8B spill/fills in the
bactracking logic. In case of a <8B spill/fill, the full 8B stack slot
will be marked as needing precision. Then, in __mark_chain_precision,
any tracked register spilled in a marked slot will itself be marked as
needing precision, regardless of the spill size. This logic makes two
assumptions: (1) only 8B-aligned spill/fill are tracked and (2) spilled
registers are only tracked if the spill and fill sizes are equal. Commit
ef979017 ("bpf: selftest: Add verifier tests for <8-byte scalar
spill and refill") covers the first assumption and the next commit in
this patchset covers the second.

Fixes: 354e8f19 ("bpf: Support <8-byte scalar spill and refill")
Signed-off-by: default avatarPaul Chaignon <paul@isovalent.com>
Signed-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
parent ab443c53
...@@ -2379,8 +2379,6 @@ static int backtrack_insn(struct bpf_verifier_env *env, int idx, ...@@ -2379,8 +2379,6 @@ static int backtrack_insn(struct bpf_verifier_env *env, int idx,
*/ */
if (insn->src_reg != BPF_REG_FP) if (insn->src_reg != BPF_REG_FP)
return 0; return 0;
if (BPF_SIZE(insn->code) != BPF_DW)
return 0;
/* dreg = *(u64 *)[fp - off] was a fill from the stack. /* dreg = *(u64 *)[fp - off] was a fill from the stack.
* that [fp - off] slot contains scalar that needs to be * that [fp - off] slot contains scalar that needs to be
...@@ -2403,8 +2401,6 @@ static int backtrack_insn(struct bpf_verifier_env *env, int idx, ...@@ -2403,8 +2401,6 @@ static int backtrack_insn(struct bpf_verifier_env *env, int idx,
/* scalars can only be spilled into stack */ /* scalars can only be spilled into stack */
if (insn->dst_reg != BPF_REG_FP) if (insn->dst_reg != BPF_REG_FP)
return 0; return 0;
if (BPF_SIZE(insn->code) != BPF_DW)
return 0;
spi = (-insn->off - 1) / BPF_REG_SIZE; spi = (-insn->off - 1) / BPF_REG_SIZE;
if (spi >= 64) { if (spi >= 64) {
verbose(env, "BUG spi %d\n", spi); verbose(env, "BUG spi %d\n", spi);
......
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