Skip to content

Conversation

@hexfoureight
Copy link
Contributor

No description provided.

@@ -0,0 +1,22 @@
A vulnerability in the traffic control subsystem's netem qdisc (`CONFIG_NET_SCH_NETEM ` in the kernel config) can lead to a use-after-free. If a netem qdisc has a child qdisc, `netem_dequeue()` will attempt to enqueue a packet to it (for every other qdisc type this happens during enqueue; netem does it this way to allow a per packet delay). If this `qdisc_enqueue()` call returns`__NETEM_XMIT_STOLEN`, the packet will be dropped but the parent qdisc's `q.qlen` will not be updated. Then `qlen_notify()` may be skipped on the parent during destruction, leaving a dangling pointer for some classful qdiscs like DRR.
Copy link
Collaborator

@artmetla artmetla Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add information on capabilities needed (if any?) to trigger the vulnerability. Do you use user namespaces?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated vulnerability.md to specify that user namespaces are needed.


## Triggering the Vulnerability

The `trigger_vuln()` function triggers the vulnerability under the passed class. A buggy netem qdisc is added as a child of the class and then removed after the vulnerability has been triggered:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So before trigger_vuln is called, we have such a situation:

[Some DRR qdisc] → [DRR class "parent"] → [default/existing child qdisc]

Is this correct?

When we call void trigger_vuln (int parent), we replace the child qdisc, which makes (those are first 2 calls in function):

[DRR qdisc] → [DRR class "parent"] → [netem] → [drr] (empty, no classes)

and after that we add filter and class like this

[DRR qdisc] → [DRR class "parent"] → [netem] → [drr] → [drr class 1]
                                                          ↑
                                                  (filter with TC_ACT_STOLEN) 

Then we send packet with loopback_send(). And following happens:

  1. Packet arrives, gets classified to parent class (due to outer filter set up before trigger_vuln)
  2. parent is added to active list of its parent DRR qdisc
  3. Packet travels: parentnetemdrrdrr class 1
  4. TC_ACT_STOLEN action drops the packet
  5. Bug: parent stays on active list even though packet was dropped

Finally, we call tc_del_qd(parent); which deletes the qdisc attached to "parent" (the netem). This deletes the netem qdisc (child of parent), which causes the parent class to be freed. And

[DRR qdisc] still has active_list pointing to → [FREED "parent" class]
                                                        ↑
                                                  USE-AFTER-FREE!

Is my logic correct? Could you add more information / explanations on how we actually trigger the vulnerability?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is my logic correct? Could you add more information / explanations on how we actually trigger the vulnerability?

It's all correct except for this part:

Finally, we call tc_del_qd(parent); which deletes the qdisc attached to "parent" (the netem). This deletes the netem qdisc (child of parent), which causes the parent class to be freed.

parent is not freed in trigger_vuln(), it is only put in a buggy state such that it will remain on the active list after being freed. The free happens when the class itself is deleted, here:

tc_del_cl(b1); // b1 freed
tbfp = b2 = drr_spray_and_find(b2);

and here:

tc_del_cl(b2); // b2 freed
b3 = drr_spray_and_find(b3);

I've updated the the docs to make this clearer.

I also noticed that the DRR qdisc does not actually need to have a class for the TC_ACT_STOLEN filter to work and updated trigger_vuln() to not create the class.

Copy link
Collaborator

@artmetla artmetla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @hexfoureight

Thanks for submitting the exploit and writeup. Please, have a look at my comments and fix them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants