diff mbox series

[bpf,v1,2/2] selftests/bpf: Add raw_tp tests for PTR_MAYBE_NULL marking

Message ID 20241204024154.21386-3-memxor@gmail.com (mailing list archive)
State Superseded
Delegated to: BPF
Headers show
Series Fix for raw_tp PTR_MAYBE_NULL unmarking | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for bpf
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag present in non-next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 3 this patch: 3
netdev/build_tools success Errors and warnings before: 0 (+0) this patch: 0 (+0)
netdev/cc_maintainers warning 11 maintainers not CCed: sdf@fomichev.me haoluo@google.com kpsingh@kernel.org john.fastabend@gmail.com shuah@kernel.org martin.lau@linux.dev linux-kselftest@vger.kernel.org jolsa@kernel.org yonghong.song@linux.dev song@kernel.org mykolal@fb.com
netdev/build_clang success Errors and warnings before: 4 this patch: 4
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 8 this patch: 8
netdev/checkpatch warning CHECK: Lines should not end with a '(' WARNING: Avoid line continuations in quoted strings WARNING: added, moved or deleted file(s), does MAINTAINERS need updating? WARNING: line length of 93 exceeds 80 columns
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
bpf/vmtest-bpf-VM_Test-0 success Logs for Lint
bpf/vmtest-bpf-VM_Test-2 success Logs for Unittests
bpf/vmtest-bpf-VM_Test-1 success Logs for ShellCheck
bpf/vmtest-bpf-VM_Test-3 success Logs for Validate matrix.py
bpf/vmtest-bpf-VM_Test-5 success Logs for aarch64-gcc / build-release
bpf/vmtest-bpf-VM_Test-4 success Logs for aarch64-gcc / build / build for aarch64 with gcc
bpf/vmtest-bpf-VM_Test-6 fail Logs for aarch64-gcc / test (sched_ext, false, 360) / sched_ext on aarch64 with gcc
bpf/vmtest-bpf-VM_Test-11 success Logs for aarch64-gcc / veristat
bpf/vmtest-bpf-VM_Test-13 success Logs for s390x-gcc / build-release
bpf/vmtest-bpf-VM_Test-7 success Logs for aarch64-gcc / test (test_maps, false, 360) / test_maps on aarch64 with gcc
bpf/vmtest-bpf-VM_Test-10 success Logs for aarch64-gcc / test (test_verifier, false, 360) / test_verifier on aarch64 with gcc
bpf/vmtest-bpf-VM_Test-12 success Logs for s390x-gcc / build / build for s390x with gcc
bpf/vmtest-bpf-VM_Test-16 success Logs for s390x-gcc / test (test_verifier, false, 360) / test_verifier on s390x with gcc
bpf/vmtest-bpf-VM_Test-17 success Logs for s390x-gcc / veristat
bpf/vmtest-bpf-VM_Test-18 success Logs for set-matrix
bpf/vmtest-bpf-VM_Test-19 success Logs for x86_64-gcc / build / build for x86_64 with gcc
bpf/vmtest-bpf-VM_Test-20 success Logs for x86_64-gcc / build-release
bpf/vmtest-bpf-VM_Test-21 success Logs for x86_64-gcc / test (sched_ext, false, 360) / sched_ext on x86_64 with gcc
bpf/vmtest-bpf-VM_Test-22 fail Logs for x86_64-gcc / test (test_maps, false, 360) / test_maps on x86_64 with gcc
bpf/vmtest-bpf-VM_Test-24 success Logs for x86_64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with gcc
bpf/vmtest-bpf-VM_Test-23 success Logs for x86_64-gcc / test (test_progs, false, 360) / test_progs on x86_64 with gcc
bpf/vmtest-bpf-VM_Test-25 success Logs for x86_64-gcc / test (test_progs_no_alu32_parallel, true, 30) / test_progs_no_alu32_parallel on x86_64 with gcc
bpf/vmtest-bpf-VM_Test-26 success Logs for x86_64-gcc / test (test_progs_parallel, true, 30) / test_progs_parallel on x86_64 with gcc
bpf/vmtest-bpf-VM_Test-27 success Logs for x86_64-gcc / test (test_verifier, false, 360) / test_verifier on x86_64 with gcc
bpf/vmtest-bpf-VM_Test-28 success Logs for x86_64-gcc / veristat / veristat on x86_64 with gcc
bpf/vmtest-bpf-VM_Test-29 success Logs for x86_64-llvm-17 / build / build for x86_64 with llvm-17
bpf/vmtest-bpf-VM_Test-31 success Logs for x86_64-llvm-17 / test (sched_ext, false, 360) / sched_ext on x86_64 with llvm-17
bpf/vmtest-bpf-VM_Test-30 success Logs for x86_64-llvm-17 / build-release / build for x86_64 with llvm-17-O2
bpf/vmtest-bpf-VM_Test-32 success Logs for x86_64-llvm-17 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-17
bpf/vmtest-bpf-VM_Test-35 success Logs for x86_64-llvm-17 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-17
bpf/vmtest-bpf-VM_Test-37 success Logs for x86_64-llvm-18 / build / build for x86_64 with llvm-18
bpf/vmtest-bpf-VM_Test-36 success Logs for x86_64-llvm-17 / veristat
bpf/vmtest-bpf-VM_Test-38 success Logs for x86_64-llvm-18 / build-release / build for x86_64 with llvm-18-O2
bpf/vmtest-bpf-VM_Test-45 success Logs for x86_64-llvm-18 / veristat
bpf/vmtest-bpf-VM_Test-8 success Logs for aarch64-gcc / test (test_progs, false, 360) / test_progs on aarch64 with gcc
bpf/vmtest-bpf-VM_Test-9 success Logs for aarch64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on aarch64 with gcc
bpf/vmtest-bpf-VM_Test-33 success Logs for x86_64-llvm-17 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-17
bpf/vmtest-bpf-VM_Test-34 success Logs for x86_64-llvm-17 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-17
bpf/vmtest-bpf-VM_Test-39 success Logs for x86_64-llvm-18 / test (sched_ext, false, 360) / sched_ext on x86_64 with llvm-18
bpf/vmtest-bpf-VM_Test-40 success Logs for x86_64-llvm-18 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-18
bpf/vmtest-bpf-VM_Test-41 success Logs for x86_64-llvm-18 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-18
bpf/vmtest-bpf-VM_Test-42 success Logs for x86_64-llvm-18 / test (test_progs_cpuv4, false, 360) / test_progs_cpuv4 on x86_64 with llvm-18
bpf/vmtest-bpf-VM_Test-43 success Logs for x86_64-llvm-18 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-18
bpf/vmtest-bpf-VM_Test-44 success Logs for x86_64-llvm-18 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-18
bpf/vmtest-bpf-VM_Test-15 success Logs for s390x-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on s390x with gcc
bpf/vmtest-bpf-PR fail PR summary
bpf/vmtest-bpf-VM_Test-14 success Logs for s390x-gcc / test (test_progs, false, 360) / test_progs on s390x with gcc

