All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Zhu Yanjun" <yanjun.zhu@linux.dev>, linux-rdma@vger.kernel.org
Subject: Re: [PATCH v2 1/2] selftests/rdma: Introduce eBPF kernel and user dump utilities for RXE
Date: Mon, 19 Oct 2026 21:53:32 +0000	[thread overview]
Message-ID: <sashiko-outbox-178279@kernel.org> (raw)
In-Reply-To: <20261019213821.44842-2-yanjun.zhu@linux.dev>

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Medium] selftests/rdma: uninit ringbuf read and OOB payload dump in print_bpf_output()
- [Medium] selftests/rdma: missing packed attribute on struct rxe_atmeth
- [Medium] selftests/rdma: main() in rxe_pkg_user_dump.c masks errors by returning 0
- [Low] selftests/rdma: broken include paths and system bpf deps in Makefile
--

commit cad47e7d3ec5a1fbd0772ac7d820b7588ad1671d
Author: Zhu Yanjun <yanjun.zhu@linux.dev>

selftests/rdma: Introduce eBPF kernel and user dump utilities for RXE

This commit adds eBPF-based kernel and user-space packet dump utilities
(rxe_pkg_kernel_dump.c and rxe_pkg_user_dump.c) to tools/testing/selftests/rdma/
to capture and parse Soft-RoCE (RXE) traffic over netkit devices.

> 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 $< > $@

[Severity: Low]
Will these build rules work for out-of-tree builds or on systems without a
recent system-wide libbpf and bpftool installed?

There appear to be a few issues with the build configuration here:

1. INCLUDES hardcodes -I../../../../usr/include and -I../../../../include/uapi
   instead of using $(KHDR_INCLUDES) from ../lib.mk. In out-of-tree builds
   (make O=...), exported headers are placed under $(O)/usr/include rather
   than ../../../../usr/include. Falling back to raw unexported kernel headers
   in ../../../../include/uapi lacks generated asm headers, triggers the
   "Attempt to use kernel headers from user space" warning in
   include/uapi/linux/types.h, and uses raw _UAPI_* header guards that
   conflict with glibc headers such as net/ethernet.h. In addition, invoking
   $(CLANG) -target bpf without host multiarch system include paths (such as
   get_sys_includes in tools/testing/selftests/net/bpf.mk) can fail to locate
   architecture headers like sys/cdefs.h, gnu/stubs.h, and asm/types.h.

2. The Makefile links against a system-installed -lbpf and invokes a system
   bpftool instead of building the in-tree tools/lib/bpf and
   tools/bpf/bpftool (as done in tools/testing/selftests/net/bpf.mk and
   tools/testing/selftests/hid/Makefile). Note that -I../bpf does not provide
   bpf/bpf_helpers.h or bpf/libbpf.h, and rxe_pkg_user_dump.c requires
   recent libbpf support for bpf_program__attach_netkit() and
   SEC("netkit/primary").

3. The rule for $(OUTPUT)/rxe_pkg_kernel.skel.h redirects stdout directly to
   $@ without .DELETE_ON_ERROR or temporary file cleanup on failure, so a
   missing or failing bpftool leaves behind an empty header file that
   subsequent make invocations treat as up to date.

> +
> +EXTRA_CLEAN := $(OUTPUT)/rxe_pkg_kernel_dump.o $(OUTPUT)/rxe_pkg_kernel.skel.h
> 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..7227fc4a6f57e
> --- /dev/null
> +++ b/tools/testing/selftests/rdma/rxe_pkg_kernel_dump.c

[ ... ]

