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
521 changes: 521 additions & 0 deletions pocs/linux/kernelctf/CVE-2025-37798_lts_cos_mitigation/docs/exploit.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# CVE-2025-37798
## Overview
- Requirements:
- Capabilites: CAP_NET_ADMIN
- Kernel configuration: CONFIG_NET_SCHED=y CONFIG_NET_SCH_FQ_CODEL=y
- User namespaces required: Yes
- Introduced by: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=4b549a2ef4bef9965d97cbd992ba67930cd3e0fe
- Fixed by: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=342debc12183b51773b3345ba267e9263bdfaaef
- Affected Version: v3.5-rc1 - v6.15-rc1
- Affected Component: netfilter
- Syscall to disable: unshare
- URL: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2025-37798
- Cause: Use After Free
- Description: A use-after-free vulnerability in the Linux Kernel net scheduler subsystem can be exploited to achieve local privilege escalation. By manipulating the network interface's maximum transmission unit, it is possible to drop packets in fq_codel_dequeue without notifying a parent qdisc. This corrupts internal qlen tracking, allowing classes to be removed while still referenced in their parent, causing a use-after-free vulnerability. We recommend upgrading past commit 342debc12183b51773b3345ba267e9263bdfaaef


## Analysis
In the net/sched fq_codel qdisc, dropped packets are improperly tracked. This discrepancy can be abused to obtain a UAF on a parent scheduler. This UAF can be exploited to achieve LPE.

### Details
The vulnerability lies in the `fq_codel_dequeue()` function.
```c
skb = codel_dequeue(sch, &sch->qstats.backlog, &q->cparams,
&flow->cvars, &q->cstats, qdisc_pkt_len,
codel_get_enqueue_time, drop_func, dequeue_func); // [1]

if (!skb) {
if ((head == &q->new_flows) && !list_empty(&q->old_flows))
list_move_tail(&flow->flowchain, &q->old_flows);
else
list_del_init(&flow->flowchain);
goto begin;
}
qdisc_bstats_update(sch, skb);
flow->deficit -= qdisc_pkt_len(skb);

if (q->cstats.drop_count && sch->q.qlen) { // [2]
qdisc_tree_reduce_backlog(sch, q->cstats.drop_count, // [3]
q->cstats.drop_len);
q->cstats.drop_count = 0;
q->cstats.drop_len = 0;
}
return skb;
```

At [1], `codel_dequeue()` will dequeue a single packet. However, if certain conditions ([4]) are met, it will also drop a number of packets ([5]).

```c
drop = codel_should_drop(skb, ctx, vars, params, stats,
skb_len_func, skb_time_func, backlog, now); // [4]
// ...
} else if (drop) {
u32 delta;

if (params->ecn && INET_ECN_set_ce(skb)) {
stats->ecn_mark++;
} else {
stats->drop_len += skb_len_func(skb);
drop_func(skb, ctx);
stats->drop_count++; // [5]

skb = dequeue_func(vars, ctx);
drop = codel_should_drop(skb, ctx, vars, params,
stats, skb_len_func,
skb_time_func, backlog, now);
}
vars->dropping = true;
delta = vars->count - vars->lastcount;
if (delta > 1 &&
codel_time_before(now - vars->drop_next,
16 * params->interval)) {
vars->count = delta;
codel_Newton_step(vars);
} else {
vars->count = 1;
vars->rec_inv_sqrt = ~0U >> REC_INV_SQRT_SHIFT;
}
vars->lastcount = vars->count;
vars->drop_next = codel_control_law(now, params->interval,
vars->rec_inv_sqrt);
}
```

The number of dropped packets is maintained in `drop_count` ([5]). At [2], there is a check for whether any packets were dropped. It additionally checks that the scheduler is not empty (non-zero `qlen`). Only if both conditions are met, then the parent qdisc is notified of the dropped packets at [3].

