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.
next prev parent 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.