Re: [PATCH v1 bpf-next 8/8] selftest: bpf: Add test for BPF_SOCK_OPS_RCVLOWAT_CB.

Kuniyuki Iwashima <[email protected]> Fri, 8 May 2026 15:47:34 -0700
Newsgroups dev.linux.lists.sashiko,org.kernel.vger.bpf
Message-ID <CAAVpQUAwFPkS3rL_RtVgVy7PtzXtxCN=ZfkTn71Kb2-37D+sDg@mail.gmail.com>
On Fri, May 8, 2026 at 3:17 PM <[email protected]> wrote:
>
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [Medium] Flaky test design due to race condition between send() and epoll_wait()/getsockopt().
> - [Medium] Pipelined data causes a TOCTOU race that permanently breaks and disables the BPF autolowat program.
> --
>
> commit 7be0b88b0617982ff63886ecdcf680fd3a6f0db8
> Author: Kuniyuki Iwashima <[email protected]>
>
> selftest: bpf: Add test for BPF_SOCK_OPS_RCVLOWAT_CB.
>
> This commit adds a selftest simulating a production scenario where a BPF
> program dynamically adjusts a socket's SO_RCVLOWAT based on the parsing
> of RPC descriptors. The test exchanges partial and full RPC frames over
> a local connection and verifies that epoll wakes up the application at
> the correct boundaries.
>
> > diff --git a/tools/testing/selftests/bpf/prog_tests/tcp_autolowat.c b/tools/testing/selftests/bpf/prog_tests/tcp_autolowat.c
> > --- a/tools/testing/selftests/bpf/prog_tests/tcp_autolowat.c
> > +++ b/tools/testing/selftests/bpf/prog_tests/tcp_autolowat.c
>
> [ ... ]
>
> > @@ -0,0 +1,350 @@
> > +static void tcp_autolowat_run_rpc_test(struct tcp_autolowat_test_cb *cb,
> > +                                    struct rpc_test_case *test_case)
> > +{
>
> [ ... ]
>
> > +             switch (event->type) {
> > +             case RPC_EVENT_END:
> > +                     return;
> > +             case RPC_EVENT_AUTOLOWAT:
> > +                     err = setsockopt(cb->child, SOL_BPF, BPF_TCP_AUTOLOWAT,
> > +                                      &event->val, sizeof(event->val));
> > +                     if (!ASSERT_OK(err, "setsockopt"))
> > +                             return;
> > +                     break;
> > +             case RPC_EVENT_SEND:
> > +                     err = send(cb->client, ptr, event->len, 0);
> > +                     if (!ASSERT_EQ(err, event->len, "send"))
> > +                             return;
> > +
> > +                     ptr += event->len;
> > +                     break;
>
> [ ... ]
>
> > +             case RPC_EVENT_EPOLL:
> > +                     err = epoll_wait(cb->epoll, &ev, 1, 0);
> > +                     if (!ASSERT_EQ(err, event->nfds, "epoll_wait"))
> > +                             return;
> > +                     break;
> > +             case RPC_EVENT_RCVLOWAT:
> > +                     optval = 0;
> > +                     optlen = sizeof(optval);
> > +
> > +                     err = getsockopt(cb->child, SOL_SOCKET, SO_RCVLOWAT, &optval, &optlen);
> > +                     if (!ASSERT_OK(err, "getsockopt") ||
> > +                         !ASSERT_EQ(optval, event->rcvlowat, "rcvlowat"))
> > +                             return;
> > +                     break;
> > +             }
>
> Are the epoll and getsockopt checks vulnerable to flaky failures
> under load?
>
> Since TCP loopback delivery can be deferred to softirq context, could
> epoll_wait with a 0-millisecond timeout return 0 if the packet hasn't
> been queued yet?

No, it's over "lo", and TX/RX is in the same context.

>
> Similarly, could getsockopt read a stale SO_RCVLOWAT value before the
> BPF program gets a chance to update it?
>
> Would using a polling loop or a positive epoll timeout prevent
> intermittent test failures?
>
> > diff --git a/tools/testing/selftests/bpf/progs/tcp_autolowat.c b/tools/testing/selftests/bpf/progs/tcp_autolowat.c
> > --- a/tools/testing/selftests/bpf/progs/tcp_autolowat.c
> > +++ b/tools/testing/selftests/bpf/progs/tcp_autolowat.c
>
> [ ... ]
>
> > @@ -0,0 +1,316 @@
> > +static int tcp_parse_descriptor(struct bpf_sock_ops *skops,
> > +                             struct tcp_autolowat_cb *cb,
> > +                             u32 seq, u32 end_seq)
> > +{
> > +     struct rpc_descriptor *rpc_desc;
> > +     u32 rpc_copied_seq;
> > +     u32 copy_len;
> > +     u64 rpc_len;
> > +     int err;
> > +
> > +     rpc_copied_seq = cb->rpc_desc_seq + cb->rpc_desc_buff_len;
> > +
> > +     if (before(cb->rpc_desc_seq + RPC_DESC_SIZE, end_seq))
> > +             copy_len = RPC_DESC_SIZE - cb->rpc_desc_buff_len;
> > +     else
> > +             copy_len = end_seq - rpc_copied_seq;
> > +
> > +     /* Since LLVM commit 324e27e8bad83ca23a3cd276d7e2e729b1b0b8c7,
> > +      * clang omits the "copy_len == 0" check below, which is necessary
> > +      * to satisfy the BPF verifier's range check for bpf_skb_load_bytes().
> > +      */
> > +     barrier_var(copy_len);
> > +
> > +     if (copy_len == 0)
> > +             goto disable; /* FIN. */
> > +     if (copy_len > RPC_DESC_SIZE)
> > +             goto disable; /* always false, only for verifier. */
> > +     if (cb->rpc_desc_buf + cb->rpc_desc_buff_len >= &cb->rpc_desc_buf[RPC_DESC_SIZE])
> > +             goto disable; /* always false, only for verifier. */
> > +
> > +     err = bpf_skb_load_bytes(skops, rpc_copied_seq - seq,
> > +                              cb->rpc_desc_buf + cb->rpc_desc_buff_len, copy_len);
> > +     if (err)
> > +             goto disable;
>
> Is there a race condition if pipelined data is already in the receive
> queue when BPF_TCP_AUTOLOWAT is enabled?
>
> Looking at tcp_init_autolowat_cb(), cb->rpc_desc_seq is initialized to
> tp->copied_seq. If there is already unread data in the queue, the next
> packet arrival triggers BPF_SOCK_OPS_RCVLOWAT_CB.
>
> Because seq (which comes from tcb->seq) corresponds to the new packet
> and is greater than tp->copied_seq, will rpc_copied_seq - seq underflow?
>
> If it underflows to a large positive value, won't bpf_skb_load_bytes()
> fail with -EFAULT, causing the code to jump to the disable label and
> permanently disable the autolowat functionality for this socket?

This assumes the client will stop at a certain point; it will not
send the next request speculatively before receiving the previous
response.

In such an irregular case, we want to fall back to no autolowat,
so this is fine.