Skip to content

Commit b0388ba

Browse files
puranjaymohanAlexei Starovoitov
authored andcommitted
bpf: Relax scalar id equivalence for state pruning
Scalar register IDs are used by the verifier to track relationships between registers and enable bounds propagation across those relationships. Once an ID becomes singular (i.e. only a single register/stack slot carries it), it can no longer contribute to bounds propagation and effectively becomes stale. The previous commit makes the verifier clear such ids before caching the state. When comparing the current and cached states for pruning, these stale IDs can cause technically equivalent states to be considered different and thus prevent pruning. For example, in the selftest added in the next commit, two registers - r6 and r7 are not linked to any other registers and get cached with id=0, in the current state, they are both linked to each other with id=A. Before this commit, check_scalar_ids would give temporary ids to r6 and r7 (say tid1 and tid2) and then check_ids() would map tid1->A, and when it would see tid2->A, it would not consider these state equivalent. Relax scalar ID equivalence by treating rold->id == 0 as "independent": if the old state did not rely on any ID relationships for a register, then any ID/linking present in the current state only adds constraints and is always safe to accept for pruning. Implement this by returning true immediately in check_scalar_ids() when old_id == 0. Maintain correctness for the opposite direction (old_id != 0 && cur_id == 0) by still allocating a temporary ID for cur_id == 0. This avoids incorrectly allowing multiple independent current registers (id==0) to satisfy a single linked old ID during mapping. Signed-off-by: Puranjay Mohan <puranjay@kernel.org> Link: https://lore.kernel.org/r/20260203165102.2302462-5-puranjay@kernel.org Signed-off-by: Alexei Starovoitov <ast@kernel.org>
1 parent a24d6f9 commit b0388ba

2 files changed

Lines changed: 56 additions & 15 deletions

File tree

kernel/bpf/verifier.c

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19387,13 +19387,29 @@ static bool check_ids(u32 old_id, u32 cur_id, struct bpf_idmap *idmap)
1938719387
return false;
1938819388
}
1938919389

19390-
/* Similar to check_ids(), but allocate a unique temporary ID
19391-
* for 'old_id' or 'cur_id' of zero.
19392-
* This makes pairs like '0 vs unique ID', 'unique ID vs 0' valid.
19390+
/*
19391+
* Compare scalar register IDs for state equivalence.
19392+
*
19393+
* When old_id == 0, the old register is independent - not linked to any
19394+
* other register. Any linking in the current state only adds constraints,
19395+
* making it more restrictive. Since the old state didn't rely on any ID
19396+
* relationships for this register, it's always safe to accept cur regardless
19397+
* of its ID. Hence, return true immediately.
19398+
*
19399+
* When old_id != 0 but cur_id == 0, we need to ensure that different
19400+
* independent registers in cur don't incorrectly satisfy the ID matching
19401+
* requirements of linked registers in old.
19402+
*
19403+
* Example: if old has r6.id=X and r7.id=X (linked), but cur has r6.id=0
19404+
* and r7.id=0 (both independent), without temp IDs both would map old_id=X
19405+
* to cur_id=0 and pass. With temp IDs: r6 maps X->temp1, r7 tries to map
19406+
* X->temp2, but X is already mapped to temp1, so the check fails correctly.
1939319407
*/
1939419408
static bool check_scalar_ids(u32 old_id, u32 cur_id, struct bpf_idmap *idmap)
1939519409
{
19396-
old_id = old_id ? old_id : ++idmap->tmp_id_gen;
19410+
if (!old_id)
19411+
return true;
19412+
1939719413
cur_id = cur_id ? cur_id : ++idmap->tmp_id_gen;
1939819414

1939919415
return check_ids(old_id, cur_id, idmap);
@@ -19618,11 +19634,21 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,
1961819634
}
1961919635
if (!rold->precise && exact == NOT_EXACT)
1962019636
return true;
19621-
if ((rold->id & BPF_ADD_CONST) != (rcur->id & BPF_ADD_CONST))
19622-
return false;
19623-
if ((rold->id & BPF_ADD_CONST) && (rold->off != rcur->off))
19624-
return false;
19625-
/* Why check_ids() for scalar registers?
19637+
/*
19638+
* Linked register tracking uses rold->id to detect relationships.
19639+
* When rold->id == 0, the register is independent and any linking
19640+
* in rcur only adds constraints. When rold->id != 0, we must verify
19641+
* id mapping and (for BPF_ADD_CONST) offset consistency.
19642+
*
19643+
* +------------------+-----------+------------------+---------------+
19644+
* | | rold->id | rold + ADD_CONST | rold->id == 0 |
19645+
* |------------------+-----------+------------------+---------------|
19646+
* | rcur->id | range,ids | false | range |
19647+
* | rcur + ADD_CONST | false | range,ids,off | range |
19648+
* | rcur->id == 0 | range,ids | false | range |
19649+
* +------------------+-----------+------------------+---------------+
19650+
*
19651+
* Why check_ids() for scalar registers?
1962619652
*
1962719653
* Consider the following BPF code:
1962819654
* 1: r6 = ... unbound scalar, ID=a ...
@@ -19646,9 +19672,22 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,
1964619672
* ---
1964719673
* Also verify that new value satisfies old value range knowledge.
1964819674
*/
19649-
return range_within(rold, rcur) &&
19650-
tnum_in(rold->var_off, rcur->var_off) &&
19651-
check_scalar_ids(rold->id, rcur->id, idmap);
19675+
19676+
/* ADD_CONST mismatch: different linking semantics */
19677+
if ((rold->id & BPF_ADD_CONST) && !(rcur->id & BPF_ADD_CONST))
19678+
return false;
19679+
19680+
if (rold->id && !(rold->id & BPF_ADD_CONST) && (rcur->id & BPF_ADD_CONST))
19681+
return false;
19682+
19683+
/* Both have offset linkage: offsets must match */
19684+
if ((rold->id & BPF_ADD_CONST) && rold->off != rcur->off)
19685+
return false;
19686+
19687+
if (!check_scalar_ids(rold->id, rcur->id, idmap))
19688+
return false;
19689+
19690+
return range_within(rold, rcur) && tnum_in(rold->var_off, rcur->var_off);
1965219691
case PTR_TO_MAP_KEY:
1965319692
case PTR_TO_MAP_VALUE:
1965419693
case PTR_TO_MEM:

tools/testing/selftests/bpf/progs/verifier_scalar_ids.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -723,9 +723,9 @@ __success __log_level(2)
723723
/* The exit instruction should be reachable from two states,
724724
* use two matches and "processed .. insns" to ensure this.
725725
*/
726-
__msg("13: (95) exit")
727-
__msg("13: (95) exit")
728-
__msg("processed 18 insns")
726+
__msg("15: (95) exit")
727+
__msg("15: (95) exit")
728+
__msg("processed 20 insns")
729729
__flag(BPF_F_TEST_STATE_FREQ)
730730
__naked void two_old_ids_one_cur_id(void)
731731
{
@@ -734,9 +734,11 @@ __naked void two_old_ids_one_cur_id(void)
734734
"call %[bpf_ktime_get_ns];"
735735
"r0 &= 0xff;"
736736
"r6 = r0;"
737+
"r8 = r0;"
737738
"call %[bpf_ktime_get_ns];"
738739
"r0 &= 0xff;"
739740
"r7 = r0;"
741+
"r9 = r0;"
740742
"r0 = 0;"
741743
/* Maybe make r{6,7} IDs identical */
742744
"if r6 > r7 goto l0_%=;"

0 commit comments

Comments
 (0)