diff mbox series

[bpf,v2,7/8] bpf: consider that tail calls invalidate packet pointers

Message ID 20241210041100.1898468-8-eddyz87@gmail.com (mailing list archive)
State Accepted
Commit 1a4607ffba35bf2a630aab299e34dd3f6e658d70
Delegated to: BPF
Headers show
Series bpf: track changes_pkt_data property for global functions | expand

Checks

Context Check Description
bpf/vmtest-bpf-VM_Test-30 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 / build-release / build for x86_64 with llvm-17-O2
bpf/vmtest-bpf-VM_Test-36 success Logs for x86_64-llvm-17 / veristat-kernel
bpf/vmtest-bpf-VM_Test-37 success Logs for x86_64-llvm-17 / veristat-meta
bpf/vmtest-bpf-VM_Test-38 success Logs for x86_64-llvm-18 / build / build for x86_64 with llvm-18
bpf/vmtest-bpf-VM_Test-39 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-kernel
bpf/vmtest-bpf-VM_Test-46 success Logs for x86_64-llvm-18 / veristat-meta
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-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-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-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-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-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-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-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-0 success Logs for Lint
bpf/vmtest-bpf-VM_Test-1 success Logs for ShellCheck
bpf/vmtest-bpf-VM_Test-2 success Logs for Unittests
bpf/vmtest-bpf-VM_Test-3 success Logs for Validate matrix.py
bpf/vmtest-bpf-VM_Test-4 fail Logs for aarch64-gcc / build / build for aarch64 with gcc
bpf/vmtest-bpf-VM_Test-5 success Logs for aarch64-gcc / build-release
bpf/vmtest-bpf-VM_Test-6 success Logs for aarch64-gcc / test
bpf/vmtest-bpf-VM_Test-7 success Logs for aarch64-gcc / veristat-kernel
bpf/vmtest-bpf-VM_Test-8 success Logs for aarch64-gcc / veristat-meta
bpf/vmtest-bpf-VM_Test-9 fail Logs for s390x-gcc / build / build for s390x with gcc
bpf/vmtest-bpf-VM_Test-10 success Logs for s390x-gcc / build-release
bpf/vmtest-bpf-VM_Test-12 success Logs for s390x-gcc / veristat-kernel
bpf/vmtest-bpf-VM_Test-11 success Logs for s390x-gcc / test
bpf/vmtest-bpf-VM_Test-13 success Logs for s390x-gcc / veristat-meta
bpf/vmtest-bpf-VM_Test-14 success Logs for set-matrix
bpf/vmtest-bpf-VM_Test-15 fail Logs for x86_64-gcc / build / build for x86_64 with gcc
bpf/vmtest-bpf-VM_Test-16 success Logs for x86_64-gcc / build-release
bpf/vmtest-bpf-VM_Test-17 success Logs for x86_64-gcc / test
bpf/vmtest-bpf-VM_Test-18 success Logs for x86_64-gcc / veristat-kernel
bpf/vmtest-bpf-VM_Test-19 success Logs for x86_64-gcc / veristat-meta
bpf/vmtest-bpf-VM_Test-20 fail Logs for x86_64-llvm-17 / build / build for x86_64 with llvm-17
bpf/vmtest-bpf-VM_Test-21 fail Logs for x86_64-llvm-17 / build-release / build for x86_64 with llvm-17-O2
bpf/vmtest-bpf-VM_Test-22 success Logs for x86_64-llvm-17 / test
bpf/vmtest-bpf-VM_Test-23 success Logs for x86_64-llvm-17 / veristat-kernel
bpf/vmtest-bpf-VM_Test-24 success Logs for x86_64-llvm-17 / veristat-meta
bpf/vmtest-bpf-VM_Test-25 fail Logs for x86_64-llvm-18 / build / build for x86_64 with llvm-18
bpf/vmtest-bpf-VM_Test-26 fail Logs for x86_64-llvm-18 / build-release / build for x86_64 with llvm-18-O2
bpf/vmtest-bpf-VM_Test-27 success Logs for x86_64-llvm-18 / test
bpf/vmtest-bpf-VM_Test-28 success Logs for x86_64-llvm-18 / veristat-kernel
bpf/vmtest-bpf-VM_Test-29 success Logs for x86_64-llvm-18 / veristat-meta
bpf/vmtest-bpf-PR success PR summary

Commit Message

Eduard Zingerman Dec. 10, 2024, 4:10 a.m. UTC
Tail-called programs could execute any of the helpers that invalidate
packet pointers. Hence, conservatively assume that each tail call
invalidates packet pointers.

