selftests/bpf: Add test for scalar id on sign-extending stack fill

Add a verifier test where a spilled scalar is filled once via a sign-
extending load (BPF_MEMSX) and once via a zero-extending load (BPF_MEM).
The two destination registers must not share a scalar id.

  # LDLIBS=-static PKG_CONFIG='pkg-config --static' ./vmtest.sh -- ./test_progs -t verifier_scalar_ids
  [...]
  #643/1   verifier_scalar_ids/linked_regs_bpf_k:OK
  #643/2   verifier_scalar_ids/linked_regs_bpf_x_src:OK
  #643/3   verifier_scalar_ids/linked_regs_bpf_x_dst:OK
  #643/4   verifier_scalar_ids/linked_regs_broken_link:OK
  #643/5   verifier_scalar_ids/precision_many_frames:OK
  #643/6   verifier_scalar_ids/precision_stack:OK
  #643/7   verifier_scalar_ids/precision_two_ids:OK
  #643/8   verifier_scalar_ids/linked_regs_too_many_regs:OK
  #643/9   verifier_scalar_ids/linked_regs_broken_link_2:OK
  #643/10  verifier_scalar_ids/cjmp_no_linked_regs_trigger:OK
  #643/11  verifier_scalar_ids/check_ids_in_regsafe:OK
  #643/12  verifier_scalar_ids/check_ids_in_regsafe_2:OK
  #643/13  verifier_scalar_ids/no_scalar_id_for_const:OK
  #643/14  verifier_scalar_ids/no_scalar_id_for_const32:OK
  #643/15  verifier_scalar_ids/ignore_unique_scalar_ids_cur:OK
  #643/16  verifier_scalar_ids/ignore_unique_scalar_ids_old:OK
  #643/17  verifier_scalar_ids/two_nil_old_ids_one_cur_id:OK
  #643/18  verifier_scalar_ids/two_old_ids_one_cur_id:OK
  #643/19  verifier_scalar_ids/linked_regs_and_subreg_def:OK
  #643/20  verifier_scalar_ids/ldsx_fill_scalar_id_not_shared:OK
  #643     verifier_scalar_ids:OK
  Summary: 1/20 PASSED, 0 SKIPPED, 0 FAILED

Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
This commit is contained in:
Daniel Borkmann 2026-07-09 17:31:31 +02:00 committed by Eduard Zingerman
parent 2cb5f4ca69
commit c3d5ef291a

View File

@ -4,6 +4,13 @@
#include <bpf/bpf_helpers.h>
#include "bpf_misc.h"
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1);
__type(key, long long);
__type(value, long long);
} map_hash_8b SEC(".maps");
/* Check that precision marks propagate through scalar IDs.
* Registers r{0,1,2} have the same scalar ID.
* Range information is propagated for scalars sharing same ID.
@ -915,4 +922,53 @@ __naked void linked_regs_and_subreg_def(void)
: __clobber_all);
}
/*
* A scalar is spilled to the stack and then filled twice: once via a
* sign-extending load (BPF_MEMSX) into r4 and once via a zero-extending
* load (BPF_MEM) into r5. coerce_reg_to_size_sx() gives r4 a different
* value than the spilled/zero-extended siblings, so r4 must not keep the
* shared scalar id. Otherwise the later 'if r5 == 0x80000000' refines r4
* through sync_linked_regs() to a known 0x80000000, while at runtime r4
* is the sign-extended 0xffffffff80000000. The test turns that discrepancy
* into an out-of-bounds map value access (r4 >> 63 is believed 0 but is 1
* at runtime), which must be rejected.
*/
SEC("socket")
__failure __msg("R0 max value is outside of the allowed memory range")
__naked void ldsx_fill_scalar_id_not_shared(void)
{
asm volatile (" \
r1 = 0; \
*(u64*)(r10 - 8) = r1; \
r2 = r10; \
r2 += -8; \
r1 = %[map_hash_8b] ll; \
call %[bpf_map_lookup_elem]; \
if r0 == 0 goto l0_%=; \
/* r7 = unknown u32, keep only bit 31 */ \
r7 = *(u32*)(r0 + 0); \
r2 = 0x80000000 ll; \
r7 &= r2; \
/* link r6 and r7 via a fresh scalar id */ \
r6 = r7; \
/* spill r7 (u32) to the stack */ \
*(u32*)(r10 - 8) = r7; \
/* sign-extending fill: must drop the id */ \
r4 = *(s32*)(r10 - 8); \
/* zero-extending fill: keeps the id */ \
r5 = *(u32*)(r10 - 8); \
/* r5 becomes known 0x80000000 on fall-through */\
if r5 != r2 goto l0_%=; \
/* verifier believes r4 == 0 here, runtime is 1 */\
r4 >>= 63; \
r0 += r4; \
r0 = *(u8*)(r0 + 7); \
l0_%=: r0 = 0; \
exit; \
" :
: __imm(bpf_map_lookup_elem),
__imm_addr(map_hash_8b)
: __clobber_all);
}
char _license[] SEC("license") = "GPL";