Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
291 changes: 291 additions & 0 deletions pocs/linux/kernelctf/CVE-2025-38177_mitigation/docs/exploit.md

Large diffs are not rendered by default.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions pocs/linux/kernelctf/CVE-2025-38177_mitigation/docs/paths.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
A vulnerability in the traffic control subsystem's HFSC, QFQ, and netem qdiscs(`CONFIG_NET_SCH_HFSC`,` CONFIG_NET_SCH_QFQ`, and `CONFIG_NET_SCH_NETEM` in the kernel config) can lead to a use-after-free. The vulnerability occurs when an HFSC class has a QFQ child which has a netem child. It is possible to remove the HFSC class's `el_node` from its RB tree multiple times in a row, which can cause stale pointers in `el_node`'s child fields to be inserted into the tree.

When a QFQ qdisc is enqueueing its second packet, it may call `peek()` on its child qdisc. If it has a netem child, this will call its dequeue method. `netem_dequeue()` will attempt to enqueue a packet to its child, and call `qdisc_tree_reduce_backlog()` to update the `qlen`s of its ancestors if the packet is dropped. The problem is that this happens while the packet is still being enqueued, so the ancestors have not yet updated their `qlen`s to account for it. They will update their `qlen`s once `qfq_enqueue()` returns, leaving them accurate, but the `qdisc_tree_reduce_backlog()` call may have already removed them from their active lists if they only had one packet enqueued.

The ancestors of the QFQ qdisc will then no longer be on their active lists even though their `qlen` is 1. Sending another packet will repeat this process, causing `qdisc_tree_reduce_backlog()` to remove them from their active lists when they are not actually on them.

If the active list is a linked list, this will have no effect. The HFSC and HTB qdiscs use RB trees for their active lists instead, but the HTB qdisc was already protected from double removals. The HFSC qdisc was not, making it possible to insert a stale child into its RB tree of active classes.

This can be done by creating two HFSC classes, `1:1` and `1:2`, with the vulnerable hierarchy being placed under `1:1`. The leaves of both classes are configured to delay packets, letting us control when they are removed from the active RB tree. Then:

- A packet is sent to `1:1`, adding it to the active RB tree.
- A packet is sent to `1:2`, adding it to the active RB tree as a child of `1:1`.
- Another packet is sent to `1:1`, triggering the vulnerability and removing it from the RB tree. It retains a pointer to `1:2` in its `el_node`'s child field.
- `1:2` is removed from the active RB tree.
- Another packet is sent `1:1`. The vulnerability will be triggered again, calling `rb_erase()` on the `el_node` containing a stale pointer to `1:2`. The root of the active RB tree is now `1:2`.
- `1:2` is freed, leaving a dangling pointer to it from the active RB tree.
- Future `hfsc_dequeue()` calls will access this dangling pointer, causing a use-after-free.

This way of double removing an HFSC class became possible after commit ` 462dbc9101acd (pkt_sched: QFQ Plus: fair-queueing service at DRR cost)` in version `3.8.0`. Protection from double removal was added in commit ` 51eb3b65544c (sch_hfsc: make hfsc_qlen_notify() idempotent)` in version `6.14.6`.

The vulnerability requires `CAP_NET_ADMIN` and can therefore only be exploited for privilege escalation from a user namespace.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CFLAGS = -Wno-incompatible-pointer-types -Wno-format -Wno-address-of-packed-member -static -D MITIGATION

exploit: exploit.c
gcc $(CFLAGS) -o $@ $<

run:
./exploit
Binary file not shown.
Loading