> +SEC("netkit/primary")
> +int handle_netkit_ingress(struct __sk_buff *skb)
> +{

[ ... ]

> +			__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;
> +			}
> +
> +			/* The len is fixed to MAX_PKT_LEN */
> +			void *reserve = bpf_ringbuf_reserve(&rb, MAX_PKT_LEN, 0);
> +
> +			if (!reserve) {
> +				bpf_printk("bpf_ringbuf_reserve fails\n");
> +				return BPF_OK;
> +			}
> +
> +			if (bpf_skb_load_bytes(skb, 0, reserve, len) < 0) {
> +				bpf_ringbuf_discard(reserve, 0);
> +				bpf_printk("bpf_skb_load_bytes fails\n");
> +				return BPF_OK;
> +			}
> +
> +			bpf_ringbuf_submit(reserve, 0);

[Severity: Medium]
In handle_netkit_ingress(), bpf_ringbuf_reserve() always reserves MAX_PKT_LEN
(512) bytes, while bpf_skb_load_bytes() only writes len (skb->len) bytes into
reserve without zeroing the trailing MAX_PKT_LEN - len bytes or passing the
actual captured packet length to user space.

When handle_event() in rxe_pkg_user_dump.c receives this record, data_sz is
always 512 rather than skb->len, so print_bpf_output() cannot tell how many
bytes were actually initialized by bpf_skb_load_bytes() (see the comment on
print_bpf_output() below).

> +		}
> +	}
> +
> +	/* 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..b98d77b1dcf2c
> --- /dev/null
> +++ b/tools/testing/selftests/rdma/rxe_pkg_user_dump.c

[ ... ]

> +/******************************************************************************
> + * Atomic Extended Transport Header
> + ******************************************************************************/
> +struct rxe_atmeth {
> +	__be64			va;
> +	__be32			rkey;
> +	__be64			swap_add;
> +	__be64			comp;
> +};

[Severity: Medium]
Should struct rxe_atmeth be marked with __attribute__((packed))?

In drivers/infiniband/sw/rxe/rxe_hdr.h, struct rxe_atmeth is defined with
__packed so that va (offset 0), rkey (offset 8), swap_add (offset 12), and
comp (offset 20) match the 28-byte RoCEv2 wire layout. Even print_atmeth()
below prints "} __packed;\n".

Without the packed attribute here, the compiler inserts 4 bytes of alignment
padding between rkey and swap_add, increasing RXE_ATMETH_BYTES from 28 to 32
bytes, shifting swap_add and comp by 4 bytes in print_atmeth(), and
miscalculating the header lengths and RXE_PAYLOAD offsets for
IB_OPCODE_RC_COMPARE_SWAP, IB_OPCODE_RC_FETCH_ADD, IB_OPCODE_RD_COMPARE_SWAP,
and IB_OPCODE_RD_FETCH_ADD.

[ ... ]

> +static void print_bpf_output(void *data, __u32 size)
> +{

[ ... ]

> +	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_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)
> +{
> +	print_bpf_output(data+sizeof(struct ethhdr),
> +			 data_sz-sizeof(struct ethhdr));
> +	return 0;
> +}

[Severity: Medium]
Can print_bpf_output() read uninitialized ring buffer memory or read out of
bounds past the end of data[] when dumping headers and RXE_PAYLOAD?

Looking at handle_event() -> print_bpf_output():

1. Because handle_netkit_ingress() reserves a fixed 512-byte ring buffer
   entry without passing skb->len, size is always 498 (512 - sizeof(struct
   ethhdr)) rather than the captured packet length. Furthermore, the loop
   condition (size >= sizeof(struct iphdr) + sizeof(struct udphdr) + offset)
   only checks that the start offset is within size, not that the full
   extended header (offset + sizeof(header)) fits within the captured packet.

2. Under case RXE_PAYLOAD, len is computed as:

       int len = ntohs(udp_hdr->len) - rxe_opcode[bth->opcode].length;

   udp_hdr->len includes sizeof(struct udphdr) (8 bytes), the RoCE transport
   headers, the payload, BTH padding (0..3 bytes), and the 4-byte RoCEv2
   ICRC. Because sizeof(struct udphdr), padding, and the 4-byte ICRC are not
   subtracted, len is at least 12 bytes larger than the actual payload (and
   8 bytes larger than the entire UDP datagram). In addition,
   rxe_opcode[bth->opcode].offset[RXE_PAYLOAD] is populated even for
   header-only opcodes such as IB_OPCODE_RC_ACKNOWLEDGE and
   IB_OPCODE_RC_RDMA_READ_REQUEST, so RXE_PAYLOAD is entered unconditionally.

