* [DPDK/ethdev Bug 2083] net/pcap: scattered Rx truncates length to 16 bits and crashes Tx
@ 2026-10-27 16:19 bugzilla
0 siblings, 0 replies; only message in thread
From: bugzilla @ 2026-10-27 16:19 UTC (permalink / raw)
To: dev
https://bugs.dpdk.org/show_bug.cgi?id=2083
Bug ID: 2083
Summary: net/pcap: scattered Rx truncates length to 16 bits and
crashes Tx
Product: DPDK
Version: 26.11
Hardware: All
OS: All
Status: UNCONFIRMED
Severity: normal
Priority: Normal
Component: ethdev
Assignee: dev@dpdk.org
Reporter: stephen@networkplumber.org
Target Milestone: ---
Created attachment 431
--> https://bugs.dpdk.org/attachment.cgi?id=431&action=edit
AI proposed patch
============================================================================
ANT-2026-VD4V3KPV [NOT SECURITY: ordinary bug]
net/pcap: scattered Rx truncates length to 16 bits and crashes Tx
Location: drivers/net/pcap/pcap_ethdev.c:230 in eth_pcap_rx_jumbo()
Commit: 9a45341f18a1ab47af7123e63d942b90c1b79f38
============================================================================
When `net_pcap` receives a capture record longer than 65535 bytes with Rx
scatter enabled, it builds an mbuf whose `pkt_len` is larger than the data
actually in its segment chain. The first code that walks the chain by `pkt_len`
then follows a NULL `next` pointer and the process dies with SIGSEGV. In
testpmd this happens in `io` forwarding to `tx_pcap`, in `rte_pktmbuf_read()`
called from `eth_pcap_tx_dumper()`.
The cause is a width mismatch in `drivers/net/pcap/pcap_ethdev.c`.
`eth_pcap_rx()` holds the 32-bit `caplen` and stores all of it in `pkt_len`,
but passes it to `eth_pcap_rx_jumbo()` through a `uint16_t data_len`. A
68536-byte record therefore becomes a 2-segment chain holding 3000 bytes,
reported as `length=68536`. The crash happens whenever the chain ends up
holding fewer bytes than `min(pkt_len, snaplen)`:
- With testpmd's default mbufs, 67584 and 68536 bytes crash.
- 131072 and 262144 bytes do not crash with the default `snaplen`.
No out-of-bounds read or write was found; the failure is the NULL dereference.
Such records are unusual. They come from a capture file with records above
about 64 KiB, or from live capture on a loopback interface whose MTU was
raised. On normal interfaces, libpcap's capture ring keeps frames below the
threshold.
At this commit the port must have `RTE_ETH_RX_OFFLOAD_SCATTER` enabled. From
v24.11 to v26.03, including the 24.11 and 25.11 LTS releases, the multi-segment
path was taken without any offload flag, so no setting is needed there.
`pkt_len` has carried the untruncated value since f5ead8f84f20 ("net/pcap: fix
blocking Rx").
The attached reproducer replays a one-frame pcap through testpmd with
`--enable-scatter` and crashes deterministically. A patch that widens the
parameter to 32 bits and caps the chain at `RTE_MBUF_MAX_NB_SEGS` is included.
## Details
This is an ordinary bug, not a security issue: the oversized record comes from
the capture file the operator names
in `rx_pcap=` (or from a loopback interface whose MTU the operator raised), and
the result is a NULL-dereference crash
of the process replaying it, with no out-of-bounds read or write.
A record longer than 65535 bytes in an `rx_pcap` file, received by a `net_pcap`
port with the Rx scatter offload
enabled, leaves `eth_pcap_rx()` as an mbuf whose `pkt_len` is the full captured
length while its segment chain holds
only `caplen % 65536` bytes. The next code that walks the chain by `pkt_len`
follows a NULL `next` pointer.
Seen at commit 9a45341f18 (26.11-rc1) with a classic pcap file holding one
68536-byte Ethernet frame (65536 + 3000):
```
dpdk-testpmd -l 0-1 --no-huge -m 512 --no-pci \
--vdev net_pcap0,rx_pcap=oversized.pcap,tx_pcap=forwarded.pcap -- \
-i --total-num-mbufs=4096 --enable-scatter --no-flush-rx --forward-mode=io
testpmd> set verbose 1
testpmd> start
```
With `--forward-mode=rxonly` the frame is reported as `length=68536 -
nb_segs=2`; two default testpmd mbufs
(2176-byte data room) hold at most 4352 bytes. In `io` mode
`/src/build-asan/app/dpdk-testpmd` stops with:
```
ERROR: AddressSanitizer: SEGV on unknown address 0x000000000028
The signal is caused by a READ memory access.
#0 __rte_pktmbuf_read lib/mbuf/rte_mbuf.c:811:14
#1 rte_pktmbuf_read lib/mbuf/rte_mbuf.h:1844:10
#2 eth_pcap_tx_dumper drivers/net/pcap/pcap_ethdev.c:560:10
#3 rte_eth_tx_burst lib/ethdev/rte_ethdev.h:6565:12
#4 common_fwd_stream_transmit app/test-pmd/testpmd.h:945:10
```
The default gcc build (`/src/build/app/dpdk-testpmd`) dies with SIGSEGV on the
same input. A 65535-byte frame is
received as 31 segments and written to `tx_pcap` unchanged.
The cause is a width mismatch between `eth_pcap_rx()` and its helper. The
caller keeps the captured length in 32 bits
and stores all of it in `pkt_len`; the helper that builds the chain takes it as
16 bits:
```c
/* drivers/net/pcap/pcap_ethdev.c:228 */
static int
eth_pcap_rx_jumbo(struct rte_mempool *mb_pool, struct rte_mbuf *mbuf,
const u_char *data, uint16_t data_len) /* 16 bits */
{
/* Copy the first segment. */
uint16_t len = rte_pktmbuf_tailroom(mbuf);
…
data_len -= len;
…
while (data_len > 0) {
/* drivers/net/pcap/pcap_ethdev.c:389 */
uint32_t len = header->caplen; /* 32 bits */
if (len <= rte_pktmbuf_tailroom(mbuf)) {
…
} else if (pcap_q->rx_scatter) {
if (unlikely(eth_pcap_rx_jumbo(pcap_q->mb_pool,
mbuf, packet, len) ==
-1)) {
…
mbuf->pkt_len = len; /* line 409:
full value */
```
For the 68536-byte record, `data_len` arrives as 3000. The first mbuf takes its
2048 bytes of tailroom, a second mbuf
takes the remaining 952, and the loop ends. `pkt_len` is then set to 68536.
Nothing limits `caplen` to 16 bits before this point. The `rx_pcap` handle
comes from
`pcap_open_offline_with_tstamp_precision()` (`open_single_rx_pcap()`, line
806), to which the `snaplen` devarg is not
applied, so the record length is bounded only by the capture file; a
262144-byte record (the value of
`ETH_PCAP_MAXIMUM_SNAPLEN`, line 51) is handed to the driver.
Which lengths go wrong: write `caplen = k * 65536 + r`. If `r` is at least the
first mbuf's tailroom, the chain holds
`r` bytes. If `r` is smaller, the first-segment copy still takes a full
tailroom, `data_len -= len` wraps, and the
chain ends up holding `65536 + r` bytes. The chain therefore matches `pkt_len`
only below 65536 + tailroom. With the
2048-byte tailroom above, 67583 bytes is received correctly (32 segments),
67584 gives `nb_segs=1`, and 131072 and
262144 both give 31 segments. The helper never copies more than `caplen` bytes,
so the libpcap buffer is not over-read
and no mbuf is overrun; the defect is the short chain.
The short chain is fatal in the driver's own Tx path:
```c
/* drivers/net/pcap/pcap_ethdev.c:554 */
len = rte_pktmbuf_pkt_len(mbuf);
caplen = RTE_MIN(len, snaplen); /* 65535 */
…
data = rte_pktmbuf_read(mbuf, 0, caplen, temp_data);
/* This could only happen if mbuf is bogus pkt_len > data_len
*/
RTE_ASSERT(data != NULL);
```
`__rte_pktmbuf_read()` (`lib/mbuf/rte_mbuf.c:792`) accepts the request because
`off + len <= pkt_len`, copies the two
real segments, still has 62535 bytes to go, and reads `seg->data_len` through
the NULL `seg->next` at line 811. It does
not return NULL for this mbuf, so the `RTE_ASSERT` would not catch it in a
debug build either. By the same reading
(not run), `rte_pktmbuf_copy()` (`lib/mbuf/rte_mbuf.c:668`) advances `seg =
seg->next` under the same `pkt_len`
bound, and `eth_pcap_tx()` (`tx_iface`, line 663) makes the same
`rte_pktmbuf_read()` call for a frame that passes its
multi-segment `len > snaplen` check.
The read is capped at `snaplen`, so the Tx path crashes only when the chain
holds fewer bytes than
`min(pkt_len, snaplen)`. With the default `snaplen` of 65535, the 131072- and
262144-byte records (31 segments,
65536 bytes) are forwarded without a crash; 67584 and 68536 bytes crash.
History: `pkt_len` has carried the untruncated value since f5ead8f84f20
("net/pcap: fix blocking Rx", first released
in v24.11), which dropped the `(uint16_t)` cast on `pkt_len` but left the
helper's parameter alone. Until f28abe77f516
("net/pcap: add Rx scatter offload", v26.07) the multi-segment path was taken
for every frame larger than the first
mbuf, without an offload flag, so from v24.11 to v26.03 (including the 24.11
and 25.11 long-term releases) the bug
needs no Rx scatter setting. Only the commit above was run.
## Impact
An application replaying a capture through `net_pcap` crashes with SIGSEGV when
the capture holds a record of 65536
bytes plus the first mbuf's tailroom or more (67584 with testpmd's default
mbufs) whose chain ends up shorter than
`min(pkt_len, snaplen)` (most such lengths, not 131072 or 262144 with the
default `snaplen`) and the packet reaches code that
walks the chain by `pkt_len` (the driver's `tx_pcap` path in the run above;
`rxonly` forwarding does not crash). It is
a NULL dereference; no out-of-bounds read or write was found. At this commit
the port must also have
`RTE_ETH_RX_OFFLOAD_SCATTER` enabled (off by default); on v24.11 to v26.03 it
need not.
Users meet it rarely: captures of Ethernet traffic do not contain records this
long, so it takes a capture with
records above about 64 KiB, such as one taken on a loopback interface with a
raised MTU. Live capture (`rx_iface`,
`iface`) is not protected by the `snaplen` devarg: the Rx handle opened at
probe time is given snaplen 0, which
libpcap treats as its 262144 maximum, and a 68536-byte frame on loopback with
the MTU raised to 200000 crashes testpmd
the same way. On normal interfaces libpcap's capture ring limits a frame to
about max(MTU, 65535) + 18 bytes, below
the threshold; a veth peer sending large TCP segments was captured at 65562
bytes at most (libpcap 1.10.6, Linux 6.1).
--
You are receiving this mail because:
You are the assignee for the bug.
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2026-10-27 16:19 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-27 16:19 [DPDK/ethdev Bug 2083] net/pcap: scattered Rx truncates length to 16 bits and crashes Tx bugzilla
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox