From: Yuejie Shi <syjcnss@gmail.com>
To: davem@davemloft.net, edumazet@google.com, kuba@kernel.org,
pabeni@redhat.com, horms@kernel.org
Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
Yuejie Shi <syjcnss@gmail.com>,
stable@vger.kernel.org
Subject: [PATCH net] net: pktgen: fix out-of-bounds write in pktgen_finalize_skb()
Date: Wed, 29 Jul 2026 11:12:52 +0800 [thread overview]
Message-ID: <20260729031252.54476-1-syjcnss@gmail.com> (raw)
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
next reply other threads:[~2026-07-29 3:13 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-29 3:12 Yuejie Shi [this message]
2026-08-03 21:25 ` [PATCH net] net: pktgen: fix out-of-bounds write in pktgen_finalize_skb() Jakub Kicinski
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=20260729031252.54476-1-syjcnss@gmail.com \
--to=syjcnss@gmail.com \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=stable@vger.kernel.org \
/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.