Re: [PATCH bpf-next v3 3/3] selftests/bpf: add test cases with CONST_PTR_TO_MAP null checks

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Fri, Jun 6, 2025 at 4:38 PM Ihor Solodrai <ihor.solodrai@xxxxxxxxx> wrote:
>
> On 6/5/25 5:25 PM, Alexei Starovoitov wrote:
> > On Thu, Jun 5, 2025 at 4:40 PM Ihor Solodrai <ihor.solodrai@xxxxxxxxx> wrote:
> >>
> >> On 6/5/25 10:00 AM, Alexei Starovoitov wrote:
> >>> On Thu, Jun 5, 2025 at 9:42 AM Ihor Solodrai <ihor.solodrai@xxxxxxxxx> wrote:
> >>>>
> >>>> On 6/5/25 9:24 AM, Andrii Nakryiko wrote:
> >>>>> On Wed, Jun 4, 2025 at 3:28 PM Ihor Solodrai <isolodrai@xxxxxxxx> wrote:
> >>>>>>
> >>>>>> [...]
> >>>>>>
> >>>>>> +SEC("socket")
> >>>>>> +int map_ptr_is_never_null(void *ctx)
> >>>>>> +{
> >>>>>> +       struct bpf_map *maps[2] = { 0 };
> >>>>>> +       struct bpf_map *map = NULL;
> >>>>>> +       int __attribute__((aligned(8))) key = 0;
> >>>>>
> >>>>> aligned(8) makes any difference?
> >>>>
> >>>> Yes. If not aligned (explicitly or by accident), verification fails
> >>>> with "math between fp pointer and register with unbounded min value is
> >>>> not allowed" at maps[key]. See the log below.
> >>>
> >>> It's not clear to me why "aligned" changes code gen,
> >>> but let's not rely on it.
> >>> Try 'unsigned int key' instead.
> >>> Also note that &key part pessimizes the code.
> >>> Do for (...; i < 2; i++) {u32 key = i; &key }
> >>> instead.
> >>
> >> I've tried changing the test like this:
> >>
> >> @@ -144,12 +144,12 @@ int map_ptr_is_never_null(void *ctx)
> >>    {
> >>           struct bpf_map *maps[2] = { 0 };
> >>           struct bpf_map *map = NULL;
> >> -       int __attribute__((aligned(8))) key = 0;
> >>
> >> -       for (key = 0; key < 2; key++) {
> >> +       for (int i = 0; i < 2; i++) {
> >> +               __u32 key = i;
> >>                   map = bpf_map_lookup_elem(&map_in_map, &key);
> >>                   if (map)
> >> -                       maps[key] = map;
> >> +                       maps[i] = map;
> >>                   else
> >>                           return 0;
> >>           }
> >>
> >> This version passes verification independent of the first patch being
> >> applied, which kinda defeats the purpose of the test. Verifier log
> >> below:
> >>
> >>       0: R1=ctx() R10=fp0
> >>       ; int map_ptr_is_never_null(void *ctx) @ verifier_map_in_map.c:143
> >>       0: (b4) w1 = 0                        ; R1_w=0
> >>       ; __u32 key = i; @ verifier_map_in_map.c:149
> >>       1: (63) *(u32 *)(r10 -4) = r1
> >>       mark_precise: frame0: last_idx 1 first_idx 0 subseq_idx -1
> >>       mark_precise: frame0: regs=r1 stack= before 0: (b4) w1 = 0
> >>       2: R1_w=0 R10=fp0 fp-8=0000????
> >>       2: (bf) r2 = r10                      ; R2_w=fp0 R10=fp0
> >>       3: (07) r2 += -4                      ; R2_w=fp-4
> >>       ; map = bpf_map_lookup_elem(&map_in_map, &key); @
> >> verifier_map_in_map.c:150
> >>       4: (18) r1 = 0xff302cd6802e0a00       ;
> >> R1_w=map_ptr(map=map_in_map,ks=4,vs=4)
> >>       6: (85) call bpf_map_lookup_elem#1    ;
> >> R0_w=map_value_or_null(id=1,map=map_in_map,ks=4,vs=4)
> >>       ; if (map) @ verifier_map_in_map.c:151
> >>       7: (15) if r0 == 0x0 goto pc+7        ; R0_w=map_ptr(ks=4,vs=4)
> >>       8: (b4) w1 = 1                        ; R1_w=1
> >>       ; __u32 key = i; @ verifier_map_in_map.c:149
> >>       9: (63) *(u32 *)(r10 -4) = r1         ; R1_w=1 R10=fp0 fp-8=mmmm????
> >>       10: (bf) r2 = r10                     ; R2_w=fp0 R10=fp0
> >>       11: (07) r2 += -4                     ; R2_w=fp-4
> >>       ; map = bpf_map_lookup_elem(&map_in_map, &key); @
> >> verifier_map_in_map.c:150
> >>       12: (18) r1 = 0xff302cd6802e0a00      ;
> >> R1_w=map_ptr(map=map_in_map,ks=4,vs=4)
> >>       14: (85) call bpf_map_lookup_elem#1   ;
> >> R0=map_value_or_null(id=2,map=map_in_map,ks=4,vs=4)
> >>       ; } @ verifier_map_in_map.c:164
> >>       15: (b4) w0 = 0                       ; R0_w=0
> >>       16: (95) exit
> >
> > because the compiler removed 'if (!maps[0])' check?
> > Make maps volatile then.
>
> Making maps and/or map volatile didn't help.
>
> >
> > It's not clear to me what the point of the 'for' loop is.
> > Just one bpf_map_lookup_elem() from map_in_map should do ?
>
> No. Using an array and loop was my attempt to put map_ptr on the
> stack. But apparently this was not the reason it happened in the v1 of
> the test.
>
> >
> >>
> >> If map[i] is changed back to map[key] like this:
> >>
> >>          for (int i = 0; i < 2; i++) {
> >>                  __u32 key = i;
> >>                  map = bpf_map_lookup_elem(&map_in_map, &key);
> >>                  if (map)
> >>                          maps[key] = map; /* change here */
> >>                  else
> >>                          return 0;
> >>          }
> >>
> >> Verification fails with "invalid unbounded variable-offset write to
> >> stack R2"
> >
> > as it should, because both the compiler and the verifier
> > see that 'key' is unbounded in maps[key] access.
> >
> >>       __u32 __attribute__((aligned(8))) key = i;
> >>
> >> but that puts us back to square one.
> >>
> >> It appears that alignment becomes a problem if the variable is used as
> >> array index and also it's address is passed to a helper.
> >
> > I bet this alignment "workaround" is fragile.
> > A different version of clang or gcc-bpf will change layout.
>
> I agree, it's fragile.
>
> After I fought compiler/verifier for a while I gave up and wrote a
> test in asm:
>
>      SEC("socket")
>      __description("map_ptr is never null")
>      __success
>      __naked void map_ptr_is_never_null(void)
>      {
>         asm volatile ("                                 \
>         r1 = 0;                                         \
>         *(u32*)(r10 - 4) = r1;                          \
>         r2 = r10;                                       \
>         r2 += -4;                                       \
>         r1 = %[map_in_map] ll;                          \
>         call %[bpf_map_lookup_elem];                    \
>         if r0 != 0 goto l0_%=;                          \
>         exit;                                           \
>      l0_%=:     *(u64 *)(r10 -16) = r0;                         \
>         r1 = *(u64 *)(r10 -16);                         \
>         if r1 == 0 goto l1_%=;                          \
>         exit;                                           \
>      l1_%=:     r10 = 42;                                       \
>         exit;                                           \
>      "  :
>         : __imm(bpf_map_lookup_elem),
>           __imm_addr(map_in_map)
>         : __clobber_all);
>      }
>
> What must happen to reproduce the situation is: map_ptr gets on a
> stack, and then loaded and compared to 0.
>
> It looks like I accidentally forced map_ptr on the stack by using
> `key` both for map lookup and array access, which triggers those
> alignment problems. Without that I wasn't able to figure out simple C
> code that would produce bpf with map_ptr on the stack (besides the
> other test, with rbs).
>
> I guess I should've written an asm test right away.

Yeah. For this kind of sequence asm is the right answer.
It's not clear to me why you want to spill/fill it.
CONST_PTR_TO_MAP is already in is_spillable_regtype()
before this patch.
The test needs to validate that reg_not_null() is working.
So just:

        r1 = %[map_in_map] ll;
        call %[bpf_map_lookup_elem];
        if r0 == 0 goto l0_%=;
        exit;
l0_%=: r10 = 42;
       exit;

would do the job without spill/fill.





[Index of Archives]     [Linux Samsung SoC]     [Linux Rockchip SoC]     [Linux Actions SoC]     [Linux for Synopsys ARC Processors]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]


  Powered by Linux