Thread (2 messages) 2 messages, 2 authors, 2026-02-23

Re: [BUG] general protection fault in qdisc_tree_reduce_backlog

From: Jamal Hadi Salim <jhs@mojatatu.com>
Date: 2026-02-23 13:41:03
Also in: lkml

On Sun, Feb 22, 2026 at 8:33 PM GangMin Kim [off-list ref] wrote:
Dear Linux kernel developers and maintainers,
Using a modified version of syzkaller, I identified a new bug and
refined the PoC, and the bug-related information is attached
below.Please let me know if you need any further information.

Summary

A missing NULL check for cl_ops during qdisc replacement causes a NULL
Pointer Dereference in qdisc_tree_reduce_backlog().
This also seems to have the same root cause as your earlier email.

cheers,
jamal
Keywords
- net/sched

Kernel Info
Version: (Output of /proc/version)
- Linux version 7.0.0-rc1
Commit: (Git hash if applicable)
- 6de23f81a5e08be8fbf5e8d7e9febc72a5b5f27f

Description(Root Cause)

Most qdiscs set .cl_ops in their struct Qdisc_ops.
// net/sched/sch_tbf.c
static struct Qdisc_ops tbf_qdisc_ops __read_mostly = {
    .cl_ops     = &tbf_class_ops,
    .id         = "tbf",
    ...
};

// net/sched/sch_qfq.c
static struct Qdisc_ops qfq_qdisc_ops __read_mostly = {
    .cl_ops     = &qfq_class_ops,
    .id         = "qfq",
    ...
};
However, teql is a classless qdisc and does not set cl_ops.
static __init void teql_master_setup(struct net_device *dev)
{
    struct teql_master *master = netdev_priv(dev);
    struct Qdisc_ops *ops = &master->qops;

    master->dev = dev;
    ops->priv_size  = sizeof(struct teql_sched_data);

    ops->enqueue    =   teql_enqueue;
    ops->dequeue    =   teql_dequeue;
    ops->peek   =   teql_peek;
    ops->init   =   teql_qdisc_init;
    ops->reset  =   teql_reset;
    ops->destroy    =   teql_destroy;
    ops->owner  =   THIS_MODULE;

    dev->netdev_ops =       &teql_netdev_ops;
    dev->type       = ARPHRD_VOID;
    dev->mtu        = 1500;
    dev->min_mtu        = 68;
    dev->max_mtu        = 65535;
    dev->tx_queue_len   = 100;
    dev->flags      = IFF_NOARP;
    dev->hard_header_len    = LL_MAX_HEADER;
    netif_keep_dst(dev);
}
When a qdisc is replaced, the existing qdisc tree is deleted, and
qdisc_tree_reduce_backlog() is called to update the backlog counters.
tc_modify_qdisc
  __tc_modify_qdisc
    qdisc_graft
      qdisc_replace
        qdisc_purge_queue
          qdisc_tree_reduce_backlog
void qdisc_tree_reduce_backlog(struct Qdisc *sch, int n, int len)
{
    const struct Qdisc_class_ops *cops;
    unsigned long cl;
    u32 parentid;
    bool notify;
    int drops;

    drops = max_t(int, n, 0);
    rcu_read_lock();
    while ((parentid = sch->parent)) {
        if (parentid == TC_H_ROOT)
            break;

        if (sch->flags & TCQ_F_NOPARENT)
            break;
        /* Notify parent qdisc only if child qdisc becomes empty. */
        notify = !sch->q.qlen;
        /* TODO: perform the search on a per txq basis */
        sch = qdisc_lookup_rcu(qdisc_dev(sch), TC_H_MAJ(parentid));
        if (sch == NULL) {
            WARN_ON_ONCE(parentid != TC_H_ROOT);
            break;
        }
        cops = sch->ops->cl_ops; // [1]
        if (notify && cops->qlen_notify) { // [2]
            /* Note that qlen_notify must be idempotent as it may get called
             * multiple times.
             */
            cl = cops->find(sch, parentid);
            cops->qlen_notify(sch, cl);
        }
        sch->q.qlen -= n;
        sch->qstats.backlog -= len;
        __qdisc_qstats_drop(sch, drops);
    }
    rcu_read_unlock();
}
If the parent qdisc is replaced with teql, sch becomes teql0, and
since teql is a classless qdisc, cl_ops is NULL. At [1], cops is
assigned NULL, and at [2], accessing qlen_notify through the NULL
pointer causes a NULL Pointer Dereference.