Making the change in bpf_helper_changes_pkt_data() automatically makes
use of check_cfg() logic that computes 'changes_pkt_data' effect for
global sub-programs, such that the following program could be
rejected:

    int tail_call(struct __sk_buff *sk)
    {
    	bpf_tail_call_static(sk, &jmp_table, 0);
    	return 0;
    }

    SEC("tc")
    int not_safe(struct __sk_buff *sk)
    {
    	int *p = (void *)(long)sk->data;
    	... make p valid ...
    	tail_call(sk);
    	*p = 42; /* this is unsafe */
    	...
    }

The tc_bpf2bpf.c:subprog_tc() needs change: mark it as a function that
can invalidate packet pointers. Otherwise, it can't be freplaced with
tailcall_freplace.c:entry_freplace() that does a tail call.

Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
---
 net/core/filter.c                              | 2 ++
 tools/testing/selftests/bpf/progs/tc_bpf2bpf.c | 2 ++
 2 files changed, 4 insertions(+)

Comments

Nick Zavaritsky Dec. 10, 2024, 10:35 a.m. UTC | #1
> Tail-called programs could execute any of the helpers that invalidate
> packet pointers. Hence, conservatively assume that each tail call
> invalidates packet pointers.

Tail calls look like a clear limitation of "auto-infer packet
invalidation effect" approach. Correct solution requires propagating
effects in the dynamic callee-caller graph, unlikely to ever happen.

I'm curious if assuming that every call to a global sub program
invalidates packet pointers might be an option. Does it break too many
programs in the wild?

From an end-user perspective, the presented solution makes debugging
verifier errors harder. An error message doesn't tell which call
invalidated pointers. Whether verifier considers a particular sub
program as pointer-invalidating is not revealed. I foresee exciting
debugging sessions.

It probably doesn't matter, but I don't like bpf_xdp_adjust_meta(xdp, 0)
hack to mark a program as pointer-invalidating either.

I would've preferred a simple static rule "calls to global sub programs
invalidate packet pointers" with an optional decl tag to mark a sub
program as non-invalidating, in line with "arg:nonnull".

> Making the change in bpf_helper_changes_pkt_data() automatically makes
> use of check_cfg() logic that computes 'changes_pkt_data' effect for
> global sub-programs, such that the following program could be
> rejected:
> 
>    int tail_call(struct __sk_buff *sk)
>    {
>     bpf_tail_call_static(sk, &jmp_table, 0);
>     return 0;
>    }
> 
>    SEC("tc")
>    int not_safe(struct __sk_buff *sk)
>    {
>     int *p = (void *)(long)sk->data;
>     ... make p valid ...
>     tail_call(sk);
>     *p = 42; /* this is unsafe */
>     ...
>    }
> 
> The tc_bpf2bpf.c:subprog_tc() needs change: mark it as a function that
> can invalidate packet pointers. Otherwise, it can't be freplaced with
> tailcall_freplace.c:entry_freplace() that does a tail call.
> 
> Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
> ---
> net/core/filter.c                              | 2 ++
> tools/testing/selftests/bpf/progs/tc_bpf2bpf.c | 2 ++
> 2 files changed, 4 insertions(+)
> 
> diff --git a/net/core/filter.c b/net/core/filter.c
> index efb75eed2e35..21131ec25f24 100644
> --- a/net/core/filter.c
> +++ b/net/core/filter.c
> @@ -7924,6 +7924,8 @@ bool bpf_helper_changes_pkt_data(enum bpf_func_id func_id)
> case BPF_FUNC_xdp_adjust_head:
> case BPF_FUNC_xdp_adjust_meta:
> case BPF_FUNC_xdp_adjust_tail:
> + /* tail-called program could call any of the above */
> + case BPF_FUNC_tail_call:
> return true;
> default:
> return false;
> diff --git a/tools/testing/selftests/bpf/progs/tc_bpf2bpf.c b/tools/testing/selftests/bpf/progs/tc_bpf2bpf.c
> index d1a57f7d09bd..fe6249d99b31 100644
> --- a/tools/testing/selftests/bpf/progs/tc_bpf2bpf.c
> +++ b/tools/testing/selftests/bpf/progs/tc_bpf2bpf.c
> @@ -11,6 +11,8 @@ int subprog_tc(struct __sk_buff *skb)
> 
> __sink(skb);
> __sink(ret);
> + /* let verifier know that 'subprog_tc' can change pointers to skb->data */
> + bpf_skb_change_proto(skb, 0, 0);
> return ret;
> }
> 
> -- 
> 2.47.0
>
Alexei Starovoitov Dec. 10, 2024, 6:23 p.m. UTC | #2
On Tue, Dec 10, 2024 at 2:35 AM Nick Zavaritsky <mejedi@gmail.com> wrote:
>
>
> > Tail-called programs could execute any of the helpers that invalidate
> > packet pointers. Hence, conservatively assume that each tail call
> > invalidates packet pointers.
>
> Tail calls look like a clear limitation of "auto-infer packet
> invalidation effect" approach. Correct solution requires propagating
> effects in the dynamic callee-caller graph, unlikely to ever happen.
>
> I'm curious if assuming that every call to a global sub program
> invalidates packet pointers might be an option. Does it break too many
> programs in the wild?