Commit Message

Kumar Kartikeya Dwivedi Dec. 4, 2024, 2:41 a.m. UTC
Ensure that pointers with off != 0 are never unmarked as PTR_MAYBE_NULL,
pointers that have off == 0 continue getting unmarked, and pointers that
don't get unmarked acquire a new id instead of resetting it to zero, so
as to identify themselves uniquely and not hit other warnings where id
== 0 is not expected with PTR_MAYBE_NULL.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 .../selftests/bpf/prog_tests/raw_tp_null.c    |  6 ++
 .../selftests/bpf/progs/raw_tp_null_fail.c    | 81 +++++++++++++++++++
 2 files changed, 87 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/progs/raw_tp_null_fail.c

Comments

Eduard Zingerman Dec. 4, 2024, 8:12 p.m. UTC | #1
On Tue, 2024-12-03 at 18:41 -0800, Kumar Kartikeya Dwivedi wrote:

[...]

> +/* r2 with offset is checked, which marks r1 with off=0 as non-NULL */
> +SEC("tp_btf/bpf_testmod_test_raw_tp_null")
> +__failure
> +__msg("3: (07) r2 += 8                       ; R2_w=trusted_ptr_or_null_sk_buff(id=1,off=8)")
> +__msg("4: (15) if r2 == 0x0 goto pc+2        ; R2_w=trusted_ptr_or_null_sk_buff(id=2,off=8)")
> +__msg("5: (bf) r1 = r1                       ; R1_w=trusted_ptr_sk_buff()")