3. Because len is never clamped to the remaining bytes in data[], cur_p[j]
   reads at least 8 bytes of uninitialized ring buffer memory past the end of
   every packet shorter than 512 bytes, and reads out of bounds past the end
   of the 512-byte ring buffer record when a packet is 505..512 bytes long
   (or if udp_hdr->len is larger than the buffer).

4. cur_p is a signed char *, so passing cur_p[j] directly to isprint() and
   printf("%02x", cur_p[j]) without casting to unsigned char invokes
   undefined behavior in isprint() for bytes >= 0x80 and sign-extends them to
   8 hex digits.

> +
> +int main(void)
> +{
> +	struct rxe_pkg_kernel_dump *skel;
> +	int err = 1;
> +	struct ring_buffer *ringbuf = NULL;
> +	struct rlimit rlim = { .rlim_cur = RLIM_INFINITY, .rlim_max = RLIM_INFINITY };
> +
> +	if (setrlimit(RLIMIT_MEMLOCK, &rlim)) {
> +		perror("setrlimit failed");
> +		return 1;
> +	}
> +
> +	skel = rxe_pkg_kernel_dump__open_and_load();
> +	if (!skel) {
> +		fprintf(stderr, "Failed to open and load BPF skeleton\n");
> +		return 1;
> +	}
> +
> +	int ifindex = if_nametoindex("nk0");
> +
> +	if (ifindex == 0) {
> +		perror("if_nametoindex nk0 failed");
> +		goto cleanup;
> +	}
> +
> +	skel->links.handle_netkit_ingress = bpf_program__attach_netkit(
> +		skel->progs.handle_netkit_ingress, ifindex, NULL);
> +
> +	if (!skel->links.handle_netkit_ingress) {
> +		fprintf(stderr, "Failed to attach to netkit nk0\n");
> +		goto cleanup;
> +	}
> +
> +	ringbuf = ring_buffer__new(bpf_map__fd(skel->maps.rb),
> +				   handle_event, NULL, NULL);
> +	if (!ringbuf) {
> +		fprintf(stderr, "Failed to create ring buffer\n");
> +		goto cleanup;
> +	}
> +
> +	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 0;
> +}

[Severity: Medium]
Should main() return a non-zero exit status on error, and handle termination
signals cleanly?

If if_nametoindex("nk0"), bpf_program__attach_netkit(), or ring_buffer__new()
fails, execution jumps to cleanup with err == 1; similarly, if
ring_buffer__poll() fails with an error other than -EINTR, the loop breaks to
cleanup with err < 0. However, the cleanup path ignores err and
unconditionally returns 0, masking failures from the calling test script.

Also, because the while (1) loop has no signal handler for SIGINT/SIGTERM and
print_bpf_output() never flushes stdout, running rxe_netkit_dump in the
background with stdout redirected to a file or pipe and killing it from a
test script will lose block-buffered stdout output and skip the cleanup
label.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261019213821.44842-1-yanjun.zhu@linux.dev?part=1

  reply	other threads:[~2026-10-19 21:53 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-19 21:38 [PATCH v2 0/2] selftests/rdma: Add an eBPF-based testcase using netkit for RXE Zhu Yanjun
2026-10-19 21:38 ` [PATCH v2 1/2] selftests/rdma: Introduce eBPF kernel and user dump utilities " Zhu Yanjun
2026-10-19 21:53   ` sashiko-bot [this message]
2026-10-19 21:38 ` [PATCH v2 2/2] selftests/rdma: Add netkit-based automated test script " Zhu Yanjun
2026-10-19 21:53   ` 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-178279@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.