GangMin Kim
unread,Feb 22, 2026, 8:33:22 PM (11 hours ago) Feb 22Sign in to reply to author
Sign in to forward
You do not have permission to delete messages in this group
Either email addresses are anonymous for this group or you need the view member email addresses permission to view the original message
to da...@davemloft.net, Eric Dumazet, Jamal Hadi Salim, Jiri Pirko, ku...@kernel.org, net...@vger.kernel.org, Paolo Abeni, ho...@kernel.org, linux-...@vger.kernel.org, syzk...@googlegroups.com
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().
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.
```c
// 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.
```c
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.
```c
tc_modify_qdisc
__tc_modify_qdisc
qdisc_graft
qdisc_replace
qdisc_purge_queue
qdisc_tree_reduce_backlog
```
```c
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 ]---