This looks like a bug.
'r1 != 0' does not follow from 'r2 == r1 + 8 and r2 != 0'.

> +int BPF_PROG(test_raw_tp_null_copy_check_with_off, struct sk_buff *skb)
> +{
> +	asm volatile (
> +		"r1 = *(u64 *)(r1 +0);			\
> +		 r2 = r1;				\
> +		 r3 = 0;				\
> +		 r2 += 8;				\
> +		 if r2 == 0 goto jmp2;			\
> +		 r1 = r1;				\
> +		 *(u64 *)(r3 +0) = r3;			\
> +		 jmp2:					"
> +		::
> +		: __clobber_all
> +	);
> +	return 0;
> +}

[...]
Kumar Kartikeya Dwivedi Dec. 4, 2024, 8:19 p.m. UTC | #2
On Wed, 4 Dec 2024 at 21:12, Eduard Zingerman <eddyz87@gmail.com> wrote:
>
> On Tue, 2024-12-03 at 18:41 -0800, Kumar Kartikeya Dwivedi wrote:
>
> [...]
>
> > +/* r2 with offset is checked, which marks r1 with off=0 as non-NULL */
> > +SEC("tp_btf/bpf_testmod_test_raw_tp_null")
> > +__failure
> > +__msg("3: (07) r2 += 8                       ; R2_w=trusted_ptr_or_null_sk_buff(id=1,off=8)")
> > +__msg("4: (15) if r2 == 0x0 goto pc+2        ; R2_w=trusted_ptr_or_null_sk_buff(id=2,off=8)")
> > +__msg("5: (bf) r1 = r1                       ; R1_w=trusted_ptr_sk_buff()")
>
> This looks like a bug.
> 'r1 != 0' does not follow from 'r2 == r1 + 8 and r2 != 0'.
>

Hmm, yes, it's broken.
I am realizing where we do it now will walk r1 first and we'll not see
r2 off != 0 until after we mark it already.
I guess we need to do the check sooner outside this function in
mark_ptr_or_null_regs.
There we have the register being operated on, so if off != 0 we don't
walk all regs in state.

Do you think that should fix this?

> > +int BPF_PROG(test_raw_tp_null_copy_check_with_off, struct sk_buff *skb)
> > +{
> > +     asm volatile (
> > +             "r1 = *(u64 *)(r1 +0);                  \
> > +              r2 = r1;                               \
> > +              r3 = 0;                                \
> > +              r2 += 8;                               \
> > +              if r2 == 0 goto jmp2;                  \
> > +              r1 = r1;                               \
> > +              *(u64 *)(r3 +0) = r3;                  \
> > +              jmp2:                                  "
> > +             ::
> > +             : __clobber_all
> > +     );
> > +     return 0;
> > +}
>
> [...]
Kumar Kartikeya Dwivedi Dec. 4, 2024, 8:22 p.m. UTC | #3
On Wed, 4 Dec 2024 at 21:19, Kumar Kartikeya Dwivedi <memxor@gmail.com> wrote:
>
> On Wed, 4 Dec 2024 at 21:12, Eduard Zingerman <eddyz87@gmail.com> wrote:
> >
> > On Tue, 2024-12-03 at 18:41 -0800, Kumar Kartikeya Dwivedi wrote:
> >
> > [...]
> >
> > > +/* r2 with offset is checked, which marks r1 with off=0 as non-NULL */
> > > +SEC("tp_btf/bpf_testmod_test_raw_tp_null")
> > > +__failure
> > > +__msg("3: (07) r2 += 8                       ; R2_w=trusted_ptr_or_null_sk_buff(id=1,off=8)")
> > > +__msg("4: (15) if r2 == 0x0 goto pc+2        ; R2_w=trusted_ptr_or_null_sk_buff(id=2,off=8)")
> > > +__msg("5: (bf) r1 = r1                       ; R1_w=trusted_ptr_sk_buff()")
> >
> > This looks like a bug.
> > 'r1 != 0' does not follow from 'r2 == r1 + 8 and r2 != 0'.
> >
>
> Hmm, yes, it's broken.
> I am realizing where we do it now will walk r1 first and we'll not see
> r2 off != 0 until after we mark it already.
> I guess we need to do the check sooner outside this function in
> mark_ptr_or_null_regs.
> There we have the register being operated on, so if off != 0 we don't
> walk all regs in state.

