* [PATCH net] net: pktgen: fix out-of-bounds write in pktgen_finalize_skb()
@ 2026-07-29 3:12 Yuejie Shi
2026-08-03 21:25 ` Jakub Kicinski
0 siblings, 1 reply; 2+ messages in thread
From: Yuejie Shi @ 2026-07-29 3:12 UTC (permalink / raw)
To: davem, edumazet, kuba, pabeni, horms
Cc: netdev, linux-kernel, Yuejie Shi, stable
pktgen_finalize_skb() spreads `datalen` bytes over `frags` page
fragments, but the loop that fills skb_shinfo(skb)->frags[] is bounded
only by `datalen > 0` -- there is no `i < frags` check. The
per-fragment length is computed once with a truncating division:
frag_len = min_t(int, datalen / frags, PAGE_SIZE);
The code just above has already capped datalen at frags * PAGE_SIZE, so
a valid partition does exist, but the truncation means the first
frags - 1 fragments can leave more than PAGE_SIZE behind, and the
last-fragment branch only consumes min(datalen, PAGE_SIZE). Writing
datalen as q * frags + r with q = datalen / frags, the loop overruns
whenever
q + r > PAGE_SIZE
because q + r bytes are still outstanding after frags - 1 iterations and
the final fragment can take only PAGE_SIZE of them. The loop then runs
once more, with i == frags, and writes one element past the array.
frags is clamped to MAX_SKB_FRAGS (17) and frags[] is the last member of
struct skb_shared_info, which sits at the very end of the skb head
allocation, so writing frags[17] writes 16 bytes past that allocation.
With PAGE_SIZE == 4096 every pkt_size in [69675, 69689] hits it
(datalen = pkt_size - 58, giving q = 4095 and r >= 2):
modprobe pktgen
echo "add_device dummy0" > /proc/net/pktgen/kpktgend_0
echo "frags 17" > /proc/net/pktgen/dummy0
echo "pkt_size 69675" > /proc/net/pktgen/dummy0
echo start > /proc/net/pktgen/pgctrl
BUG: KASAN: use-after-free in pktgen_finalize_skb+0x1e8/0x4a4 [pktgen]
Write of size 8 at addr ffff0000c6b40000 by task kpktgend_0/97
__asan_store8+0xe0/0xe4
pktgen_finalize_skb+0x1e8/0x4a4 [pktgen]
pktgen_thread_worker+0x1f3c/0x2d50 [pktgen]
kthread+0x1c4/0x1e0
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 pfn:0x106b40
nr_frags ends up at 18 as well, so skb_release_data() later walks one
entry past the array too.
Compute the fragment length with DIV_ROUND_UP() instead. datalen is
still <= frags * PAGE_SIZE so the result cannot exceed PAGE_SIZE, but
the fragments now cover datalen exactly and the loop always terminates
on the last one. As a side effect pktgen stops emitting zero-length
fragments when datalen < frags. Bound the loop with `i < frags` as
well, so that a future arithmetic mistake cannot walk off the array
again.
Reaching this needs real root: pktgen creates its /proc/net/pktgen
entries with proc_mkdir()/proc_create_data() at mode 0600 owned by
global root, so unlike /proc/net itself they are not remapped to the
owner of a user namespace, and the module has to be loaded first. It is
still a plain heap overflow behind a documented interface.
Fixes: 7d36a991e8d3 ("pktgen: create num frags requested")
Cc: stable@vger.kernel.org
Signed-off-by: Yuejie Shi <syjcnss@gmail.com>
---
net/core/pktgen.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/net/core/pktgen.c b/net/core/pktgen.c
index 8e185b318288..2dbb7caacd0d 100644
--- a/net/core/pktgen.c
+++ b/net/core/pktgen.c
@@ -2843,8 +2843,8 @@ static void pktgen_finalize_skb(struct pktgen_dev *pkt_dev, struct sk_buff *skb,
}
i = 0;
- frag_len = min_t(int, datalen / frags, PAGE_SIZE);
- while (datalen > 0) {
+ frag_len = min_t(int, DIV_ROUND_UP(datalen, frags), PAGE_SIZE);
+ while (datalen > 0 && i < frags) {
if (unlikely(!pkt_dev->page)) {
int node = numa_node_id();
--
2.51.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH net] net: pktgen: fix out-of-bounds write in pktgen_finalize_skb()
2026-07-29 3:12 [PATCH net] net: pktgen: fix out-of-bounds write in pktgen_finalize_skb() Yuejie Shi
@ 2026-08-03 21:25 ` Jakub Kicinski
0 siblings, 0 replies; 2+ messages in thread
From: Jakub Kicinski @ 2026-08-03 21:25 UTC (permalink / raw)
To: syjcnss
Cc: Jakub Kicinski, davem, edumazet, pabeni, horms, netdev,
linux-kernel, stable
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
--- Patch [1]: [PATCH net] net: pktgen: fix out-of-bounds write in pktgen_finalize_skb() ---
commit 482871be2b4cb4112bd717673de3a4fe3ab0b9c3
Author: Yuejie Shi <syjcnss@gmail.com>
net: pktgen: fix out-of-bounds write in pktgen_finalize_skb()
The patch changes the per-fragment length in pktgen_finalize_skb() from a
truncating division to DIV_ROUND_UP(), and adds an i < frags bound to the
fragment fill loop, so the loop can no longer write past
skb_shinfo(skb)->frags[]. The reported KASAN overflow with pkt_size in
[69675, 69689] and frags 17 does look fixed by this.
> diff --git a/net/core/pktgen.c b/net/core/pktgen.c
> index ee64f3012321..60dbe60005eb 100644
> --- a/net/core/pktgen.c
> +++ b/net/core/pktgen.c
> @@ -2843,8 +2843,8 @@ static void pktgen_finalize_skb(struct pktgen_dev *pkt_dev, struct sk_buff *skb,
> }
>
> i = 0;
> - frag_len = min_t(int, datalen / frags, PAGE_SIZE);
> - while (datalen > 0) {
> + frag_len = min_t(int, DIV_ROUND_UP(datalen, frags), PAGE_SIZE);
> + while (datalen > 0 && i < frags) {
[Severity: Medium]
[Finding: main-2-0]
[Sources: opus-5]
With frag_len rounded up, can the partition now run out of bytes before
i reaches frags - 1, so that the else branch over-charges the last
fragment?
frag_len is computed once and never re-clamped against the bytes still
outstanding, and only the i == (frags - 1) branch uses min(datalen,
PAGE_SIZE):
/*last fragment, fill rest of data*/
if (i == (frags - 1))
skb_frag_fill_page_desc(&skb_shinfo(skb)->frags[i],
pkt_dev->page, 0,
min(datalen, PAGE_SIZE));
else
skb_frag_fill_page_desc(&skb_shinfo(skb)->frags[i],
pkt_dev->page, 0, frag_len);
datalen -= skb_frag_size(&skb_shinfo(skb)->frags[i]);
skb->len += skb_frag_size(&skb_shinfo(skb)->frags[i]);
skb->data_len += skb_frag_size(&skb_shinfo(skb)->frags[i]);
Taking pkt_size 77 with frags 17, which the pkt_size setter accepts since
it only clamps against 14 + 20 + 8:
fill_packet_ipv4(): datalen = 77 - 42 = 35
pktgen_finalize_skb(): datalen -= sizeof(*pgh) -> 19
frag_len = DIV_ROUND_UP(19, 17) = 2
Iterations i = 0..8 consume 18 bytes and leave 1 byte. At i == 9 the
i == (frags - 1) test is false (frags - 1 is 16), so the else branch
charges the full frag_len of 2:
datalen becomes -1
skb->len and skb->data_len are 1 byte larger than requested
nr_frags ends at 10 instead of the requested 17
Another example is datalen 71 with frags 17: frag_len is 5, 15 iterations
consume 75 bytes, overshooting by 4, and nr_frags ends at 15. The
overshoot is bounded by frag_len - 1, so at most 16 bytes with
MAX_SKB_FRAGS == 17, and fragment sizes still never exceed PAGE_SIZE, so
this is a length-accounting problem rather than a memory-safety one.
Does the inflated skb->len leave the frame inconsistent with the L3/L4
headers? fill_packet_ipv4() writes the lengths from the same datalen it
hands to pktgen_finalize_skb():
udph->len = htons(datalen + 8); /* DATA + udphdr */
...
iplen = 20 + 8 + datalen;
iph->tot_len = htons(iplen);
fill_packet_ipv6() follows the same pattern. pktgen_xmit() also does
pkt_dev->last_pkt_size = pkt_dev->skb->len, which feeds
pkt_dev->tx_bytes, so the reported byte counts would drift from the
configured pkt_size too.
Before this change these same inputs partitioned exactly: with datalen 19
and frags 17 the truncated frag_len was 1, giving 16 one-byte fragments
plus a last fragment of 3, and the i == (frags - 1) branch was always
reached.
Would using min(datalen, frag_len) in the else branch, or treating
datalen <= frag_len as the last fragment, keep the sum equal to datalen
in these cases?
The commit message says:
the fragments now cover datalen exactly and the loop always terminates
on the last one. As a side effect pktgen stops emitting zero-length
fragments when datalen < frags.
Should this also mention that for small datalen with many fragments the
loop terminates before the last fragment, and that fewer fragments than
requested are produced?
--
pw-bot: cr
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-08-03 21:25 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-29 3:12 [PATCH net] net: pktgen: fix out-of-bounds write in pktgen_finalize_skb() Yuejie Shi
2026-08-03 21:25 ` Jakub Kicinski
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox