diff options
| author | Alexei Starovoitov <ast@kernel.org> | 2026-11-02 05:59:12 +0000 |
|---|---|---|
| committer | Alexei Starovoitov <ast@kernel.org> | 2026-11-02 17:53:14 +0000 |
| commit | cdefba74b53fb9b482355ebde1654800aea45dae (patch) | |
| tree | 6e27876598dd1bc8d80a92af88a807514d793ae8 /scripts/basic | |
| parent | 27f0738d348c386e59c7ed147abdc9b91711f97d (diff) | |
| download | linux-cdefba74b53fb9b482355ebde1654800aea45dae.tar.gz linux-cdefba74b53fb9b482355ebde1654800aea45dae.zip | |
bpf: Don't trust pointers spilled in slots that a callee can write
arg_track follows pointers to the stack through registers and spills.
Scalar evolution and the removal of dead slots rely on it to know which
slots an insn reads and writes.
arg_track assumes that a call does not change the pointers that are spilled
in the frame of the caller. But a static subprog or a callback can store
a pointer to other memory there, and the caller can use it:
*(u64 *)(r10 - 16) = fp-8
r1 = fp-16, r2 = map_value
call sub ; *(u64 *)(r1 + 0) = r2
r7 = *(u64 *)(r10 - 16) ; verifier: map_value. arg_track: fp-8
*(u64 *)(r10 - 8) = fp-24
*(u64 *)(r7 + 0) = 0 ; arg_track: fp-8 is overwritten
r8 = *(u64 *)(r10 - 8) ; verifier: fp-24. arg_track: none
loop:
r1 = *(u64 *)(r10 - 24) ; the counter of the loop
...
*(u64 *)(r8 + 0) = 4096 ; arg_track: not a write to fp-24
Scalar evolution does not see the last store. It gives the counter the
range of a loop that only increments it. The prog loads, and it accesses
a map value out of bounds.
Valid progs are rejected as well. The store through r7 makes fp-8 dead, and
a later read of fp-8 fails with "slot poisoned by dead code elimination".
When a static subprog or a callback can get a pointer to the frame of its
caller, join every slot of that frame with "not a pointer to the stack".
Reads and possible writes through a filled pointer are still seen, but no
store through it is a certain write. It has to be every slot: the callee
reaches the whole frame from one pointer, and callees are analyzed after
the caller. The price is precision: an 8-byte load through a filled
pointer gives a value that can point to any frame.
For a spilled copy of r10 this needs "bpf: Fix the join of fp+0 with
a value that is not a pointer to stack".
In selftests, Cilium and sched_ext the stack marks change in 4 of 12522
progs. Verdicts, insns and states do not change in any prog.
one_fp_clear_stack_threshold and two_fp_clear_stack_threshold expect the
fake offset fp+0 in the log now.
Fixes: bf0c571f7feb ("bpf: introduce forward arg-tracking dataflow analysis")
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://patch.msgid.link/20261102055913.3598414-4-alexei.starovoitov@gmail.com
Diffstat (limited to 'scripts/basic')
0 files changed, 0 insertions, 0 deletions