What this will do in both cases::
First, avoid walking states when off != 0, and reset id.
If off == 0, go inside mark_ptr_or_null_reg and walk all regs, and
remove marks for those with off != 0.

>
> Do you think that should fix this?
>
> > > +int BPF_PROG(test_raw_tp_null_copy_check_with_off, struct sk_buff *skb)
> > > +{
> > > +     asm volatile (
> > > +             "r1 = *(u64 *)(r1 +0);                  \
> > > +              r2 = r1;                               \
> > > +              r3 = 0;                                \
> > > +              r2 += 8;                               \
> > > +              if r2 == 0 goto jmp2;                  \
> > > +              r1 = r1;                               \
> > > +              *(u64 *)(r3 +0) = r3;                  \
> > > +              jmp2:                                  "
> > > +             ::
> > > +             : __clobber_all
> > > +     );
> > > +     return 0;
> > > +}
> >
> > [...]
Alexei Starovoitov Dec. 4, 2024, 8:40 p.m. UTC | #4
On Wed, Dec 4, 2024 at 12:22 PM Kumar Kartikeya Dwivedi
<memxor@gmail.com> wrote:
>
> On Wed, 4 Dec 2024 at 21:19, Kumar Kartikeya Dwivedi <memxor@gmail.com> wrote:
> >
> > On Wed, 4 Dec 2024 at 21:12, Eduard Zingerman <eddyz87@gmail.com> wrote:
> > >
> > > On Tue, 2024-12-03 at 18:41 -0800, Kumar Kartikeya Dwivedi wrote:
> > >
> > > [...]
> > >
> > > > +/* r2 with offset is checked, which marks r1 with off=0 as non-NULL */
> > > > +SEC("tp_btf/bpf_testmod_test_raw_tp_null")
> > > > +__failure
> > > > +__msg("3: (07) r2 += 8                       ; R2_w=trusted_ptr_or_null_sk_buff(id=1,off=8)")
> > > > +__msg("4: (15) if r2 == 0x0 goto pc+2        ; R2_w=trusted_ptr_or_null_sk_buff(id=2,off=8)")
> > > > +__msg("5: (bf) r1 = r1                       ; R1_w=trusted_ptr_sk_buff()")
> > >
> > > This looks like a bug.
> > > 'r1 != 0' does not follow from 'r2 == r1 + 8 and r2 != 0'.
> > >
> >
> > Hmm, yes, it's broken.
> > I am realizing where we do it now will walk r1 first and we'll not see
> > r2 off != 0 until after we mark it already.
> > I guess we need to do the check sooner outside this function in
> > mark_ptr_or_null_regs.
> > There we have the register being operated on, so if off != 0 we don't
> > walk all regs in state.
>
> What this will do in both cases::
> First, avoid walking states when off != 0, and reset id.
> If off == 0, go inside mark_ptr_or_null_reg and walk all regs, and
> remove marks for those with off != 0.

That's getting intrusive.
How about we reset id=0 in adjust_ptr_min_max_vals()
right after we suppressed "null-check it first" message for raw_tp-s.