By triggering the conditions in `codel_dequeue()` to drop packets in a manner that empties the fq_codel qdisc, it is possible to cause `drop_count > 0` and `qlen == 0` at [2], so the parent is never notified of the dropped packets.

The mismatch in qlen can be turned into a UAF in a classful parent (like drr), as found in other reports, e.g. https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=638ba5089324796c2ee49af10427459c2de35f71 and https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=647cef20e649c576dff271e018d5d15d998b629d. It is possible to escalate the UAF into a LPE.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*build/*
gdb_stuff/
out/
tools/
exp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# taken from: https://github.com/google/security-research/blob/1bb2f8c8d95a34cafe7861bc890cfba5d85ec141/pocs/linux/kernelctf/CVE-2024-0193_lts/exploit/lts-6.1.67/Makefile

LIBMNL_DIR = $(realpath ./)/libmnl_build
LIBNFTNL_DIR = $(realpath ./)/libnftnl_build
LIBNFNETLINK_DIR = $(realpath ./)/libnfnetlink_build
LIBNETFILTER_QUEUE_DIR = $(realpath ./)/libnetfilterqueue_build
LIBIPTC_DIR = $(realpath ./)/libiptc_build

LIBS = -L$(LIBNFTNL_DIR)/install/lib -L$(LIBMNL_DIR)/install/lib -L$(LIBNFNETLINK_DIR)/install/lib -L$(LIBNETFILTER_QUEUE_DIR)/install/lib -L$(LIBIPTC_DIR)/install/lib -lxtables -lip4tc -lnftnl -lmnl -lnetfilter_queue -lnfnetlink
INCLUDES = -I$(LIBNFTNL_DIR)/libnftnl-1.2.5/include -I$(LIBMNL_DIR)/libmnl-1.0.5/include -I$(LIBNFNETLINK_DIR)/libnfnetlink-1.0.2/include -I$(LIBNETFILTER_QUEUE_DIR)/libnetfilter_queue-1.0.5/include -I$(LIBIPTC_DIR)/iptables-1.8.9/include
CFLAGS = -static -s

exp: exploit
cp exploit exp

exploit: exploit.c *.h
gcc -g -o exploit exploit.c -Wall -Wextra -Wno-unused -Werror -Wno-int-to-pointer-cast $(LIBS) $(INCLUDES) $(CFLAGS)

prerequisites: libnftnl-build libnetfilter-queue-build libiptc-build

libiptc-build : libiptc-download #libmnl-build libnftnl-build
tar -C $(LIBIPTC_DIR) -xvf $(LIBIPTC_DIR)/iptables-1.8.9.tar.xz
cd $(LIBIPTC_DIR)/iptables-1.8.9 && PKG_CONFIG_PATH=$(LIBMNL_DIR)/install/lib/pkgconfig:$(LIBNFTNL_DIR)/install/lib/pkgconfig ./configure --enable-static --prefix=`realpath ../install`
cd $(LIBIPTC_DIR)/iptables-1.8.9 && C_INCLUDE_PATH=$(C_INCLUDE_PATH):$(LIBMNL_DIR)/install/include::$(LIBNFTNL_DIR)/install/include LD_LIBRARY_PATH=$(LD_LIBRARY_PATH):$(LIBMNL_DIR)/install/lib:$(LIBNFTNL_DIR)/install/lib make -j`nproc`
cd $(LIBIPTC_DIR)/iptables-1.8.9 && make install

libiptc-download:
mkdir $(LIBIPTC_DIR)
wget -P $(LIBIPTC_DIR) https://netfilter.org/projects/iptables/files/iptables-1.8.9.tar.xz


libnfnetlink-build : libnfnetlink-download
tar -C $(LIBNFNETLINK_DIR) -xvf $(LIBNFNETLINK_DIR)/libnfnetlink-1.0.2.tar.bz2
cd $(LIBNFNETLINK_DIR)/libnfnetlink-1.0.2 && ./configure --enable-static --prefix=`realpath ../install`
cd $(LIBNFNETLINK_DIR)/libnfnetlink-1.0.2 && make -j`nproc`
cd $(LIBNFNETLINK_DIR)/libnfnetlink-1.0.2 && make install

libnetfilter-queue-build : libnetfilter-queue-download libnfnetlink-build #libmnl-build
tar -C $(LIBNETFILTER_QUEUE_DIR) -xvf $(LIBNETFILTER_QUEUE_DIR)/libnetfilter_queue-1.0.5.tar.bz2
cd $(LIBNETFILTER_QUEUE_DIR)/libnetfilter_queue-1.0.5 && PKG_CONFIG_PATH=$(LIBNFNETLINK_DIR)/install/lib/pkgconfig:$(LIBMNL_DIR)/install/lib/pkgconfig ./configure --enable-static --prefix=`realpath ../install`
cd $(LIBNETFILTER_QUEUE_DIR)/libnetfilter_queue-1.0.5 && C_INCLUDE_PATH=$(C_INCLUDE_PATH):$(LIBNFNETLINK_DIR)/install/include:$(LIBMNL_DIR)/install/include LD_LIBRARY_PATH=$(LD_LIBRARY_PATH):$(LIBNFNETLINK_DIR)/install/lib:$(LIBMNL_DIR)/install/lib make -j`nproc`
cd $(LIBNETFILTER_QUEUE_DIR)/libnetfilter_queue-1.0.5 && make install

libnetfilter-queue-download:
mkdir $(LIBNETFILTER_QUEUE_DIR)
wget -P $(LIBNETFILTER_QUEUE_DIR) https://netfilter.org/projects/libnetfilter_queue/files/libnetfilter_queue-1.0.5.tar.bz2

libnfnetlink-download:
mkdir $(LIBNFNETLINK_DIR)
wget -P $(LIBNFNETLINK_DIR) https://netfilter.org/projects/libnfnetlink/files/libnfnetlink-1.0.2.tar.bz2

libmnl-build : libmnl-download
tar -C $(LIBMNL_DIR) -xvf $(LIBMNL_DIR)/libmnl-1.0.5.tar.bz2
cd $(LIBMNL_DIR)/libmnl-1.0.5 && ./configure --enable-static --prefix=`realpath ../install`
cd $(LIBMNL_DIR)/libmnl-1.0.5 && make -j`nproc`
cd $(LIBMNL_DIR)/libmnl-1.0.5 && make install

libnftnl-build : libmnl-build libnftnl-download
tar -C $(LIBNFTNL_DIR) -xvf $(LIBNFTNL_DIR)/libnftnl-1.2.5.tar.xz
cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && PKG_CONFIG_PATH=$(LIBMNL_DIR)/install/lib/pkgconfig ./configure --enable-static --prefix=`realpath ../install`
cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && C_INCLUDE_PATH=$(C_INCLUDE_PATH):$(LIBMNL_DIR)/install/include LD_LIBRARY_PATH=$(LD_LIBRARY_PATH):$(LIBMNL_DIR)/install/lib make -j`nproc`
cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && make install

libmnl-download :
mkdir $(LIBMNL_DIR)
wget -P $(LIBMNL_DIR) https://netfilter.org/projects/libmnl/files/libmnl-1.0.5.tar.bz2

libnftnl-download :
mkdir $(LIBNFTNL_DIR)
wget -P $(LIBNFTNL_DIR) https://netfilter.org/projects/libnftnl/files/libnftnl-1.2.5.tar.xz

run:
./exp

clean:
rm -f exp
rm -rf $(LIBMNL_DIR)
rm -rf $(LIBNFTNL_DIR)
rm -rf $(LIBNFNETLINK_DIR)
rm -rf $(LIBNETFILTER_QUEUE_DIR)
rm -rf $(LIBIPTC_DIR)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash
set -euo pipefail
make exp

mkdir -p out
cp exp out/
Binary file not shown.
Loading
Loading