Re: [PATCH net] net/sched: defer qdisc freeing after failed creation
From: David Lee <hidden>
Date: 2026-08-10 15:58:40
Also in:
lkml
Hi Jamal, I have attached the reproducer and satnizer logs here: --- [ 230.057740] ipip: IPv4 and MPLS over IPv4 tunneling driver [ 231.667279] ================================================================== [ 231.667738] BUG: KASAN: stack-out-of-bounds in __ip_options_echo+0xdf7/0x1860 [ 231.667738] Write of size 255 at addr ffffc9000029f008 by task ksoftirqd/3/37 [ 231.667738] [ 231.667738] CPU: 3 UID: 0 PID: 37 Comm: ksoftirqd/3 Not tainted 7.2.0-rc3-kasan #1 PREEMPT(lazy) [ 231.667738] Hardware name: QEMU Ubuntu 26.04 PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1ubuntu1 04/01/2014 [ 231.667738] Call Trace: [ 231.667738] <TASK> [ 231.667738] dump_stack_lvl+0x5f/0x90 [ 231.667738] print_report+0x15b/0x4ec [ 231.667738] ? __pfx__raw_spin_lock_irqsave+0x10/0x10 [ 231.667738] ? kasan_addr_to_slab+0xd/0x80 [ 231.667738] kasan_report+0xf2/0x130 [ 231.667738] ? __ip_options_echo+0xdf7/0x1860 [ 231.667738] ? __ip_options_echo+0xdf7/0x1860 [ 231.667738] kasan_check_range+0x13a/0x230 [ 231.667738] __asan_memcpy+0x3b/0x80 [ 231.667738] __ip_options_echo+0xdf7/0x1860 [ 231.667738] ? __pfx___ip_options_echo+0x10/0x10 [ 231.667738] __icmp_send+0x8c5/0x26e0 [ 231.667738] ? __pfx___icmp_send+0x10/0x10 [ 231.667738] ? sysvec_apic_timer_interrupt+0x54/0xd0 [ 231.667738] ? __pfx_nf_reject_fill_skb_dst+0x10/0x10 [nf_reject_ipv4] [ 231.667738] ? _raw_spin_lock+0x82/0xf0 [ 231.667738] ? irqentry_exit+0x1cd/0x7b0 [ 231.667738] nf_send_unreach+0x303/0x810 [nf_reject_ipv4] [ 231.667738] ? __pfx_nf_send_unreach+0x10/0x10 [nf_reject_ipv4] [ 231.667738] nft_reject_inet_eval+0x4cd/0x8a0 [nft_reject_inet] [ 231.667738] ? nft_do_chain+0x45a/0x1ae0 [nf_tables] [ 231.667738] nft_do_chain+0x25a/0x1ae0 [nf_tables] [ 231.667738] ? __pfx_tcp_v4_rcv+0x10/0x10 [ 231.667738] ? raw_local_deliver+0x3b9/0xc80 [ 231.667738] ? __pfx_nft_do_chain+0x10/0x10 [nf_tables] [ 231.667738] ? update_stack_state+0x26e/0x6a0 [ 231.667738] ? fib_validate_source+0x455/0x770 [ 231.667738] ? update_stack_state+0x26e/0x6a0 [ 231.667738] nft_do_chain_inet_ingress+0x44f/0x1420 [nf_tables] [ 231.667738] ? __pfx_nft_do_chain_inet_ingress+0x10/0x10 [nf_tables] [ 231.667738] ? unwind_next_frame+0x18a/0xac0 [ 231.667738] nf_hook_slow+0xaa/0x1f0 [ 231.667738] __netif_receive_skb_core.constprop.0+0x19b6/0x31e0 [ 231.667738] ? __pfx_read_hpet+0x10/0x10 [ 231.667738] ? ret_from_fork_asm+0x1a/0x30 [ 231.667738] ? __pfx___netif_receive_skb_core.constprop.0+0x10/0x10 [ 231.667738] ? clockevents_program_event+0x2bd/0x750 [ 231.667738] ? run_ksoftirqd+0x3a/0x60 [ 231.667738] ? kasan_save_stack+0x4e/0x70 [ 231.667738] ? __kasan_check_write+0x14/0x30 [ 231.667738] ? _raw_spin_lock+0x82/0xf0 [ 231.667738] ? __hrtimer_rearm_deferred+0x18a/0x520 [ 231.667738] __netif_receive_skb_list_core+0x314/0xb10 [ 231.667738] ? sysvec_apic_timer_interrupt+0x54/0xd0 [ 231.667738] ? __pfx___netif_receive_skb_list_core+0x10/0x10 [ 231.667738] ? kasan_save_track+0x27/0x70 [ 231.667738] netif_receive_skb_list_internal+0x5eb/0xde0 [ 231.667738] ? __kasan_check_write+0x14/0x30 [ 231.667738] ? __pfx_netif_receive_skb_list_internal+0x10/0x10 [ 231.667738] ? __pfx_napi_complete_done+0x10/0x10 [ 231.667738] ? __pfx_dql_completed+0x10/0x10 [ 231.667738] ? __kasan_check_read+0x11/0x20 [ 231.667738] ? dev_gro_receive+0x20d/0x3060 [ 231.667738] napi_complete_done+0x1b6/0x830 [ 231.667738] ? pick_eevdf+0x19b/0x7e0 [ 231.667738] ? __pfx_napi_complete_done+0x10/0x10 [ 231.667738] ? gro_receive_skb+0x292/0xa90 [ 231.667738] gro_cell_poll+0x120/0x1f0 [ 231.667738] __napi_poll+0xa3/0x4c0 [ 231.667738] net_rx_action+0x4c1/0xfb0 [ 231.667738] ? __pfx_net_rx_action+0x10/0x10 [ 231.667738] ? finish_task_switch.isra.0+0x1f1/0xc10 [ 231.667738] ? __switch_to+0x8d7/0xd40 [ 231.667738] handle_softirqs+0x1ae/0x670 [ 231.667738] ? __pfx_handle_softirqs+0x10/0x10 [ 231.667738] run_ksoftirqd+0x3a/0x60 [ 231.667738] smpboot_thread_fn+0x29d/0x6e0 [ 231.667738] ? __pfx_smpboot_thread_fn+0x10/0x10 [ 231.667738] kthread+0x333/0x420 [ 231.667738] ? calculate_sigpending+0x78/0xb0 [ 231.667738] ? __pfx_kthread+0x10/0x10 [ 231.667738] ret_from_fork+0x426/0x7c0 [ 231.667738] ? __pfx_ret_from_fork+0x10/0x10 [ 231.667738] ? native_load_gs_index+0x3f/0x60 [ 231.667738] ? __switch_to+0x8d7/0xd40 [ 231.667738] ? __switch_to_asm+0x39/0x70 [ 231.667738] ? __pfx_kthread+0x10/0x10 [ 231.667738] ret_from_fork_asm+0x1a/0x30 [ 231.667738] </TASK> [ 231.667738] [ 231.667738] The buggy address belongs to stack of task ksoftirqd/3/37 [ 231.667738] and is located at offset 376 in frame: [ 231.667738] __icmp_send+0x0/0x26e0 [ 231.667738] [ 231.667738] This frame has 7 objects: [ 231.667738] [32, 33) 'apply_ratelimit' [ 231.667738] [48, 49) '_inner_type' [ 231.667738] [64, 68) 'data' [ 231.667738] [80, 88) 'rt' [ 231.667738] [112, 168) 'ipc' [ 231.667738] [208, 264) 'fl4' [ 231.667738] [304, 416) 'icmp_param_u' [ 231.667738] [ 231.667738] The buggy address belongs to a vmalloc virtual mapping [ 231.667738] The buggy address belongs to the physical page: [ 231.667738] page: refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x100c1f [ 231.667738] flags: 0x17ffffc0000000(node=0|zone=2|lastcpupid=0x1fffff) [ 231.667738] raw: 0017ffffc0000000 ffffea00040307c8 ffffea00040307c8 0000000000000000 [ 231.667738] raw: 0000000000000000 0000000000000000 00000001ffffffff 0000000000000000 [ 231.667738] page dumped because: kasan: bad access detected [ 231.667738] [ 231.667738] Memory state around the buggy address: [ 231.667738] ffffc9000029ef00: 00 00 00 00 00 00 00 f2 f2 f2 f2 f2 00 00 00 00 [ 231.667738] ffffc9000029ef80: 00 00 00 f2 f2 f2 f2 f2 00 00 00 00 00 00 00 00 [ 231.667738] >ffffc9000029f000: 00 00 00 00 00 00 f3 f3 f3 f3 00 00 00 00 00 00 [ 231.667738] ^ [ 231.667738] ffffc9000029f080: 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1 00 f3 [ 231.667738] ffffc9000029f100: f3 f3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 231.667738] ================================================================== [ 231.746888] Disabling lock debugging due to kernel taint --- ======== Affected Versions ======= Confirmed Version: * 6f5156d7a31a8c3b0f34af4675c9299c8f877cbe (post-v7.2-rc3) Required Configs for the Vulnerability: * CONFIG_INET * CONFIG_NETFILTER * CONFIG_NETFILTER_INGRESS * CONFIG_NF_TABLES * CONFIG_NF_TABLES_INET * CONFIG_NFT_REJECT * CONFIG_NFT_REJECT_INET * CONFIG_NF_REJECT_IPV4 Additional Configs for the Proof of Concept: * CONFIG_NET_IPIP * CONFIG_VETH * CONFIG_PACKET * CONFIG_USER_NS * CONFIG_NET_NS ========== Reproduction ========== Step 1: Build kernel commit `6f5156d7a31a8c3b0f34af4675c9299c8f877cbe` with KASAN and the configuration options listed above. Step 2: Build the attached `poc.c`: ~ gcc -static -O2 -Wall -Wextra -o poc poc.c ~ Step 3: Ensure the `nft_reject_inet`, `nf_reject_ipv4`, `ipip`, and `veth` modules are available or built into the kernel. Run `./poc` as an ordinary local user on a system that permits unprivileged user namespaces. The `ip` utility must be installed. The program creates its own user and network namespace, configures a veth pair and IPIP device, installs an inet-ingress ICMPX reject rule, and transmits the triggering packet. KASAN reports the stack-out-of-bounds write. Best regards, David On Wed, Aug 5, 2026 at 2:01 PM Jamal Hadi Salim [off-list ref] wrote:
On Wed, Aug 5, 2026 at 6:25 AM David Lee [off-list ref] wrote:quoted
From: Kyle Zeng <redacted> A qdisc's init callback can publish state to RCU readers before qdisc_create() completes. In particular, clsact_init() binds a populated shared ingress block and installs an embedded mini_Qdisc in dev->tcx_ingress. If subsequent rate estimator setup fails, the unwind removes that pointer but qdisc_free() immediately releases the qdisc and its per-CPU statistics. A reader that obtained the miniq before removal can then access freed memory. Add qdisc_free_rcu() and use it for the creation error path, matching normal qdisc destruction. This keeps the embedded miniq and the per-CPU statistics alive until pre-existing readers complete. Fixes: 51ab2994c387 ("net: sched: allow ingress and clsact qdiscs toshare filter blocks")quoted
Assisted-by: Codex:gpt-5.6-sol Codex:gpt-5.5-cyber Signed-off-by: Kyle Zeng <redacted> Signed-off-by: David Lee <redacted>Thanks for finding the issue. But you should know the deal by now, send the poc - you can send it in private. Same goes for your other patch. cheers, jamalquoted
--- Bug found and triaged by OpenAI Security Research and validated by Trail of Bits. Trail of Bits has a reproducer for this bug that triggers a KASAN use-after-free and can share if needed. include/net/sch_generic.h | 1 + net/sched/sch_api.c | 2 +- net/sched/sch_generic.c | 7 ++++++- 3 files changed, 8 insertions(+), 2 deletions(-)diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h index 45a1e8c782..d45442c926 100644 --- a/include/net/sch_generic.h +++ b/include/net/sch_generic.h@@ -793,6 +793,7 @@ struct Qdisc *qdisc_alloc(struct netdev_queue*dev_queue,quoted
const struct Qdisc_ops *ops, struct netlink_ext_ack *extack); void qdisc_free(struct Qdisc *qdisc); +void qdisc_free_rcu(struct Qdisc *qdisc); struct Qdisc *qdisc_create_dflt(struct netdev_queue *dev_queue, const struct Qdisc_ops *ops, u32parentid,quoted
struct netlink_ext_ack *extack);diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c index 668bcd60d1..041bd60072 100644 --- a/net/sched/sch_api.c +++ b/net/sched/sch_api.c@@ -1373,7 +1373,7 @@ static struct Qdisc *qdisc_create(structnet_device *dev,quoted
err_out3: qdisc_lock_uninit(sch, ops); netdev_put(dev, &sch->dev_tracker); - qdisc_free(sch); + qdisc_free_rcu(sch); err_out2: bpf_module_put(ops, ops->owner); err_out:diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c index ef2b4bf515..86d551fbab 100644 --- a/net/sched/sch_generic.c +++ b/net/sched/sch_generic.c@@ -1103,6 +1103,11 @@ static void qdisc_free_cb(struct rcu_head *head) qdisc_free(q); } +void qdisc_free_rcu(struct Qdisc *qdisc) +{ + call_rcu(&qdisc->rcu, qdisc_free_cb); +} + static void __qdisc_destroy(struct Qdisc *qdisc) { const struct Qdisc_ops *ops = qdisc->ops;@@ -1127,7 +1132,7 @@ static void __qdisc_destroy(struct Qdisc *qdisc) trace_qdisc_destroy(qdisc); - call_rcu(&qdisc->rcu, qdisc_free_cb); + qdisc_free_rcu(qdisc); } void qdisc_destroy(struct Qdisc *qdisc) --2.53.0