That will address the issue as well, right?
Kumar Kartikeya Dwivedi Dec. 4, 2024, 8:48 p.m. UTC | #5
On Wed, 4 Dec 2024 at 21:40, Alexei Starovoitov
<alexei.starovoitov@gmail.com> wrote:
>
> On Wed, Dec 4, 2024 at 12:22 PM Kumar Kartikeya Dwivedi
> <memxor@gmail.com> wrote:
> >
> > On Wed, 4 Dec 2024 at 21:19, Kumar Kartikeya Dwivedi <memxor@gmail.com> wrote:
> > >
> > > On Wed, 4 Dec 2024 at 21:12, Eduard Zingerman <eddyz87@gmail.com> wrote:
> > > >
> > > > On Tue, 2024-12-03 at 18:41 -0800, Kumar Kartikeya Dwivedi wrote:
> > > >
> > > > [...]
> > > >
> > > > > +/* r2 with offset is checked, which marks r1 with off=0 as non-NULL */
> > > > > +SEC("tp_btf/bpf_testmod_test_raw_tp_null")
> > > > > +__failure
> > > > > +__msg("3: (07) r2 += 8                       ; R2_w=trusted_ptr_or_null_sk_buff(id=1,off=8)")
> > > > > +__msg("4: (15) if r2 == 0x0 goto pc+2        ; R2_w=trusted_ptr_or_null_sk_buff(id=2,off=8)")
> > > > > +__msg("5: (bf) r1 = r1                       ; R1_w=trusted_ptr_sk_buff()")
> > > >
> > > > This looks like a bug.
> > > > 'r1 != 0' does not follow from 'r2 == r1 + 8 and r2 != 0'.
> > > >
> > >
> > > Hmm, yes, it's broken.
> > > I am realizing where we do it now will walk r1 first and we'll not see
> > > r2 off != 0 until after we mark it already.
> > > I guess we need to do the check sooner outside this function in
> > > mark_ptr_or_null_regs.
> > > There we have the register being operated on, so if off != 0 we don't
> > > walk all regs in state.
> >
> > What this will do in both cases::
> > First, avoid walking states when off != 0, and reset id.
> > If off == 0, go inside mark_ptr_or_null_reg and walk all regs, and
> > remove marks for those with off != 0.
>
> That's getting intrusive.
> How about we reset id=0 in adjust_ptr_min_max_vals()
> right after we suppressed "null-check it first" message for raw_tp-s.
>
> That will address the issue as well, right?

Yes (minor detail, it needs to be reset to a new id, otherwise we have
warn on maybe_null set but !reg->id, but the idea is the same).
Let's see what Eduard thinks and then I can give it a go.
Eduard Zingerman Dec. 4, 2024, 9:08 p.m. UTC | #6
On Wed, 2024-12-04 at 21:48 +0100, Kumar Kartikeya Dwivedi wrote:

