bpf.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Ihor Solodrai <ihor.solodrai@linux.dev>
To: Alexei Starovoitov <alexei.starovoitov@gmail.com>
Cc: Andrii Nakryiko <andrii.nakryiko@gmail.com>,
	Andrii Nakryiko <andrii@kernel.org>, bpf <bpf@vger.kernel.org>,
	Alexei Starovoitov <ast@kernel.org>,
	Daniel Borkmann <daniel@iogearbox.net>,
	Eduard <eddyz87@gmail.com>, Mykola Lysenko <mykolal@fb.com>,
	Yonghong Song <yonghong.song@linux.dev>,
	Kernel Team <kernel-team@meta.com>
Subject: Re: [PATCH bpf-next v3 3/3] selftests/bpf: add test cases with CONST_PTR_TO_MAP null checks
Date: Fri, 6 Jun 2025 16:37:42 -0700	[thread overview]
Message-ID: <3e0d243a-f769-464c-ab58-49e94e52611d@linux.dev> (raw)
In-Reply-To: <CAADnVQKwekQ61umAokC09OB+ao5T4E_yg_cLgzVEUtCtFwo=0Q@mail.gmail.com>

On 6/5/25 5:25 PM, Alexei Starovoitov wrote:
> On Thu, Jun 5, 2025 at 4:40 PM Ihor Solodrai <ihor.solodrai@linux.dev> wrote:
>>
>> On 6/5/25 10:00 AM, Alexei Starovoitov wrote:
>>> On Thu, Jun 5, 2025 at 9:42 AM Ihor Solodrai <ihor.solodrai@linux.dev> wrote:
>>>>
>>>> On 6/5/25 9:24 AM, Andrii Nakryiko wrote:
>>>>> On Wed, Jun 4, 2025 at 3:28 PM Ihor Solodrai <isolodrai@meta.com> 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.


  reply	other threads:[~2025-06-06 23:37 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-06-04 22:27 [PATCH bpf-next v3 1/3] bpf: make reg_not_null() true for CONST_PTR_TO_MAP Ihor Solodrai
2025-06-04 22:27 ` [PATCH bpf-next v3 2/3] selftests/bpf: add cmp_map_pointer_with_const test Ihor Solodrai
2025-06-04 22:41   ` Alexei Starovoitov
2025-06-05  3:04     ` Ihor Solodrai
2025-06-05 16:08       ` Alexei Starovoitov
2025-06-05 17:17         ` Ihor Solodrai
2025-06-05 17:42           ` Yonghong Song
2025-06-05 18:11             ` Alexei Starovoitov
2025-06-06  6:24               ` Yonghong Song
2025-06-05 16:20   ` Andrii Nakryiko
2025-06-05 16:30     ` Ihor Solodrai
2025-06-04 22:27 ` [PATCH bpf-next v3 3/3] selftests/bpf: add test cases with CONST_PTR_TO_MAP null checks Ihor Solodrai
2025-06-05 16:24   ` Andrii Nakryiko
2025-06-05 16:42     ` Ihor Solodrai
2025-06-05 17:00       ` Alexei Starovoitov
2025-06-05 23:40         ` Ihor Solodrai
2025-06-06  0:25           ` Alexei Starovoitov
2025-06-06 23:37             ` Ihor Solodrai [this message]
2025-06-06 23:52               ` Alexei Starovoitov
2025-06-07 14:07                 ` Alexei Starovoitov
2025-06-05 16:27 ` [PATCH bpf-next v3 1/3] bpf: make reg_not_null() true for CONST_PTR_TO_MAP Andrii Nakryiko

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=3e0d243a-f769-464c-ab58-49e94e52611d@linux.dev \
    --to=ihor.solodrai@linux.dev \
    --cc=alexei.starovoitov@gmail.com \
    --cc=andrii.nakryiko@gmail.com \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=eddyz87@gmail.com \
    --cc=kernel-team@meta.com \
    --cc=mykolal@fb.com \
    --cc=yonghong.song@linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).