net/pcap: scattered Rx truncates length to 16 bits and crashes Tx
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: [email protected]
Reporter: [email protected]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 frompcap_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 ofETH_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 holdsr 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 thanmin(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 thanmin(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 haveRTE_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).
Signature · 2 lines
You are receiving this mail because: You are the assignee for the bug.