CVE-2026-45845
Description
In the Linux kernel, the following vulnerability has been resolved:
net/sched: taprio: fix NULL pointer dereference in class dump
When a TAPRIO child qdisc is deleted via RTM_DELQDISC, taprio_graft() is called with new == NULL and stores NULL into q->qdiscs[cl - 1]. Subsequent RTM_GETTCLASS dump operations walk all classes via taprio_walk() and call taprio_dump_class(), which calls taprio_leaf() returning the NULL pointer, then dereferences it to read child->handle, causing a kernel NULL pointer dereference.
The bug is reachable with namespace-scoped CAP_NET_ADMIN on any kernel with CONFIG_NET_SCH_TAPRIO enabled. On systems with unprivileged user namespaces enabled, an unprivileged local user can trigger a kernel panic by creating a taprio qdisc inside a new network namespace, grafting an explicit child qdisc, deleting it, and requesting a class dump. The RTM_GETTCLASS dump itself requires no capability.
Oops: general protection fault, probably for non-canonical address 0xdffffc0000000007: 0000 [#1] SMP KASAN NOPTI KASAN: null-ptr-deref in range [0x0000000000000038-0x000000000000003f] RIP: 0010:taprio_dump_class (net/sched/sch_taprio.c:2478) Call Trace:
tc_fill_tclass (net/sched/sch_api.c:1966) qdisc_class_dump (net/sched/sch_api.c:2326) taprio_walk (net/sched/sch_taprio.c:2514) tc_dump_tclass_qdisc (net/sched/sch_api.c:2352) tc_dump_tclass_root (net/sched/sch_api.c:2370) tc_dump_tclass (net/sched/sch_api.c:2431) rtnl_dumpit (net/core/rtnetlink.c:6864) netlink_dump (net/netlink/af_netlink.c:2325) rtnetlink_rcv_msg (net/core/rtnetlink.c:6959) netlink_rcv_skb (net/netlink/af_netlink.c:2550)
Fix this by substituting &noop_qdisc when new is NULL in taprio_graft(), a common pattern used by other qdiscs (e.g., multiq_graft()) to ensure the q->qdiscs[] slots are never NULL. This makes control-plane dump paths safe without requiring individual NULL checks.
Since the data-plane paths (taprio_enqueue and taprio_dequeue_from_txq) previously had explicit NULL guards that would drop/skip the packet cleanly, update those checks to test for &noop_qdisc instead. Without this, packets would reach taprio_enqueue_one() which increments the root qdisc's qlen and backlog before calling the child's enqueue; noop_qdisc drops the packet but those counters are never rolled back, permanently inflating the root qdisc's statistics.
After this change *old can be a valid qdisc, NULL, or &noop_qdisc. Only call qdisc_put(*old) in the first case to avoid decreasing noop_qdisc's refcount, which was never increased.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
In the Linux taprio qdisc, deleting a child and then requesting a class dump causes a NULL pointer dereference, leading to a kernel panic.
Vulnerability
In the Linux kernel's TAPRIO qdisc (net/sched/sch_taprio.c), the function taprio_graft() writes NULL into q->qdiscs[cl-1] when a child qdisc is deleted via RTM_DELQDISC (new == NULL) [1][2][3][4]. Subsequent RTM_GETTCLASS dump operations call taprio_leaf() which returns the NULL pointer, and taprio_dump_class() dereferences it to read child->handle, causing a NULL pointer dereference. This affects kernels with CONFIG_NET_SCH_TAPRIO enabled, prior to the fix commits.
Exploitation
An attacker with namespace-scoped CAP_NET_ADMIN (or unprivileged user namespace if enabled) can trigger the bug by creating a taprio qdisc inside a network namespace, grafting an explicit child qdisc, deleting that child via RTM_DELQDISC, and then requesting an RTM_GETTCLASS dump. The dump operation itself requires no capability, so the attacker only needs the initial privilege to set up the qdisc.
Impact
The NULL pointer dereference results in a kernel panic (general protection fault) [KASAN report: null-ptr-deref in taprio_dump_class]. This leads to a denial of service (system crash). No privilege escalation or data corruption is indicated.
Mitigation
The fix is to substitute &noop_qdisc when new is NULL in taprio_graft(), a common pattern used by other qdiscs (e.g., multiq_graft()). This ensures q->qdiscs[] slots are never NULL, making dump paths safe. The fix has been applied to the Linux stable tree via commits [1], [2], [3], and [4]. Users should update to a kernel version containing these commits. If unprivileged user namespaces are disabled, the bug is not exploitable by unprivileged local users.
AI Insight generated on May 27, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1Patches
10d02e2fbf60denet/sched: taprio: fix NULL pointer dereference in class dump
1 file changed · +8 −6
net/sched/sch_taprio.c+8 −6 modifieddiff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c index 366eb7627b30d7..f1709efb5f04e7 100644 --- a/net/sched/sch_taprio.c +++ b/net/sched/sch_taprio.c @@ -633,7 +633,7 @@ static int taprio_enqueue(struct sk_buff *skb, struct Qdisc *sch, queue = skb_get_queue_mapping(skb); child = q->qdiscs[queue]; - if (unlikely(!child)) + if (unlikely(child == &noop_qdisc)) return qdisc_drop(skb, sch, to_free); if (taprio_skb_exceeds_queue_max_sdu(sch, skb)) { @@ -716,7 +716,7 @@ static struct sk_buff *taprio_dequeue_from_txq(struct Qdisc *sch, int txq, int len; u8 tc; - if (unlikely(!child)) + if (unlikely(child == &noop_qdisc)) return NULL; if (TXTIME_ASSIST_IS_ENABLED(q->flags)) @@ -2191,6 +2191,9 @@ static int taprio_graft(struct Qdisc *sch, unsigned long cl, if (!dev_queue) return -EINVAL; + if (!new) + new = &noop_qdisc; + if (dev->flags & IFF_UP) dev_deactivate(dev); @@ -2204,14 +2207,14 @@ static int taprio_graft(struct Qdisc *sch, unsigned long cl, *old = q->qdiscs[cl - 1]; if (FULL_OFFLOAD_IS_ENABLED(q->flags)) { WARN_ON_ONCE(dev_graft_qdisc(dev_queue, new) != *old); - if (new) + if (new != &noop_qdisc) qdisc_refcount_inc(new); - if (*old) + if (*old && *old != &noop_qdisc) qdisc_put(*old); } q->qdiscs[cl - 1] = new; - if (new) + if (new != &noop_qdisc) new->flags |= TCQ_F_ONETXQUEUE | TCQ_F_NOPARENT; if (dev->flags & IFF_UP) -- cgit 1.3-korg
8f1ff8866cb9net/sched: taprio: fix NULL pointer dereference in class dump
1 file changed · +8 −6
net/sched/sch_taprio.c+8 −6 modifieddiff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c index 0316f2dee06ac8..3c85ef1ef4818c 100644 --- a/net/sched/sch_taprio.c +++ b/net/sched/sch_taprio.c @@ -634,7 +634,7 @@ static int taprio_enqueue(struct sk_buff *skb, struct Qdisc *sch, queue = skb_get_queue_mapping(skb); child = q->qdiscs[queue]; - if (unlikely(!child)) + if (unlikely(child == &noop_qdisc)) return qdisc_drop(skb, sch, to_free); if (taprio_skb_exceeds_queue_max_sdu(sch, skb)) { @@ -717,7 +717,7 @@ static struct sk_buff *taprio_dequeue_from_txq(struct Qdisc *sch, int txq, int len; u8 tc; - if (unlikely(!child)) + if (unlikely(child == &noop_qdisc)) return NULL; if (TXTIME_ASSIST_IS_ENABLED(q->flags)) @@ -2184,6 +2184,9 @@ static int taprio_graft(struct Qdisc *sch, unsigned long cl, if (!dev_queue) return -EINVAL; + if (!new) + new = &noop_qdisc; + if (dev->flags & IFF_UP) dev_deactivate(dev); @@ -2197,14 +2200,14 @@ static int taprio_graft(struct Qdisc *sch, unsigned long cl, *old = q->qdiscs[cl - 1]; if (FULL_OFFLOAD_IS_ENABLED(q->flags)) { WARN_ON_ONCE(dev_graft_qdisc(dev_queue, new) != *old); - if (new) + if (new != &noop_qdisc) qdisc_refcount_inc(new); - if (*old) + if (*old && *old != &noop_qdisc) qdisc_put(*old); } q->qdiscs[cl - 1] = new; - if (new) + if (new != &noop_qdisc) new->flags |= TCQ_F_ONETXQUEUE | TCQ_F_NOPARENT; if (dev->flags & IFF_UP) -- cgit 1.3-korg
3d07ca5c0faenet/sched: taprio: fix NULL pointer dereference in class dump
1 file changed · +8 −6
net/sched/sch_taprio.c+8 −6 modifieddiff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c index a47a09d764001d..45245157e00a69 100644 --- a/net/sched/sch_taprio.c +++ b/net/sched/sch_taprio.c @@ -634,7 +634,7 @@ static int taprio_enqueue(struct sk_buff *skb, struct Qdisc *sch, queue = skb_get_queue_mapping(skb); child = q->qdiscs[queue]; - if (unlikely(!child)) + if (unlikely(child == &noop_qdisc)) return qdisc_drop(skb, sch, to_free); if (taprio_skb_exceeds_queue_max_sdu(sch, skb)) { @@ -717,7 +717,7 @@ static struct sk_buff *taprio_dequeue_from_txq(struct Qdisc *sch, int txq, int len; u8 tc; - if (unlikely(!child)) + if (unlikely(child == &noop_qdisc)) return NULL; if (TXTIME_ASSIST_IS_ENABLED(q->flags)) @@ -2184,6 +2184,9 @@ static int taprio_graft(struct Qdisc *sch, unsigned long cl, if (!dev_queue) return -EINVAL; + if (!new) + new = &noop_qdisc; + if (dev->flags & IFF_UP) dev_deactivate(dev, false); @@ -2197,14 +2200,14 @@ static int taprio_graft(struct Qdisc *sch, unsigned long cl, *old = q->qdiscs[cl - 1]; if (FULL_OFFLOAD_IS_ENABLED(q->flags)) { WARN_ON_ONCE(dev_graft_qdisc(dev_queue, new) != *old); - if (new) + if (new != &noop_qdisc) qdisc_refcount_inc(new); - if (*old) + if (*old && *old != &noop_qdisc) qdisc_put(*old); } q->qdiscs[cl - 1] = new; - if (new) + if (new != &noop_qdisc) new->flags |= TCQ_F_ONETXQUEUE | TCQ_F_NOPARENT; if (dev->flags & IFF_UP) -- cgit 1.3-korg
ec2501e361b0net/sched: taprio: fix NULL pointer dereference in class dump
1 file changed · +8 −6
net/sched/sch_taprio.c+8 −6 modifieddiff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c index 6df93d5c2e9d9f..c14f9e2d8dc51a 100644 --- a/net/sched/sch_taprio.c +++ b/net/sched/sch_taprio.c @@ -644,7 +644,7 @@ static int taprio_enqueue(struct sk_buff *skb, struct Qdisc *sch, queue = skb_get_queue_mapping(skb); child = q->qdiscs[queue]; - if (unlikely(!child)) + if (unlikely(child == &noop_qdisc)) return qdisc_drop(skb, sch, to_free); if (taprio_skb_exceeds_queue_max_sdu(sch, skb)) { @@ -727,7 +727,7 @@ static struct sk_buff *taprio_dequeue_from_txq(struct Qdisc *sch, int txq, int len; u8 tc; - if (unlikely(!child)) + if (unlikely(child == &noop_qdisc)) return NULL; if (TXTIME_ASSIST_IS_ENABLED(q->flags)) @@ -2214,6 +2214,9 @@ static int taprio_graft(struct Qdisc *sch, unsigned long cl, if (!dev_queue) return -EINVAL; + if (!new) + new = &noop_qdisc; + if (dev->flags & IFF_UP) dev_deactivate(dev); @@ -2227,14 +2230,14 @@ static int taprio_graft(struct Qdisc *sch, unsigned long cl, *old = q->qdiscs[cl - 1]; if (FULL_OFFLOAD_IS_ENABLED(q->flags)) { WARN_ON_ONCE(dev_graft_qdisc(dev_queue, new) != *old); - if (new) + if (new != &noop_qdisc) qdisc_refcount_inc(new); - if (*old) + if (*old && *old != &noop_qdisc) qdisc_put(*old); } q->qdiscs[cl - 1] = new; - if (new) + if (new != &noop_qdisc) new->flags |= TCQ_F_ONETXQUEUE | TCQ_F_NOPARENT; if (dev->flags & IFF_UP) -- cgit 1.3-korg
48b26d48e762net/sched: taprio: fix NULL pointer dereference in class dump
1 file changed · +8 −6
net/sched/sch_taprio.c+8 −6 modifieddiff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c index 91b85360f80912..b3481cafa6ecaf 100644 --- a/net/sched/sch_taprio.c +++ b/net/sched/sch_taprio.c @@ -633,7 +633,7 @@ static int taprio_enqueue(struct sk_buff *skb, struct Qdisc *sch, queue = skb_get_queue_mapping(skb); child = q->qdiscs[queue]; - if (unlikely(!child)) + if (unlikely(child == &noop_qdisc)) return qdisc_drop(skb, sch, to_free); if (taprio_skb_exceeds_queue_max_sdu(sch, skb)) { @@ -716,7 +716,7 @@ static struct sk_buff *taprio_dequeue_from_txq(struct Qdisc *sch, int txq, int len; u8 tc; - if (unlikely(!child)) + if (unlikely(child == &noop_qdisc)) return NULL; if (TXTIME_ASSIST_IS_ENABLED(q->flags)) @@ -2185,6 +2185,9 @@ static int taprio_graft(struct Qdisc *sch, unsigned long cl, if (!dev_queue) return -EINVAL; + if (!new) + new = &noop_qdisc; + if (dev->flags & IFF_UP) dev_deactivate(dev); @@ -2198,14 +2201,14 @@ static int taprio_graft(struct Qdisc *sch, unsigned long cl, *old = q->qdiscs[cl - 1]; if (FULL_OFFLOAD_IS_ENABLED(q->flags)) { WARN_ON_ONCE(dev_graft_qdisc(dev_queue, new) != *old); - if (new) + if (new != &noop_qdisc) qdisc_refcount_inc(new); - if (*old) + if (*old && *old != &noop_qdisc) qdisc_put(*old); } q->qdiscs[cl - 1] = new; - if (new) + if (new != &noop_qdisc) new->flags |= TCQ_F_ONETXQUEUE | TCQ_F_NOPARENT; if (dev->flags & IFF_UP) -- cgit 1.3-korg
48b26d48e762net/sched: taprio: fix NULL pointer dereference in class dump
1 file changed · +8 −6
net/sched/sch_taprio.c+8 −6 modifieddiff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c index 91b85360f80912..b3481cafa6ecaf 100644 --- a/net/sched/sch_taprio.c +++ b/net/sched/sch_taprio.c @@ -633,7 +633,7 @@ static int taprio_enqueue(struct sk_buff *skb, struct Qdisc *sch, queue = skb_get_queue_mapping(skb); child = q->qdiscs[queue]; - if (unlikely(!child)) + if (unlikely(child == &noop_qdisc)) return qdisc_drop(skb, sch, to_free); if (taprio_skb_exceeds_queue_max_sdu(sch, skb)) { @@ -716,7 +716,7 @@ static struct sk_buff *taprio_dequeue_from_txq(struct Qdisc *sch, int txq, int len; u8 tc; - if (unlikely(!child)) + if (unlikely(child == &noop_qdisc)) return NULL; if (TXTIME_ASSIST_IS_ENABLED(q->flags)) @@ -2185,6 +2185,9 @@ static int taprio_graft(struct Qdisc *sch, unsigned long cl, if (!dev_queue) return -EINVAL; + if (!new) + new = &noop_qdisc; + if (dev->flags & IFF_UP) dev_deactivate(dev); @@ -2198,14 +2201,14 @@ static int taprio_graft(struct Qdisc *sch, unsigned long cl, *old = q->qdiscs[cl - 1]; if (FULL_OFFLOAD_IS_ENABLED(q->flags)) { WARN_ON_ONCE(dev_graft_qdisc(dev_queue, new) != *old); - if (new) + if (new != &noop_qdisc) qdisc_refcount_inc(new); - if (*old) + if (*old && *old != &noop_qdisc) qdisc_put(*old); } q->qdiscs[cl - 1] = new; - if (new) + if (new != &noop_qdisc) new->flags |= TCQ_F_ONETXQUEUE | TCQ_F_NOPARENT; if (dev->flags & IFF_UP) -- cgit 1.3-korg
3d07ca5c0faenet/sched: taprio: fix NULL pointer dereference in class dump
1 file changed · +8 −6
net/sched/sch_taprio.c+8 −6 modifieddiff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c index a47a09d764001d..45245157e00a69 100644 --- a/net/sched/sch_taprio.c +++ b/net/sched/sch_taprio.c @@ -634,7 +634,7 @@ static int taprio_enqueue(struct sk_buff *skb, struct Qdisc *sch, queue = skb_get_queue_mapping(skb); child = q->qdiscs[queue]; - if (unlikely(!child)) + if (unlikely(child == &noop_qdisc)) return qdisc_drop(skb, sch, to_free); if (taprio_skb_exceeds_queue_max_sdu(sch, skb)) { @@ -717,7 +717,7 @@ static struct sk_buff *taprio_dequeue_from_txq(struct Qdisc *sch, int txq, int len; u8 tc; - if (unlikely(!child)) + if (unlikely(child == &noop_qdisc)) return NULL; if (TXTIME_ASSIST_IS_ENABLED(q->flags)) @@ -2184,6 +2184,9 @@ static int taprio_graft(struct Qdisc *sch, unsigned long cl, if (!dev_queue) return -EINVAL; + if (!new) + new = &noop_qdisc; + if (dev->flags & IFF_UP) dev_deactivate(dev, false); @@ -2197,14 +2200,14 @@ static int taprio_graft(struct Qdisc *sch, unsigned long cl, *old = q->qdiscs[cl - 1]; if (FULL_OFFLOAD_IS_ENABLED(q->flags)) { WARN_ON_ONCE(dev_graft_qdisc(dev_queue, new) != *old); - if (new) + if (new != &noop_qdisc) qdisc_refcount_inc(new); - if (*old) + if (*old && *old != &noop_qdisc) qdisc_put(*old); } q->qdiscs[cl - 1] = new; - if (new) + if (new != &noop_qdisc) new->flags |= TCQ_F_ONETXQUEUE | TCQ_F_NOPARENT; if (dev->flags & IFF_UP) -- cgit 1.3-korg
8f1ff8866cb9net/sched: taprio: fix NULL pointer dereference in class dump
1 file changed · +8 −6
net/sched/sch_taprio.c+8 −6 modifieddiff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c index 0316f2dee06ac8..3c85ef1ef4818c 100644 --- a/net/sched/sch_taprio.c +++ b/net/sched/sch_taprio.c @@ -634,7 +634,7 @@ static int taprio_enqueue(struct sk_buff *skb, struct Qdisc *sch, queue = skb_get_queue_mapping(skb); child = q->qdiscs[queue]; - if (unlikely(!child)) + if (unlikely(child == &noop_qdisc)) return qdisc_drop(skb, sch, to_free); if (taprio_skb_exceeds_queue_max_sdu(sch, skb)) { @@ -717,7 +717,7 @@ static struct sk_buff *taprio_dequeue_from_txq(struct Qdisc *sch, int txq, int len; u8 tc; - if (unlikely(!child)) + if (unlikely(child == &noop_qdisc)) return NULL; if (TXTIME_ASSIST_IS_ENABLED(q->flags)) @@ -2184,6 +2184,9 @@ static int taprio_graft(struct Qdisc *sch, unsigned long cl, if (!dev_queue) return -EINVAL; + if (!new) + new = &noop_qdisc; + if (dev->flags & IFF_UP) dev_deactivate(dev); @@ -2197,14 +2200,14 @@ static int taprio_graft(struct Qdisc *sch, unsigned long cl, *old = q->qdiscs[cl - 1]; if (FULL_OFFLOAD_IS_ENABLED(q->flags)) { WARN_ON_ONCE(dev_graft_qdisc(dev_queue, new) != *old); - if (new) + if (new != &noop_qdisc) qdisc_refcount_inc(new); - if (*old) + if (*old && *old != &noop_qdisc) qdisc_put(*old); } q->qdiscs[cl - 1] = new; - if (new) + if (new != &noop_qdisc) new->flags |= TCQ_F_ONETXQUEUE | TCQ_F_NOPARENT; if (dev->flags & IFF_UP) -- cgit 1.3-korg
d02e2fbf60denet/sched: taprio: fix NULL pointer dereference in class dump
1 file changed · +8 −6
net/sched/sch_taprio.c+8 −6 modifieddiff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c index 366eb7627b30d7..f1709efb5f04e7 100644 --- a/net/sched/sch_taprio.c +++ b/net/sched/sch_taprio.c @@ -633,7 +633,7 @@ static int taprio_enqueue(struct sk_buff *skb, struct Qdisc *sch, queue = skb_get_queue_mapping(skb); child = q->qdiscs[queue]; - if (unlikely(!child)) + if (unlikely(child == &noop_qdisc)) return qdisc_drop(skb, sch, to_free); if (taprio_skb_exceeds_queue_max_sdu(sch, skb)) { @@ -716,7 +716,7 @@ static struct sk_buff *taprio_dequeue_from_txq(struct Qdisc *sch, int txq, int len; u8 tc; - if (unlikely(!child)) + if (unlikely(child == &noop_qdisc)) return NULL; if (TXTIME_ASSIST_IS_ENABLED(q->flags)) @@ -2191,6 +2191,9 @@ static int taprio_graft(struct Qdisc *sch, unsigned long cl, if (!dev_queue) return -EINVAL; + if (!new) + new = &noop_qdisc; + if (dev->flags & IFF_UP) dev_deactivate(dev); @@ -2204,14 +2207,14 @@ static int taprio_graft(struct Qdisc *sch, unsigned long cl, *old = q->qdiscs[cl - 1]; if (FULL_OFFLOAD_IS_ENABLED(q->flags)) { WARN_ON_ONCE(dev_graft_qdisc(dev_queue, new) != *old); - if (new) + if (new != &noop_qdisc) qdisc_refcount_inc(new); - if (*old) + if (*old && *old != &noop_qdisc) qdisc_put(*old); } q->qdiscs[cl - 1] = new; - if (new) + if (new != &noop_qdisc) new->flags |= TCQ_F_ONETXQUEUE | TCQ_F_NOPARENT; if (dev->flags & IFF_UP) -- cgit 1.3-korg
ec2501e361b0net/sched: taprio: fix NULL pointer dereference in class dump
1 file changed · +8 −6
net/sched/sch_taprio.c+8 −6 modifieddiff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c index 6df93d5c2e9d9f..c14f9e2d8dc51a 100644 --- a/net/sched/sch_taprio.c +++ b/net/sched/sch_taprio.c @@ -644,7 +644,7 @@ static int taprio_enqueue(struct sk_buff *skb, struct Qdisc *sch, queue = skb_get_queue_mapping(skb); child = q->qdiscs[queue]; - if (unlikely(!child)) + if (unlikely(child == &noop_qdisc)) return qdisc_drop(skb, sch, to_free); if (taprio_skb_exceeds_queue_max_sdu(sch, skb)) { @@ -727,7 +727,7 @@ static struct sk_buff *taprio_dequeue_from_txq(struct Qdisc *sch, int txq, int len; u8 tc; - if (unlikely(!child)) + if (unlikely(child == &noop_qdisc)) return NULL; if (TXTIME_ASSIST_IS_ENABLED(q->flags)) @@ -2214,6 +2214,9 @@ static int taprio_graft(struct Qdisc *sch, unsigned long cl, if (!dev_queue) return -EINVAL; + if (!new) + new = &noop_qdisc; + if (dev->flags & IFF_UP) dev_deactivate(dev); @@ -2227,14 +2230,14 @@ static int taprio_graft(struct Qdisc *sch, unsigned long cl, *old = q->qdiscs[cl - 1]; if (FULL_OFFLOAD_IS_ENABLED(q->flags)) { WARN_ON_ONCE(dev_graft_qdisc(dev_queue, new) != *old); - if (new) + if (new != &noop_qdisc) qdisc_refcount_inc(new); - if (*old) + if (*old && *old != &noop_qdisc) qdisc_put(*old); } q->qdiscs[cl - 1] = new; - if (new) + if (new != &noop_qdisc) new->flags |= TCQ_F_ONETXQUEUE | TCQ_F_NOPARENT; if (dev->flags & IFF_UP) -- cgit 1.3-korg
Vulnerability mechanics
Root cause
"taprio_graft() stores NULL into q->qdiscs[] when a child qdisc is deleted, and taprio_dump_class() dereferences that NULL pointer without a guard."
Attack vector
An attacker with namespace-scoped CAP_NET_ADMIN creates a taprio qdisc inside a network namespace, grafts an explicit child qdisc, then deletes it via RTM_DELQDISC. This causes `taprio_graft()` to store NULL into the `q->qdiscs[]` slot [patch_id=2654152]. A subsequent RTM_GETTCLASS dump (which requires no capability) walks all classes via `taprio_walk()` and calls `taprio_dump_class()`, which dereferences the NULL pointer from `taprio_leaf()` to read `child->handle`, triggering a kernel NULL pointer dereference and panic. On systems with unprivileged user namespaces enabled, an unprivileged local user can exploit this.
Affected code
The vulnerability is in `net/sched/sch_taprio.c` in the `taprio_graft()`, `taprio_enqueue()`, `taprio_dequeue_from_txq()`, and `taprio_dump_class()` functions. When a child qdisc is deleted via RTM_DELQDISC, `taprio_graft()` stores NULL into `q->qdiscs[cl - 1]`. The dump path in `taprio_dump_class()` (at line 2478) calls `taprio_leaf()` which returns this NULL pointer, then dereferences it to read `child->handle`, causing the crash [patch_id=2654152].
What the fix does
The patch applies a pattern used by other qdiscs (e.g., `multiq_graft()`): in `taprio_graft()`, when `new` is NULL, substitute `&noop_qdisc` instead. This ensures `q->qdiscs[]` slots are never NULL, making control-plane dump paths safe without per-function NULL checks [patch_id=2654152]. The data-plane guards in `taprio_enqueue()` and `taprio_dequeue_from_txq()` are updated from `if (unlikely(!child))` to `if (unlikely(child == &noop_qdisc))` to correctly detect the sentinel. The refcount and flag-setting logic in `taprio_graft()` is also updated to skip `&noop_qdisc` to avoid permanently inflating root qdisc statistics and to prevent decreasing `noop_qdisc`'s refcount.
Preconditions
- authThe attacker must have CAP_NET_ADMIN within a network namespace (or root on systems without unprivileged user namespaces).
- configThe kernel must have CONFIG_NET_SCH_TAPRIO enabled.
- inputThe attacker must be able to create a taprio qdisc, graft a child qdisc, delete it, and then trigger an RTM_GETTCLASS dump.
- authOn systems with unprivileged user namespaces, an unprivileged local user can satisfy the CAP_NET_ADMIN requirement inside a new namespace.
Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- git.kernel.org/stable/c/3d07ca5c0fae311226f737963984bd94bb159a87nvd
- git.kernel.org/stable/c/48b26d48e76221dc90b02bf5428bab53643461canvd
- git.kernel.org/stable/c/8f1ff8866cb9f655e5faea6994eb902960be8e04nvd
- git.kernel.org/stable/c/d02e2fbf60de46678e2ea698a6a904fd21e1cc31nvd
- git.kernel.org/stable/c/ec2501e361b08b50bcb1e7b3253fc861abbda28dnvd
News mentions
0No linked articles in our index yet.