pktgen_if_write() can update cflows while the packet generator thread is inside mod_cur_headers(). The latter first tests cflows, but f_pick() then reloads it when selecting a random flow. This allows the following interleaving: CPU 0 (kpktgend) CPU 1 (proc write) if (pkt_dev->cflows) // 10 pkt_dev->cflows = 0 get_random_u32_below(pkt_dev->cflows) get_random_u32_below(0) returns a full-width random value. Using that value as an index into the fixed-size flows array causes an out-of-bounds access. The kernel reported: BUG: unable to handle page fault for address: ffffc8fe2d2674bc #PF: supervisor read access in kernel mode Oops: Oops: 0000 [#1] SMP KASAN NOPTI CPU: 0 UID: 0 PID: 65 Comm: kpktgend_0 RIP: 0010:mod_cur_headers+0x16f8/0x2840 Call Trace: pktgen_thread_worker+0x305a/0x6bc0 kthread+0x2c6/0x3b0 ret_from_fork+0x36e/0x5a0 ret_from_fork_asm+0x1a/0x30 Read cflows once at the start of mod_cur_headers(), pass the snapshot to f_pick(), and use it for later flow-state decisions in the same packet. Publish proc updates with WRITE_ONCE(). Flow selection then always uses a nonzero count bounded by MAX_CFLOWS, while a concurrent update takes effect on a later packet. Fixes: 007a531b0a0c ("[PKTGEN]: Introduce sequential flows") Cc: stable@vger.kernel.org Signed-off-by: Chengfeng Ye Signed-off-by: Qi Zhang --- v2: - Use the written value when reporting the new flow count. - Add READ_ONCE() to the remaining lockless cflows reads, including pktgen_if_show(), as suggested by Paolo Abeni. v1: https://lore.kernel.org/r/20260802152309.821584-1-marsy12010123@gmail.com net/core/pktgen.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/net/core/pktgen.c b/net/core/pktgen.c index ee64f3012..e14bb9a48 100644 --- a/net/core/pktgen.c +++ b/net/core/pktgen.c @@ -566,6 +566,7 @@ static const struct proc_ops pktgen_proc_ops = { static int pktgen_if_show(struct seq_file *seq, void *v) { const struct pktgen_dev *pkt_dev = seq->private; + unsigned int cflows = READ_ONCE(pkt_dev->cflows); ktime_t stopped; unsigned int i; u64 idle; @@ -590,7 +591,7 @@ static int pktgen_if_show(struct seq_file *seq, void *v) pkt_dev->nfrags, (unsigned long long) pkt_dev->delay, pkt_dev->clone_skb, pkt_dev->odevname); - seq_printf(seq, " flows: %u flowlen: %u\n", pkt_dev->cflows, + seq_printf(seq, " flows: %u flowlen: %u\n", cflows, pkt_dev->lflow); seq_printf(seq, @@ -675,7 +676,7 @@ static int pktgen_if_show(struct seq_file *seq, void *v) for (i = 0; i < NR_PKT_FLAGS; i++) { if (i == FLOW_SEQ_SHIFT) - if (!pkt_dev->cflows) + if (!cflows) continue; if (pkt_dev->flags & (1 << i)) { @@ -1632,8 +1633,8 @@ static ssize_t pktgen_if_write(struct file *file, if (value > MAX_CFLOWS) value = MAX_CFLOWS; - pkt_dev->cflows = value; - sprintf(pg_result, "OK: flows=%u", pkt_dev->cflows); + WRITE_ONCE(pkt_dev->cflows, value); + sprintf(pg_result, "OK: flows=%u", (unsigned int)value); return count; } #ifdef CONFIG_XFRM @@ -2373,7 +2374,7 @@ static inline int f_seen(const struct pktgen_dev *pkt_dev, int flow) return !!(pkt_dev->flows[flow].flags & F_INIT); } -static inline int f_pick(struct pktgen_dev *pkt_dev) +static inline int f_pick(struct pktgen_dev *pkt_dev, unsigned int cflows) { int flow = pkt_dev->curfl; @@ -2383,11 +2384,11 @@ static inline int f_pick(struct pktgen_dev *pkt_dev) pkt_dev->flows[flow].count = 0; pkt_dev->flows[flow].flags = 0; pkt_dev->curfl += 1; - if (pkt_dev->curfl >= pkt_dev->cflows) + if (pkt_dev->curfl >= cflows) pkt_dev->curfl = 0; /*reset */ } } else { - flow = get_random_u32_below(pkt_dev->cflows); + flow = get_random_u32_below(cflows); pkt_dev->curfl = flow; if (pkt_dev->flows[flow].count > pkt_dev->lflow) { @@ -2461,12 +2462,14 @@ static void set_cur_queue_map(struct pktgen_dev *pkt_dev) */ static void mod_cur_headers(struct pktgen_dev *pkt_dev) { + unsigned int cflows; __u32 imn; __u32 imx; int flow = 0; - if (pkt_dev->cflows) - flow = f_pick(pkt_dev); + cflows = READ_ONCE(pkt_dev->cflows); + if (cflows) + flow = f_pick(pkt_dev, cflows); /* Deal with source MAC */ if (pkt_dev->src_mac_count > 1) { @@ -2582,7 +2585,7 @@ static void mod_cur_headers(struct pktgen_dev *pkt_dev) pkt_dev->cur_saddr = htonl(t); } - if (pkt_dev->cflows && f_seen(pkt_dev, flow)) { + if (cflows && f_seen(pkt_dev, flow)) { pkt_dev->cur_daddr = pkt_dev->flows[flow].cur_daddr; } else { imn = ntohl(pkt_dev->daddr_min); @@ -2611,7 +2614,7 @@ static void mod_cur_headers(struct pktgen_dev *pkt_dev) pkt_dev->cur_daddr = htonl(t); } } - if (pkt_dev->cflows) { + if (cflows) { pkt_dev->flows[flow].flags |= F_INIT; pkt_dev->flows[flow].cur_daddr = pkt_dev->cur_daddr; -- 2.25.1