It might. Assuming every global prog changes pkt data is too risky,
also it would diverge global vs static verification even further,
which is a bad user experience.

> From an end-user perspective, the presented solution makes debugging
> verifier errors harder. An error message doesn't tell which call
> invalidated pointers. Whether verifier considers a particular sub
> program as pointer-invalidating is not revealed. I foresee exciting
> debugging sessions.

There is such a risk.

> It probably doesn't matter, but I don't like bpf_xdp_adjust_meta(xdp, 0)
> hack to mark a program as pointer-invalidating either.
>
> I would've preferred a simple static rule "calls to global sub programs
> invalidate packet pointers" with an optional decl tag to mark a sub
> program as non-invalidating, in line with "arg:nonnull".

That's not an option.
Eduard Zingerman Dec. 10, 2024, 6:29 p.m. UTC | #3
On Tue, 2024-12-10 at 10:23 -0800, Alexei Starovoitov wrote:
> On Tue, Dec 10, 2024 at 2:35 AM Nick Zavaritsky <mejedi@gmail.com> wrote:
> > 
> > 
> > > Tail-called programs could execute any of the helpers that invalidate
> > > packet pointers. Hence, conservatively assume that each tail call
> > > invalidates packet pointers.
> > 
> > Tail calls look like a clear limitation of "auto-infer packet
> > invalidation effect" approach. Correct solution requires propagating
> > effects in the dynamic callee-caller graph, unlikely to ever happen.
> > 
> > I'm curious if assuming that every call to a global sub program
> > invalidates packet pointers might be an option. Does it break too many
> > programs in the wild?
> 
> It might. Assuming every global prog changes pkt data is too risky,
> also it would diverge global vs static verification even further,
> which is a bad user experience.

I assume that freplace and tail calls are used much less often than
global calls. If so, I think that some degree of inference, even with
limitations, would be convenient more often than not.

> > From an end-user perspective, the presented solution makes debugging
> > verifier errors harder. An error message doesn't tell which call
> > invalidated pointers. Whether verifier considers a particular sub
> > program as pointer-invalidating is not revealed. I foresee exciting
> > debugging sessions.
> 
> There is such a risk.

I can do a v4 and add a line in the log each time the packet pointers
are invalidated. Such lines would be presented in verification failure
logs. (Can also print every register/stack slot where packet pointer
is invalidated, but this may be too verbose).

[...]
Alexei Starovoitov Dec. 10, 2024, 6:31 p.m. UTC | #4
On Tue, Dec 10, 2024 at 10:29 AM Eduard Zingerman <eddyz87@gmail.com> wrote:
>
> On Tue, 2024-12-10 at 10:23 -0800, Alexei Starovoitov wrote:
> > On Tue, Dec 10, 2024 at 2:35 AM Nick Zavaritsky <mejedi@gmail.com> wrote:
> > >
> > >
> > > > Tail-called programs could execute any of the helpers that invalidate
> > > > packet pointers. Hence, conservatively assume that each tail call
> > > > invalidates packet pointers.
> > >
> > > Tail calls look like a clear limitation of "auto-infer packet
> > > invalidation effect" approach. Correct solution requires propagating
> > > effects in the dynamic callee-caller graph, unlikely to ever happen.
> > >
> > > I'm curious if assuming that every call to a global sub program
> > > invalidates packet pointers might be an option. Does it break too many
> > > programs in the wild?
> >
> > It might. Assuming every global prog changes pkt data is too risky,
> > also it would diverge global vs static verification even further,
> > which is a bad user experience.
>
> I assume that freplace and tail calls are used much less often than
> global calls. If so, I think that some degree of inference, even with
> limitations, would be convenient more often than not.
>
> > > From an end-user perspective, the presented solution makes debugging
> > > verifier errors harder. An error message doesn't tell which call
> > > invalidated pointers. Whether verifier considers a particular sub
> > > program as pointer-invalidating is not revealed. I foresee exciting
> > > debugging sessions.
> >
> > There is such a risk.
>
> I can do a v4 and add a line in the log each time the packet pointers
> are invalidated. Such lines would be presented in verification failure
> logs. (Can also print every register/stack slot where packet pointer
> is invalidated, but this may be too verbose).