Kasan Report
Oops: general protection fault, probably for non-canonical address
0xdffffc0000000004: 0000 [#1] SMP KASAN NOPTI
KASAN: null-ptr-deref in range [0x0000000000000020-0x0000000000000027]
CPU: 0 UID: 0 PID: 2275 Comm: test Tainted: G        W
7.0.0-rc1 #54 PREEMPT(full)
Tainted: [W]=WARN
Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix,
1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
RIP: 0010:qdisc_tree_reduce_backlog+0xcf/0x400 net/sched/sch_api.c:810
Code: 4d 8b 67 18 49 8d 7c 24 08 48 89 f8 48 c1 e8 03 80 3c 18 00 0f
85 fb 02 00 00 4d 8b 6c 24 08 4d 8d 65 20 4c 89 e0 48 c1 e8 03 <80> 3c
18 00 0f 85 d4 02 00 00 49 83 7d 20 00 74 48 e8 7b ac 98 fd
RSP: 0018:ffff8881213ef2d8 EFLAGS: 00010212
RAX: 0000000000000004 RBX: dffffc0000000000 RCX: ffffffff83d4b680
RDX: ffff8881213e4680 RSI: 0000000000010000 RDI: ffff888103812a08
RBP: 0000000000010001 R08: 0000000000000000 R09: fffffbfff0d3595c
R10: ffffffff869acae7 R11: 00000000ac3123a9 R12: 0000000000000020
R13: 0000000000000000 R14: 0000000000000000 R15: ffff888121071c00
FS:  00007f10c4c3f700(0000) GS:ffff88834ad0d000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007f10c4bfcfb8 CR3: 0000000121398000 CR4: 0000000000750ef0
PKRU: 55555554
Call Trace:
 <TASK>
 htb_graft+0x257/0x810 net/sched/sch_htb.c:1468
 qdisc_refcount_dec_if_one include/net/sch_generic.h:152 [inline]
 qdisc_graft+0x2ef/0x1460 net/sched/sch_api.c:1117
 __tc_modify_qdisc net/sched/sch_api.c:1631 [inline]
 tc_modify_qdisc+0xf4f/0x1e40 net/sched/sch_api.c:1819
 rcu_read_unlock include/linux/rcupdate.h:883 [inline]
 rtnetlink_rcv_msg+0x3b9/0xa90 net/core/rtnetlink.c:6913
 netlink_rcv_skb+0x12e/0x390 net/netlink/af_netlink.c:2539
 kfree_skb_reason include/linux/skbuff.h:1322 [inline]
 kfree_skb include/linux/skbuff.h:1331 [inline]
 netlink_unicast_kernel net/netlink/af_netlink.c:1321 [inline]
 netlink_unicast+0x6c1/0x970 net/netlink/af_netlink.c:1344
 netlink_sendmsg+0x79c/0xc50 net/netlink/af_netlink.c:2465
 __sock_release net/socket.c:673 [inline]
 ____sys_sendmsg+0x8b2/0xa50 net/socket.c:690
 sendmsg_copy_msghdr net/socket.c:2621 [inline]
 ___sys_sendmsg+0x120/0x1c0 net/socket.c:2642
 __sys_sendmsg+0x147/0x1f0 net/socket.c:2681
 arch_atomic64_read arch/x86/include/asm/atomic64_64.h:15 [inline]
 raw_atomic64_read include/linux/atomic/atomic-arch-fallback.h:2583 [inline]
 raw_atomic_long_read include/linux/atomic/atomic-long.h:38 [inline]
 atomic_long_read include/linux/atomic/atomic-instrumented.h:3189 [inline]
 unwind_reset_info include/linux/unwind_deferred.h:37 [inline]
 exit_to_user_mode include/linux/irq-entry-common.h:296 [inline]
 syscall_exit_to_user_mode include/linux/entry-common.h:327 [inline]
 do_syscall_64+0xf1/0x530 arch/x86/entry/syscall_64.c:100
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f10c4e29e4d
Code: 28 89 54 24 1c 48 89 74 24 10 89 7c 24 08 e8 ca ee ff ff 8b 54
24 1c 48 8b 74 24 10 41 89 c0 8b 7c 24 08 b8 2e 00 00 00 0f 05 <48> 3d
00 f0 ff ff 77 33 44 89 c7 48 89 44 24 08 e8 fe ee ff ff 48
RSP: 002b:00007f10c4c3ed90 EFLAGS: 00000293 ORIG_RAX: 000000000000002e
RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f10c4e29e4d
RDX: 0000000000000000 RSI: 00007f10c4c3edd0 RDI: 0000000000000003
RBP: 00007f10c4c3ee30 R08: 0000000000000000 R09: 0000000000050000
R10: 0000000000000000 R11: 0000000000000293 R12: 00007fff03a356fe
R13: 00007fff03a356ff R14: 00007f10c4c3efc0 R15: 0000000000022000
 </TASK>
Modules linked in:
---[ end trace 0000000000000000 ]---
RIP: 0010:qdisc_tree_reduce_backlog+0xcf/0x400 net/sched/sch_api.c:810
Code: 4d 8b 67 18 49 8d 7c 24 08 48 89 f8 48 c1 e8 03 80 3c 18 00 0f
85 fb 02 00 00 4d 8b 6c 24 08 4d 8d 65 20 4c 89 e0 48 c1 e8 03 <80> 3c
18 00 0f 85 d4 02 00 00 49 83 7d 20 00 74 48 e8 7b ac 98 fd
RSP: 0018:ffff8881213ef2d8 EFLAGS: 00010212
RAX: 0000000000000004 RBX: dffffc0000000000 RCX: ffffffff83d4b680
RDX: ffff8881213e4680 RSI: 0000000000010000 RDI: ffff888103812a08
RBP: 0000000000010001 R08: 0000000000000000 R09: fffffbfff0d3595c
R10: ffffffff869acae7 R11: 00000000ac3123a9 R12: 0000000000000020
R13: 0000000000000000 R14: 0000000000000000 R15: ffff888121071c00
FS:  00007f10c4c3f700(0000) GS:ffff88834ad0d000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007f10c4bfcfb8 CR3: 0000000121398000 CR4: 0000000000750ef0
PKRU: 55555554
----------------
Code disassembly (best guess):
   0: 4d 8b 67 18           mov    0x18(%r15),%r12
   4: 49 8d 7c 24 08        lea    0x8(%r12),%rdi
   9: 48 89 f8              mov    %rdi,%rax
   c: 48 c1 e8 03           shr    $0x3,%rax
  10: 80 3c 18 00           cmpb   $0x0,(%rax,%rbx,1)
  14: 0f 85 fb 02 00 00     jne    0x315
  1a: 4d 8b 6c 24 08        mov    0x8(%r12),%r13
  1f: 4d 8d 65 20           lea    0x20(%r13),%r12
  23: 4c 89 e0              mov    %r12,%rax
  26: 48 c1 e8 03           shr    $0x3,%rax
* 2a: 80 3c 18 00           cmpb   $0x0,(%rax,%rbx,1) <-- trapping instruction
  2e: 0f 85 d4 02 00 00     jne    0x308
  34: 49 83 7d 20 00        cmpq   $0x0,0x20(%r13)
  39: 74 48                 je     0x83
  3b: e8 7b ac 98 fd        call   0xfd98acbb
R13: 0000000000000000 R14: 0000000000000000 R15: ffff888121071c00
FS:  00007f10c4c3f700(0000) GS:ffff88834ad0d000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007f10c4bfcfb8 CR3: 0000000121398000 CR4: 0000000000750ef0
PKRU: 55555554
Kernel panic - not syncing: Fatal exception in interrupt
Kernel Offset: disabled
---[ end Kernel panic - not syncing: Fatal exception in interrupt ]---
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help