From: sashiko-bot@kernel.org
To: "Zhu Yanjun" <yanjun.zhu@linux.dev>, linux-rdma@vger.kernel.org
Subject: Re: [PATCH v3 1/2] selftests/rdma: Introduce eBPF kernel and user dump utilities for RXE
Date: Wed, 21 Oct 2026 20:46:26 +0000 [thread overview]
Message-ID: <sashiko-outbox-180647@kernel.org> (raw)
In-Reply-To: <20261021203115.54560-2-yanjun.zhu@linux.dev>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Medium] selftests/rdma: out-of-bounds read in print_bpf_output() packet parsing
- [Medium] selftests/rdma: invalid userspace header and build flags for BPF dump object
- [Medium] selftests/rdma: missing signal handling and stdout flush in main()
- [Low] selftests/rdma: conflicting __u64 typedef in rxe_pkg_user_dump.c
--
commit 812649f812ece426007a23045167b73c8aff9b63
Author: Zhu Yanjun <yanjun.zhu@linux.dev>
selftests/rdma: Introduce eBPF kernel and user dump utilities for RXE
This commit adds an eBPF program and a user-space ring-buffer consumer utility
to capture and dump Soft-RoCE (rxe) packets over netkit devices in the rdma
selftests.
> diff --git a/tools/testing/selftests/rdma/Makefile b/tools/testing/selftests/rdma/Makefile
> index a91c14c45006e..ba225e580787d 100644
> --- a/tools/testing/selftests/rdma/Makefile
> +++ b/tools/testing/selftests/rdma/Makefile
> @@ -4,6 +4,28 @@ TEST_PROGS := rxe_rping_between_netns.sh \
> rxe_socket_with_netns.sh \
> rxe_test_NETDEV_UNREGISTER.sh \
> rxe_sent_rcvd_bytes.sh \
> - rxe_netns_names.sh
> + rxe_netns_names.sh \
> + rxe_pkg_netkit.sh
>
> +TEST_GEN_PROGS_EXTENDED := rxe_netkit_dump
> include ../lib.mk
> +
> +CLANG ?= clang
> +LLC ?= llc
> +BPFTOOL ?= bpftool
> +ARCH ?= $(shell uname -m | sed 's/x86_64/x86/' | sed 's/aarch64/arm64/' | sed 's/ppc64le/powerpc/')
> +
> +INCLUDES := -I../../../../usr/include -I../../../../include/uapi -I../bpf -I$(OUTPUT)
> +CFLAGS += -g -Wall $(INCLUDES)
> +LDFLAGS += -lbpf -lelf -lz
> +
> +$(OUTPUT)/rxe_netkit_dump: rxe_pkg_user_dump.c $(OUTPUT)/rxe_pkg_kernel.skel.h
> + $(CC) $(CFLAGS) $< $(LDFLAGS) -o $@
> +
> +$(OUTPUT)/rxe_pkg_kernel_dump.o: rxe_pkg_kernel_dump.c
> + $(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(ARCH) $(INCLUDES) -c $< -o $@
> +
> +$(OUTPUT)/rxe_pkg_kernel.skel.h: $(OUTPUT)/rxe_pkg_kernel_dump.o
> + $(BPFTOOL) gen skeleton $< > $@
> +
> +EXTRA_CLEAN := $(OUTPUT)/rxe_pkg_kernel_dump.o $(OUTPUT)/rxe_pkg_kernel.skel.h
[ ... ]
> diff --git a/tools/testing/selftests/rdma/rxe_pkg_common.h b/tools/testing/selftests/rdma/rxe_pkg_common.h
> new file mode 100644
> index 0000000000000..0dca4147d5f9a
> --- /dev/null
> +++ b/tools/testing/selftests/rdma/rxe_pkg_common.h
> @@ -0,0 +1,17 @@
> +// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
> +/*
> + * Copyright (c) 2026 Zhu Yanjun <yanjun.zhu@linux.dev>
> + */
> +
> +// rxe_pkg_common.h
> +#ifndef RXE_PKG_COMMON_H
> +#define RXE_PKG_COMMON_H
> +
> +#define MAX_PKT_LEN 512
> +
> +struct pkg_event {
> + __u32 len;
> + __u8 data[MAX_PKT_LEN];
> +};
> +
> +#endif
> diff --git a/tools/testing/selftests/rdma/rxe_pkg_kernel_dump.c b/tools/testing/selftests/rdma/rxe_pkg_kernel_dump.c
> new file mode 100644
> index 0000000000000..f465b52e7366c
> --- /dev/null
> +++ b/tools/testing/selftests/rdma/rxe_pkg_kernel_dump.c
> @@ -0,0 +1,77 @@
> +// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
> +/*
> + * Copyright (c) 2026 Zhu Yanjun <yanjun.zhu@linux.dev>
> + */
> +
> +#include <linux/bpf.h>
> +#include <bpf/bpf_helpers.h>
> +#include <linux/if_ether.h>
> +#include <linux/ip.h>
> +#include <arpa/inet.h>
> +
> +#include "rxe_pkg_common.h"
> +
> +struct {
> + __uint(type, BPF_MAP_TYPE_RINGBUF);
> + __uint(max_entries, 256 * 1024);/* Shared by all CPUs */
> +} rb SEC(".maps");
> +
> +SEC("netkit/primary")
> +int handle_netkit_ingress(struct __sk_buff *skb)
> +{
> + /* Get the header pointer of skb */
> + void *data_end = (void *)(long)skb->data_end;
> + void *data = (void *)(long)skb->data;
> +
> + struct ethhdr *eth = data;
> +
> + /* Verify the data */
> + if (data + sizeof(*eth) > data_end)
> + return BPF_OK;
> +
> + /* Check if it is IP skb */
> + if (eth->h_proto == htons(ETH_P_IP)) {
[Severity: Medium]
Will compiling rxe_pkg_kernel_dump.c with -target bpf and including the
userspace <arpa/inet.h> header break the build or fail verification on
standard multiarch and 64-bit distributions?
When compiling with -target bpf in tools/testing/selftests/rdma/Makefile,
clang does not define host architecture macros such as __x86_64__. Including
glibc's <arpa/inet.h> pulls in <features.h> and <gnu/stubs.h>, which falls
back to <gnu/stubs-32.h> and fails on 64-bit hosts without 32-bit multilib
headers, or fails to find multiarch headers such as <sys/cdefs.h> and
<asm/types.h> without clang system include paths. In addition, calling
htons() instead of bpf_htons() from <bpf/bpf_endian.h> leaves an unresolved
external symbol call in handle_netkit_ingress() that libbpf and the bpf
verifier reject when loading the skeleton.
Also in tools/testing/selftests/rdma/Makefile, the build rules pass raw
unexported kernel headers via -I../../../../include/uapi, link against the
host -lbpf instead of building the in-tree tools/lib/bpf (which provides
bpf_program__attach_netkit()), omit rxe_pkg_common.h from the prerequisites,
and redirect bpftool output directly to $@ without cleaning up a partial
rxe_pkg_kernel.skel.h on failure. Should rxe_pkg_kernel_dump.c use
<bpf/bpf_endian.h> and bpf_htons(), and should the makefile build against
the in-tree libbpf and exported uapi headers?
> + struct iphdr *iph = data + sizeof(*eth);
> +
> + if ((void *)iph + sizeof(*iph) <= data_end) {
> + bpf_printk("Got IP packet from netkit ingress, %d\n", skb->len);
> +
> + __u32 len = skb->len;
> +
> + /* It is a rping test, the size of packets should be
> + * less than MAX_PKT_LEN
> + */
> + if (len == 0 || len > MAX_PKT_LEN) {
> + bpf_printk("len is not in 0..MAX_PKT_LEN\n");
> + return BPF_OK;
> + }
> +
> + struct pkg_event *e;
> +
> + /* The len is fixed to MAX_PKT_LEN */
> + e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0);
> +
> + if (!e) {
> + bpf_printk("bpf_ringbuf_reserve fails\n");
> + return BPF_OK;
> + }
> +
> + e->len = len;
> +
> + if (bpf_skb_load_bytes(skb, 0, e->data, len) < 0) {
> + bpf_ringbuf_discard(e, 0);
> + bpf_printk("bpf_skb_load_bytes fails\n");
> + return BPF_OK;
> + }
> +
> + bpf_ringbuf_submit(e, 0);
> + }
> + }
> +
> + /* BPF_OK: Pass
> + * BPF_DROP: Drop skb
> + */
> + return BPF_OK;
> +}
> +
> +char _license[] SEC("license") = "GPL";
> diff --git a/tools/testing/selftests/rdma/rxe_pkg_user_dump.c b/tools/testing/selftests/rdma/rxe_pkg_user_dump.c
> new file mode 100644
> index 0000000000000..0fe6d00c198e3
> --- /dev/null
> +++ b/tools/testing/selftests/rdma/rxe_pkg_user_dump.c
> @@ -0,0 +1,1342 @@
> +// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
> +/*
> + * Copyright (c) 2026 Zhu Yanjun <yanjun.zhu@linux.dev>
> + */
> +
> +#include <stdio.h>
> +#include <sys/resource.h>
> +#include <net/if.h>
> +#include "rxe_pkg_kernel.skel.h"
> +
> +#include <linux/ip.h>
> +#include <linux/udp.h>
> +#include <ctype.h>
> +
> +#include <net/ethernet.h>
> +#include <arpa/inet.h>
> +
> +#include "rxe_pkg_common.h"
> +
> +typedef unsigned char __u8;
> +typedef unsigned short __u16;
> +typedef unsigned int __u32;
> +typedef unsigned long long __u64;
[Severity: Low]
Does redefining __u64 in tools/testing/selftests/rdma/rxe_pkg_user_dump.c
cause a conflicting type error on ppc64le?
Because <linux/ip.h> and <linux/udp.h> are included above, <linux/types.h>
is already pulled in. On 64-bit powerpc (ppc64le, which the makefile
explicitly supports) as well as mips64 and alpha, the userspace uapi headers
use <asm-generic/int-l64.h> and define __u64 as unsigned long, conflicting
with this unsigned long long typedef. Could these duplicate __u8 through
__u64 typedefs be dropped in favor of <linux/types.h>?
[ ... ]
> +static void print_bpf_output(const void *data, __u32 size)
> +{
> + /* Validate size covers IP and UDP headers */
> + if (size < sizeof(struct iphdr) + sizeof(struct udphdr))
> + return;
> +
> + struct iphdr *iph = (struct iphdr *)data;
> +
> + if (iph->protocol != IPPROTO_UDP)
> + return;
> +
> + struct udphdr *udp_hdr = (struct udphdr *)(data + sizeof(struct iphdr));
> +
> + if (ntohs(udp_hdr->dest) != ROCEV2_PORT)
> + return;
> +
> + print_ipaddr(iph);
> + printf("udp hdr:\n");
> + print_udphdr(udp_hdr);
> +
> + size_t base_hdr_size = sizeof(struct iphdr) + sizeof(struct udphdr)
> + + sizeof(struct rxe_bth);
> +
> + if (size < base_hdr_size)
> + return;
> +
> + struct rxe_bth *bth = (struct rxe_bth *)(data + sizeof(struct iphdr)
> + + sizeof(struct udphdr));
> +
> + /* Validate opcode bounds and ensure name exists to prevent NULL dereference */
> + if (bth->opcode >= 256 || !rxe_opcode[bth->opcode].name) {
> + printf("Unknown or unpopulated opcode: 0x%x\n", bth->opcode);
> + return;
> + }
> +
> + printf("%s\n", rxe_opcode[bth->opcode].name);
> + print_bth(bth);
> +
> + for (int i = RXE_BTH + 1; i < NUM_HDR_TYPES; i++) {
> + int offset = rxe_opcode[bth->opcode].offset[i];
> +
> + if (offset && (size >= sizeof(struct iphdr) + sizeof(struct udphdr) + offset)) {
> + char *cur_p = (char *)bth + offset;
> +
> + switch (i) {
> + case RXE_RETH:
> + printf("RXE_RETH\n");
> + struct rxe_reth *reth = (struct rxe_reth *)cur_p;
> +
> + print_reth(reth);
> + break;
[ ... ]
> + case RXE_PAYLOAD:
> + printf("RXE_PAYLOAD\n");
> + int len = ntohs(udp_hdr->len) - rxe_opcode[bth->opcode].length;
> +
> + for (int j = 0; j < len; j++) {
> + if (isprint(cur_p[j]))
> + printf("%c", cur_p[j]);
> + else
> + printf("%02x", cur_p[j]);
> + }
> + printf("\n");
> + break;
> + default:
> + break;
> + }
> + }
> + }
> +
> + printf("\n");
> +}
> +
> +static int handle_event(void *ctx, void *data, unsigned long data_sz)
> +{
> + const struct pkg_event *e = data;
> +
> + if (data_sz < sizeof(*e))
> + return -1;
> +
> + print_bpf_output(e->data + sizeof(struct ethhdr),
> + e->len - sizeof(struct ethhdr));
> +
> + return 0;
> +}
[Severity: Medium]
Can handle_event() -> print_bpf_output() read uninitialized ring-buffer
memory and overflow past e->data[]?
In handle_netkit_ingress(), bpf_ringbuf_reserve() allocates a struct
pkg_event without zeroing e->data, and bpf_skb_load_bytes() only writes the
first e->len bytes. Then in handle_event(), e->len is not checked against
sizeof(struct ethhdr) or the capacity of e->data before subtracting
sizeof(struct ethhdr) and calling print_bpf_output().
Inside print_bpf_output(), there are several bounds issues:
1. The header loop check:
if (offset && (size >= sizeof(struct iphdr) + sizeof(struct udphdr) + offset))
only verifies that the start pointer cur_p = (char *)bth + offset is within
size, without checking that the full extended header struct at cur_p (such as
struct rxe_reth or struct rxe_atmeth) fits within size.
2. In the payload case:
int len = ntohs(udp_hdr->len) - rxe_opcode[bth->opcode].length;
ntohs(udp_hdr->len) includes the 8-byte struct udphdr as well as the 4-byte
rocev2 icrc (and any bth pad count), and len is never clamped to the
remaining captured bytes in size. Because sizeof(struct udphdr) is not
subtracted, cur_p + len always extends at least 8 bytes past data + size,
reading uninitialized ring-buffer memory on every packet and reading
out-of-bounds past struct pkg_event whenever e->len is between 505 and 512
(or up to 64 kilobytes past e->data if udp_hdr->len is larger than the
captured size).
3. In rxe_opcode[], header-only opcodes such as acknowledge and rdma read
request define a non-zero payload offset, so the 4-byte trailing icrc
satisfies size >= sizeof(struct iphdr) + sizeof(struct udphdr) + offset and
causes print_bpf_output() to print the icrc plus 8 uninitialized or
out-of-bounds bytes as payload.
4. Because cur_p is a signed char *, passing cur_p[j] with bit 7 set is
undefined behavior in isprint() and sign-extends to a negative int in
printf("%02x", cur_p[j]). Should cur_p be unsigned char * (or cast to
unsigned char)?
> +
> +int main(void)
> +{
[ ... ]
> + err = 0;
> +
> + while (1) {
> + err = ring_buffer__poll(ringbuf, 100 /* timeout */);
> + if (err < 0 && err != -EINTR) {
> + fprintf(stderr, "poll error\n");
> + break;
> + }
> + }
> +
> +cleanup:
> + ring_buffer__free(ringbuf);
> + rxe_pkg_kernel_dump__destroy(skel);
> + return err;
> +}
[Severity: Medium]
What happens when rxe_netkit_dump is run in the background with stdout
redirected to a file or pipe and is then stopped by a signal?
In main(), the while (1) loop around ring_buffer__poll() has no signal
handler for termination signals and only breaks when err < 0 && err != -EINTR,
making the cleanup label unreachable on normal termination. In addition,
print_bpf_output() writes via printf() without flushing stdout (or setting
line buffering via setvbuf()), so when stdout is redirected to a non-tty file
or pipe, stdio block-buffers the output and discards captured packet dumps
when the process is killed. Could main() install a signal handler to break
out of the poll loop and flush stdout after dumping each packet?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261021203115.54560-1-yanjun.zhu@linux.dev?part=1
next prev parent reply other threads:[~2026-10-21 20:46 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-21 20:31 [PATCH v3 0/2] selftests/rdma: Add an eBPF-based testcase using netkit for RXE Zhu Yanjun
2026-10-21 20:31 ` [PATCH v3 1/2] selftests/rdma: Introduce eBPF kernel and user dump utilities " Zhu Yanjun
2026-10-21 20:46 ` sashiko-bot [this message]
2026-10-21 20:31 ` [PATCH v3 2/2] selftests/rdma: Add netkit-based automated test script " Zhu Yanjun
2026-10-21 20:46 ` sashiko-bot
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=sashiko-outbox-180647@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=linux-rdma@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
--cc=yanjun.zhu@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.