Re: Packet pointer invalidation and subprograms

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

 



On Tue, 2024-12-03 at 12:19 -0800, Eduard Zingerman wrote:
> On Tue, 2024-12-03 at 17:26 +0100, Nick Zavaritsky wrote:
> > Hi,
> > 
> > Calls to helpers such as bpf_skb_pull_data, are supposed to invalidate
> > all prior checks on packet pointers.
> > 
> > I noticed that if I wrap a call to bpf_skb_pull_data in a function with
> > global linkage, pointers checked prior to the call are still considered
> > valid after the call. The program is accepted on 6.8 and 6.13-rc1.
> > 
> > I'm curious if it is by design and if not, if it is a known issue.
> > Please find the program below.
> > 
> > #include <linux/bpf.h>
> > #include <bpf/bpf_helpers.h>
> > 
> > __attribute__((__noinline__))
> > long skb_pull_data(struct __sk_buff *sk, __u32 len)
> > {
> >     return bpf_skb_pull_data(sk, len);
> > }
> > 
> > SEC("tc")
> > int test_invalidate_checks(struct __sk_buff *sk)
> > {
> >     int *p = (void *)(long)sk->data;
> >     if ((void *)(p + 1) > (void *)(long)sk->data_end) return TCX_DROP;
> >     skb_pull_data(sk, 0);
> >     *p = 42;
> >     return TCX_PASS;
> > }
> > 
> > If I remove noinline or add static, the program is rejected as expected.
> > 
> 
> Hi Nick,
> 
> Thank you for the report. This is a bug. Technically, packet pointers
> are invalidated by clear_all_pkt_pointers() called from check_helper_callf().
> This functions looks through all packets in current verifier state.
> However, global functions are verified independent of call sites,
> so pointer 'p' does not exist in verifier state when 'skb_pull_data'
> is verified, and thus is not invalidated.
> 

There are several ways to fix this:
- The "dumb" way:
  - forbid calling helpers that bpf_helper_changes_pkt_data()
    from global functions.
- The "simple" way:
  - at some early stage:
    - scan all global functions, to see if there are any calls to
      helpers that bpf_helper_changes_pkt_data(). If there are,
      remember this as an "effect" of the function;
    - build a call-graph of global functions and propagate computed
      effects over this call-graph (if A calls B and B does
      clear_all_pkt_pointers(), then A also does it).
  - during main verification phase, if a call to a global function is
    verified, check it's effects and update state accordingly
    (e.g. call clear_all_pkt_pointers()).
- The "correct" way:
  - build a call-graph of global functions;
  - verify these functions in a post-order;
  - while verifying, collect "effects" information
    (so far, the single effect is whether or not
     clear_all_pkt_pointers() had been ever called for the function);
  - if a call to global function is verified, check it's effects and
    update state accordingly (e.g. call clear_all_pkt_pointers()).

"dumb" is probably a no-go as it is too restrictive.
The only advantage of "simple" over "correct" that I see is
that the logic for clear_all_pkt_pointers() remains confined
to check_helper_call() and is not duplicated in a separate pass.
In theory, this also allows to compute more complex function effects
on the main verification pass.

I think "simple" is a way to go at the moment.

Alexei, Andrii, what do you think?






[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