mirror of
https://github.com/torvalds/linux.git
synced 2026-11-06 21:58:01 +01:00
bpf: Fix the join of fp+0 with a value that is not a pointer to stack
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.
Where two paths meet, a register can be fp-N on one path and not a pointer
to the stack on the other path. There is no value for that. The join adds
a fake second offset, fp+0: with two offsets no store through the register
is a certain write. But for fp+0 the fake offset is the offset itself:
join(fp-8, none) = {fp-8, fp+0} two offsets
join(fp+0, none) = {fp+0} one offset: "exactly fp+0"
Then a store counts as a certain write to the stack, but at run time it can
go to other memory:
r7 = r10, or r7 = map_value + 8 ; arg_track: exactly fp+0
*(u64 *)(r10 - 8) = fp-24
*(u64 *)(r7 - 8) = map_value ; arg_track: fp-8 is overwritten
r8 = *(u64 *)(r10 - 8) ; fp-24 or map_value. 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 the single offset is 0, drop the offsets. The result is a pointer to
an unknown slot of the frame, and all updates through it are weak.
Veristat sees no difference in selftests, Cilium and sched_ext.
Fixes: bf0c571f7f ("bpf: introduce forward arg-tracking dataflow analysis")
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://patch.msgid.link/20261102055913.3598414-2-alexei.starovoitov@gmail.com
This commit is contained in:
parent
03b0138974
commit
6185d4fc6e
|
|
@ -976,6 +976,19 @@ static struct arg_track arg_join_imprecise(struct arg_track a, struct arg_track
|
|||
return (struct arg_track){ .mask = m, .frame = ARG_IMPRECISE };
|
||||
}
|
||||
|
||||
/*
|
||||
* 'a' or a value that is not a pointer to stack. A single fp-N gets a fake fp+0,
|
||||
* to keep stack_use and prevent stack_def. fp+0 itself would stay a single offset.
|
||||
*/
|
||||
static struct arg_track arg_or_none(struct arg_track a)
|
||||
{
|
||||
if (a.off_cnt != 1)
|
||||
return a;
|
||||
if (!a.off[0])
|
||||
return (struct arg_track){ .frame = a.frame };
|
||||
return arg_merge_offsets(a, arg_single(a.frame, 0));
|
||||
}
|
||||
|
||||
/* Join two arg_track values at merge points */
|
||||
static struct arg_track __arg_track_join(struct arg_track a, struct arg_track b)
|
||||
{
|
||||
|
|
@ -1000,20 +1013,10 @@ static struct arg_track __arg_track_join(struct arg_track a, struct arg_track b)
|
|||
*/
|
||||
if (a.frame == ARG_NONE && b.frame == ARG_NONE)
|
||||
return a;
|
||||
if (a.frame >= 0 && b.frame == ARG_NONE) {
|
||||
/*
|
||||
* When joining single fp-N add fake fp+0 to
|
||||
* keep stack_use and prevent stack_def
|
||||
*/
|
||||
if (a.off_cnt == 1)
|
||||
return arg_merge_offsets(a, arg_single(a.frame, 0));
|
||||
return a;
|
||||
}
|
||||
if (b.frame >= 0 && a.frame == ARG_NONE) {
|
||||
if (b.off_cnt == 1)
|
||||
return arg_merge_offsets(b, arg_single(b.frame, 0));
|
||||
return b;
|
||||
}
|
||||
if (a.frame >= 0 && b.frame == ARG_NONE)
|
||||
return arg_or_none(a);
|
||||
if (b.frame >= 0 && a.frame == ARG_NONE)
|
||||
return arg_or_none(b);
|
||||
|
||||
return arg_join_imprecise(a, b);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user