This is something to consider for bpf-next.
For bpf we need a minimal fix. So I applied as-is.
Eduard Zingerman Dec. 10, 2024, 6:52 p.m. UTC | #5
On Tue, 2024-12-10 at 10:31 -0800, Alexei Starovoitov wrote:

[...]

> > > > From an end-user perspective, the presented solution makes debugging
> > > > verifier errors harder. An error message doesn't tell which call
> > > > invalidated pointers. Whether verifier considers a particular sub
> > > > program as pointer-invalidating is not revealed. I foresee exciting
> > > > debugging sessions.
> > > 
> > > There is such a risk.
> > 
> > I can do a v4 and add a line in the log each time the packet pointers
> > are invalidated. Such lines would be presented in verification failure
> > logs. (Can also print every register/stack slot where packet pointer
> > is invalidated, but this may be too verbose).
> 
> This is something to consider for bpf-next.
> For bpf we need a minimal fix. So I applied as-is.

I must admit, I'm not familiar with the way bpf/bpf-next interact.
Should I wait for certain merges to happen before posting a patch
to bpf-next?
Alexei Starovoitov Dec. 10, 2024, 7 p.m. UTC | #6
On Tue, Dec 10, 2024 at 10:52 AM Eduard Zingerman <eddyz87@gmail.com> wrote:
>
> On Tue, 2024-12-10 at 10:31 -0800, Alexei Starovoitov wrote:
>
> [...]
>
> > > > > From an end-user perspective, the presented solution makes debugging
> > > > > verifier errors harder. An error message doesn't tell which call
> > > > > invalidated pointers. Whether verifier considers a particular sub
> > > > > program as pointer-invalidating is not revealed. I foresee exciting
> > > > > debugging sessions.
> > > >
> > > > There is such a risk.
> > >
> > > I can do a v4 and add a line in the log each time the packet pointers
> > > are invalidated. Such lines would be presented in verification failure
> > > logs. (Can also print every register/stack slot where packet pointer
> > > is invalidated, but this may be too verbose).
> >
> > This is something to consider for bpf-next.
> > For bpf we need a minimal fix. So I applied as-is.
>
> I must admit, I'm not familiar with the way bpf/bpf-next interact.
> Should I wait for certain merges to happen before posting a patch
> to bpf-next?

bpf tree is for fixes only.
We typically send PR every week.
Once it lands in Linus's tree we merge the fixes into bpf-next.
At that time follows up can be send targeting bpf-next.
Eduard Zingerman Dec. 10, 2024, 7:06 p.m. UTC | #7
On Tue, 2024-12-10 at 11:00 -0800, Alexei Starovoitov wrote:

[...]

> bpf tree is for fixes only.
> We typically send PR every week.
> Once it lands in Linus's tree we merge the fixes into bpf-next.
> At that time follows up can be send targeting bpf-next.

Will send next week, then.
Thank you.
diff mbox series

Patch

diff --git a/net/core/filter.c b/net/core/filter.c
index efb75eed2e35..21131ec25f24 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -7924,6 +7924,8 @@  bool bpf_helper_changes_pkt_data(enum bpf_func_id func_id)
 	case BPF_FUNC_xdp_adjust_head:
 	case BPF_FUNC_xdp_adjust_meta:
 	case BPF_FUNC_xdp_adjust_tail:
+	/* tail-called program could call any of the above */
+	case BPF_FUNC_tail_call:
 		return true;
 	default:
 		return false;
diff --git a/tools/testing/selftests/bpf/progs/tc_bpf2bpf.c b/tools/testing/selftests/bpf/progs/tc_bpf2bpf.c
index d1a57f7d09bd..fe6249d99b31 100644
--- a/tools/testing/selftests/bpf/progs/tc_bpf2bpf.c
+++ b/tools/testing/selftests/bpf/progs/tc_bpf2bpf.c
@@ -11,6 +11,8 @@  int subprog_tc(struct __sk_buff *skb)
 
 	__sink(skb);
 	__sink(ret);
+	/* let verifier know that 'subprog_tc' can change pointers to skb->data */
+	bpf_skb_change_proto(skb, 0, 0);
 	return ret;
 }