Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

bpf: use check_ids() for active_lock comparison

An update for verifier.c:states_equal()/regsafe() to use check_ids()
for active spin lock comparisons. This fixes the issue reported by
Kumar Kartikeya Dwivedi in [1] using technique suggested by Edward Cree.

W/o this commit the verifier might be tricked to accept the following
program working with a map containing spin locks:

0: r9 = map_lookup_elem(...) ; Returns PTR_TO_MAP_VALUE_OR_NULL id=1.
1: r8 = map_lookup_elem(...) ; Returns PTR_TO_MAP_VALUE_OR_NULL id=2.
2: if r9 == 0 goto exit ; r9 -> PTR_TO_MAP_VALUE.
3: if r8 == 0 goto exit ; r8 -> PTR_TO_MAP_VALUE.
4: r7 = ktime_get_ns() ; Unbound SCALAR_VALUE.
5: r6 = ktime_get_ns() ; Unbound SCALAR_VALUE.
6: bpf_spin_lock(r8) ; active_lock.id == 2.
7: if r6 > r7 goto +1 ; No new information about the state
; is derived from this check, thus
; produced verifier states differ only
; in 'insn_idx'.
8: r9 = r8 ; Optionally make r9.id == r8.id.
--- checkpoint --- ; Assume is_state_visisted() creates a
; checkpoint here.
9: bpf_spin_unlock(r9) ; (a,b) active_lock.id == 2.
; (a) r9.id == 2, (b) r9.id == 1.
10: exit(0)

Consider two verification paths:
(a) 0-10
(b) 0-7,9-10

The path (a) is verified first. If checkpoint is created at (8)
the (b) would assume that (8) is safe because regsafe() does not
compare register ids for registers of type PTR_TO_MAP_VALUE.

[1] https://lore.kernel.org/bpf/20221111202719.982118-1-memxor@gmail.com/

Reported-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Suggested-by: Edward Cree <ecree.xilinx@gmail.com>
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
Link: https://lore.kernel.org/r/20221209135733.28851-6-eddyz87@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>

authored by

Eduard Zingerman and committed by
Alexei Starovoitov
4ea2bb15 7d057943

+13 -3
+13 -3
kernel/bpf/verifier.c
··· 13119 13119 */ 13120 13120 return memcmp(rold, rcur, offsetof(struct bpf_reg_state, id)) == 0 && 13121 13121 range_within(rold, rcur) && 13122 - tnum_in(rold->var_off, rcur->var_off); 13122 + tnum_in(rold->var_off, rcur->var_off) && 13123 + check_ids(rold->id, rcur->id, idmap); 13123 13124 case PTR_TO_PACKET_META: 13124 13125 case PTR_TO_PACKET: 13125 13126 if (rcur->type != rold->type) ··· 13292 13291 if (old->speculative && !cur->speculative) 13293 13292 return false; 13294 13293 13295 - if (old->active_lock.ptr != cur->active_lock.ptr || 13296 - old->active_lock.id != cur->active_lock.id) 13294 + if (old->active_lock.ptr != cur->active_lock.ptr) 13295 + return false; 13296 + 13297 + /* Old and cur active_lock's have to be either both present 13298 + * or both absent. 13299 + */ 13300 + if (!!old->active_lock.id != !!cur->active_lock.id) 13301 + return false; 13302 + 13303 + if (old->active_lock.id && 13304 + !check_ids(old->active_lock.id, cur->active_lock.id, env->idmap_scratch)) 13297 13305 return false; 13298 13306 13299 13307 if (old->active_rcu_lock != cur->active_rcu_lock)