[...[

(A) ----.
        |
        v
> > > What this will do in both cases::
> > > First, avoid walking states when off != 0, and reset id.
> > > If off == 0, go inside mark_ptr_or_null_reg and walk all regs, and
> > > remove marks for those with off != 0.

(B) ----.
        |
        v
> > That's getting intrusive.
> > How about we reset id=0 in adjust_ptr_min_max_vals()
> > right after we suppressed "null-check it first" message for raw_tp-s.
> > 
> > That will address the issue as well, right?
> 
> Yes (minor detail, it needs to be reset to a new id, otherwise we have
> warn on maybe_null set but !reg->id, but the idea is the same).
> Let's see what Eduard thinks and then I can give it a go.

Sorry for delay.

I like what Kumar is proposing in (A) because it could be generalized:
there is no real harm in doing 'r2 = r1; r2 += 8; r1 != 0; ...'
and what Kumar suggests could be used to lift the "null-check it first ..."
restriction.

However, as far as I understand, the plan is to fix this by generating
two entry tracepoint states: one with parameter as null, another with
parameter not-null (all combinations for every parameter).
If that is the plan, what Alexei suggests in (B) is simpler.
Alexei Starovoitov Dec. 4, 2024, 9:13 p.m. UTC | #7
On Wed, Dec 4, 2024 at 1:08 PM Eduard Zingerman <eddyz87@gmail.com> wrote:
>
> On Wed, 2024-12-04 at 21:48 +0100, Kumar Kartikeya Dwivedi wrote:
>
> [...[
>
> (A) ----.
>         |
>         v
> > > > What this will do in both cases::
> > > > First, avoid walking states when off != 0, and reset id.
> > > > If off == 0, go inside mark_ptr_or_null_reg and walk all regs, and
> > > > remove marks for those with off != 0.
>
> (B) ----.
>         |
>         v
> > > That's getting intrusive.
> > > How about we reset id=0 in adjust_ptr_min_max_vals()
> > > right after we suppressed "null-check it first" message for raw_tp-s.
> > >
> > > That will address the issue as well, right?
> >
> > Yes (minor detail, it needs to be reset to a new id, otherwise we have
> > warn on maybe_null set but !reg->id, but the idea is the same).
> > Let's see what Eduard thinks and then I can give it a go.
>
> Sorry for delay.
>
> I like what Kumar is proposing in (A) because it could be generalized:
> there is no real harm in doing 'r2 = r1; r2 += 8; r1 != 0; ...'
> and what Kumar suggests could be used to lift the "null-check it first ..."
> restriction.

I don't see how it can be generalized.
Also 'avoid walking states when off != 0' is far from simple.
We call into mark_ptr_or_null_regs() with id == 0 already
and with reg->off != 0 for RCU and alloc_obj.

'avoid walking with off != 0' doesn't look trivial.
It would need to be special cased to raw_tp and some other
conditions.
I could be missing something.

Let's see how patches look.

> However, as far as I understand, the plan is to fix this by generating
> two entry tracepoint states: one with parameter as null, another with
> parameter not-null (all combinations for every parameter).
> If that is the plan, what Alexei suggests in (B) is simpler.
Eduard Zingerman Dec. 4, 2024, 9:37 p.m. UTC | #8
On Wed, 2024-12-04 at 13:13 -0800, Alexei Starovoitov wrote:
> On Wed, Dec 4, 2024 at 1:08 PM Eduard Zingerman <eddyz87@gmail.com> wrote:
> > 
> > On Wed, 2024-12-04 at 21:48 +0100, Kumar Kartikeya Dwivedi wrote:
> > 
> > [...[
> > 
> > (A) ----.
> >         |
> >         v
> > > > > What this will do in both cases::
> > > > > First, avoid walking states when off != 0, and reset id.
> > > > > If off == 0, go inside mark_ptr_or_null_reg and walk all regs, and
> > > > > remove marks for those with off != 0.
> > 
> > (B) ----.
> >         |
> >         v
> > > > That's getting intrusive.
> > > > How about we reset id=0 in adjust_ptr_min_max_vals()
> > > > right after we suppressed "null-check it first" message for raw_tp-s.
> > > > 
> > > > That will address the issue as well, right?
> > > 
> > > Yes (minor detail, it needs to be reset to a new id, otherwise we have
> > > warn on maybe_null set but !reg->id, but the idea is the same).
> > > Let's see what Eduard thinks and then I can give it a go.
> > 
> > Sorry for delay.
> > 
> > I like what Kumar is proposing in (A) because it could be generalized:
> > there is no real harm in doing 'r2 = r1; r2 += 8; r1 != 0; ...'
> > and what Kumar suggests could be used to lift the "null-check it first ..."
> > restriction.
> 
> I don't see how it can be generalized.
> Also 'avoid walking states when off != 0' is far from simple.
> We call into mark_ptr_or_null_regs() with id == 0 already
> and with reg->off != 0 for RCU and alloc_obj.

I did not try to implement this, so there might be a devil in the details.
The naive approach looks as below.

Suppose we want to allow 'rX += K' when rX is PTR_MAYBE_NULL.
Such operations generate a set of pointers with different .off values
but same .id .
For a regular (non raw_tp) case:
- dereferencing PTR_MAYBE_NULL is disallowed;
- if there is a check 'if rY != 0' and rY.off == 0,
  the non-null status could be propagated to each
  register in a set (and PTR_MAYBE_NULL mark removed);
- if there is a check 'if rY != 0' and rY.off != 0,
  nothing happens, no marks are changed.

For a raw_tp case:
- dereferencing PTR_MAYBE_NULL is allowed (as it is already);
- the mechanics for 'if rY != 0' and rY.off ==/!= 0 can remain the same,
  nothing is wrong with removing PTR_MAYBE_NULL marks from such pointers.

> 'avoid walking with off != 0' doesn't look trivial.
> It would need to be special cased to raw_tp and some other
> conditions.
> I could be missing something.
> 
> Let's see how patches look.
> 
> > However, as far as I understand, the plan is to fix this by generating
> > two entry tracepoint states: one with parameter as null, another with
> > parameter not-null (all combinations for every parameter).
> > If that is the plan, what Alexei suggests in (B) is simpler.
Kumar Kartikeya Dwivedi Dec. 4, 2024, 10:08 p.m. UTC | #9
On Wed, 4 Dec 2024 at 22:37, Eduard Zingerman <eddyz87@gmail.com> wrote:
>
> On Wed, 2024-12-04 at 13:13 -0800, Alexei Starovoitov wrote:
> > On Wed, Dec 4, 2024 at 1:08 PM Eduard Zingerman <eddyz87@gmail.com> wrote:
> > >
> > > On Wed, 2024-12-04 at 21:48 +0100, Kumar Kartikeya Dwivedi wrote:
> > >
> > > [...[
> > >
> > > (A) ----.
> > >         |
> > >         v
> > > > > > What this will do in both cases::
> > > > > > First, avoid walking states when off != 0, and reset id.
> > > > > > If off == 0, go inside mark_ptr_or_null_reg and walk all regs, and
> > > > > > remove marks for those with off != 0.
> > >
> > > (B) ----.
> > >         |
> > >         v
> > > > > That's getting intrusive.
> > > > > How about we reset id=0 in adjust_ptr_min_max_vals()
> > > > > right after we suppressed "null-check it first" message for raw_tp-s.
> > > > >
> > > > > That will address the issue as well, right?
> > > >
> > > > Yes (minor detail, it needs to be reset to a new id, otherwise we have
> > > > warn on maybe_null set but !reg->id, but the idea is the same).
> > > > Let's see what Eduard thinks and then I can give it a go.
> > >
> > > Sorry for delay.
> > >
> > > I like what Kumar is proposing in (A) because it could be generalized:
> > > there is no real harm in doing 'r2 = r1; r2 += 8; r1 != 0; ...'
> > > and what Kumar suggests could be used to lift the "null-check it first ..."
> > > restriction.
> >
> > I don't see how it can be generalized.
> > Also 'avoid walking states when off != 0' is far from simple.
> > We call into mark_ptr_or_null_regs() with id == 0 already
> > and with reg->off != 0 for RCU and alloc_obj.
>
> I did not try to implement this, so there might be a devil in the details.
> The naive approach looks as below.
>
> Suppose we want to allow 'rX += K' when rX is PTR_MAYBE_NULL.
> Such operations generate a set of pointers with different .off values
> but same .id .
> For a regular (non raw_tp) case:
> - dereferencing PTR_MAYBE_NULL is disallowed;
> - if there is a check 'if rY != 0' and rY.off == 0,
>   the non-null status could be propagated to each
>   register in a set (and PTR_MAYBE_NULL mark removed);
> - if there is a check 'if rY != 0' and rY.off != 0,
>   nothing happens, no marks are changed.

Yes, also I realized after some thinking that when rY with off != 0 is
checked, it just needs to be a no-op (in context of this solution), we
don't need to remove it from the set. Later if rX with off == 0
sharing the same id is checked rY should be marked not null.

>
> For a raw_tp case:
> - dereferencing PTR_MAYBE_NULL is allowed (as it is already);
> - the mechanics for 'if rY != 0' and rY.off ==/!= 0 can remain the same,
>   nothing is wrong with removing PTR_MAYBE_NULL marks from such pointers.

Yes, it can be generalized, this solution to generalize to all types
does not require the state forking approach, which is different.

It is getting late for me here so I will continue looking at this
tomorrow, but I can do this for raw_tp in this patch as a fix for the
warning.
Then, I can send a follow up doing it for all pointer types against
bpf-next where can continue discussion based on concrete code.

Alexei said he was giving the state forking a go in parallel (which is
much more involved and impact on veristat needs to be analyzed).

Anyhow, I will continue tomorrow. Let me know what you think.

>
> > 'avoid walking with off != 0' doesn't look trivial.
> > It would need to be special cased to raw_tp and some other
> > conditions.
> > I could be missing something.
> >
> > Let's see how patches look.
> >
> > > However, as far as I understand, the plan is to fix this by generating
> > > two entry tracepoint states: one with parameter as null, another with
> > > parameter not-null (all combinations for every parameter).
> > > If that is the plan, what Alexei suggests in (B) is simpler.
>
diff mbox series

Patch

diff --git a/tools/testing/selftests/bpf/prog_tests/raw_tp_null.c b/tools/testing/selftests/bpf/prog_tests/raw_tp_null.c
index 6fa19449297e..13fcd4c31034 100644
--- a/tools/testing/selftests/bpf/prog_tests/raw_tp_null.c
+++ b/tools/testing/selftests/bpf/prog_tests/raw_tp_null.c
@@ -3,6 +3,12 @@ 
 
 #include <test_progs.h>
 #include "raw_tp_null.skel.h"
+#include "raw_tp_null_fail.skel.h"
+
+void test_raw_tp_null_fail(void)
+{
+	RUN_TESTS(raw_tp_null_fail);
+}
 
 void test_raw_tp_null(void)
 {
diff --git a/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c b/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c
new file mode 100644
index 000000000000..12096150a48c
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/raw_tp_null_fail.c
@@ -0,0 +1,81 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
+
+#include <vmlinux.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+
+/* r1 with off = 0 is checked, which marks new id for r0 with off=8 */
+SEC("tp_btf/bpf_testmod_test_raw_tp_null")
+__failure
+__msg("2: (b7) r2 = 0                        ; R2_w=0")
+__msg("3: (07) r0 += 8                       ; R0_w=trusted_ptr_or_null_sk_buff(id=1,off=8)")
+__msg("4: (15) if r1 == 0x0 goto pc+2        ; R1_w=trusted_ptr_sk_buff()")
+__msg("5: (bf) r2 = r0                       ; R0_w=trusted_ptr_or_null_sk_buff(id=2,off=8)")
+int BPF_PROG(test_raw_tp_null_check_zero_off, struct sk_buff *skb)
+{
+	asm volatile (
+		"r1 = *(u64 *)(r1 +0);			\
+		 r0 = r1;				\
+		 r2 = 0;				\
+		 r0 += 8;				\
+		 if r1 == 0 goto jmp;			\
+		 r2 = r0;				\
+		 *(u64 *)(r2 +0) = r2;			\
+		 jmp:					"
+		::
+		: __clobber_all
+	);
+	return 0;
+}
+
+/* r2 with offset is checked, which marks r1 with off=0 as non-NULL */
+SEC("tp_btf/bpf_testmod_test_raw_tp_null")
+__failure
+__msg("3: (07) r2 += 8                       ; R2_w=trusted_ptr_or_null_sk_buff(id=1,off=8)")
+__msg("4: (15) if r2 == 0x0 goto pc+2        ; R2_w=trusted_ptr_or_null_sk_buff(id=2,off=8)")
+__msg("5: (bf) r1 = r1                       ; R1_w=trusted_ptr_sk_buff()")
+int BPF_PROG(test_raw_tp_null_copy_check_with_off, struct sk_buff *skb)
+{
+	asm volatile (
+		"r1 = *(u64 *)(r1 +0);			\
+		 r2 = r1;				\
+		 r3 = 0;				\
+		 r2 += 8;				\
+		 if r2 == 0 goto jmp2;			\
+		 r1 = r1;				\
+		 *(u64 *)(r3 +0) = r3;			\
+		 jmp2:					"
+		::
+		: __clobber_all
+	);
+	return 0;
+}
+
+/* Ensure id's are incremented everytime things are checked.. */
+SEC("tp_btf/bpf_testmod_test_raw_tp_null")
+__failure
+__msg("2: (07) r0 += 8                       ; R0_w=trusted_ptr_or_null_sk_buff(id=1,off=8)")
+__msg("3: (15) if r0 == 0x0 goto pc+4        ; R0_w=trusted_ptr_or_null_sk_buff(id=2,off=8)")
+__msg("4: (15) if r0 == 0x0 goto pc+3        ; R0_w=trusted_ptr_or_null_sk_buff(id=4,off=8)")
+__msg("5: (15) if r0 == 0x0 goto pc+2        ; R0_w=trusted_ptr_or_null_sk_buff(id=6,off=8)")
+__msg("6: (bf) r2 = r0                       ; R0_w=trusted_ptr_or_null_sk_buff(id=6,off=8)")
+int BPF_PROG(test_raw_tp_check_with_off, struct sk_buff *skb)
+{
+	asm volatile (
+		"r1 = *(u64 *)(r1 +0);			\
+		 r0 = r1;				\
+		 r0 += 8;				\
+		 if r0 == 0 goto jmp3;			\
+		 if r0 == 0 goto jmp3;			\
+		 if r0 == 0 goto jmp3;			\
+		 r2 = r0;				\
+		 *(u64 *)(r2 +0) = r2;			\
+		 jmp3:					"
+		::
+		: __clobber_all
+	);
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";