From: shaos <shawnq1@163.com>
To: Amery Hung <amery.hung@bytedance.com>
Cc: "Jamal Hadi Salim" <jhs@mojatatu.com>,
"Jiri Pirko" <jiri@resnulli.us>,
"Martin KaFai Lau" <martin.lau@kernel.org>,
"Toke Høiland-Jørgensen" <toke@redhat.com>,
netdev@vger.kernel.org, bpf@vger.kernel.org,
linux-kernel@vger.kernel.org
Subject: [PATCH] bpf: net_sched: reject bpf_throw() in qdisc teardown callbacks
Date: Wed, 4 Nov 2026 01:32:29 +0800 (CST) [thread overview]
Message-ID: <6AEA1AAD.2E7977.08739@m16.mail.163.com> (raw)
Hello,
A BPF qdisc's generated reset/destroy epilogue cancels its watchdog
timer. bpf_throw() unwinds directly to the exception callback and
skips that epilogue. If the timer was armed, qdisc deletion frees the
qdisc while the embedded timer remains queued. Later timer-queue
activity accesses the freed qdisc, producing a KASAN slab-use-after-free
and a kernel crash.
The reproducer registers a BPF qdisc whose dequeue callback arms the
watchdog and whose reset/destroy callbacks throw. Two fresh KASAN boots
reported use-after-free in rb_insert_color on an object allocated by
qdisc_alloc() and later freed by qdisc teardown. An otherwise identical
no-throw control completed deletion cleanly. The required BPF and
network-administration capabilities make this a privileged local kernel
crash; no privilege escalation or code execution is demonstrated.
HEAD commit: c03c1a14507dfaf8b856c2608f9ac216021fc222
Git tree: 30fd2ef82912c26cf3a58ad0ba6afc375fa851ac
Source version: Linux 7.3
Compiler: gcc 11.4.0, GNU ld 2.38, clang 20 (BPF PoC)
The console log, full KASAN kernel config, and BPF C reproducer are
available from one fixed-revision Gist:
Console output:
https://gist.githubusercontent.com/shaos26/9908a8ddac69e303cb5dcc709958537f/raw/3fa043e43683907bcccacdc80a71b150920049da/console-output.txt
Kernel config:
https://gist.githubusercontent.com/shaos26/9908a8ddac69e303cb5dcc709958537f/raw/3fa043e43683907bcccacdc80a71b150920049da/kernel-config.txt
PoC source:
https://gist.githubusercontent.com/shaos26/9908a8ddac69e303cb5dcc709958537f/raw/3fa043e43683907bcccacdc80a71b150920049da/poc.c
Run this as root in a disposable VM booted with the target kernel,
CONFIG_DEBUG_INFO_BTF=y, bpftool, iproute2, clang 20, and libbpf-dev.
Build each variant against that kernel's BTF; test them on separate
fresh boots. With libbpf headers installed under /usr/include:
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
CLANG=/usr/lib/llvm-20/bin/clang
LIBBPF_INC=/usr/include
"$CLANG" -target bpf -D__TARGET_ARCH_x86 -O2 -g -Wall -Werror \
-Wno-missing-declarations -I. -I"$LIBBPF_INC" \
-DVARIANT_THROW=1 -c poc.c -o qdisc-throw.bpf.o
"$CLANG" -target bpf -D__TARGET_ARCH_x86 -O2 -g -Wall -Werror \
-Wno-missing-declarations -I. -I"$LIBBPF_INC" \
-DVARIANT_THROW=0 -c poc.c -o qdisc-control.bpf.o
Mount bpffs if it is not already mounted, register the matching object,
attach its qdisc to loopback, send one packet to arm the watchdog, and
delete the qdisc:
mkdir -p /sys/fs/bpf
mount -t bpf bpffs /sys/fs/bpf 2>/dev/null || true
bpftool struct_ops register qdisc-throw.bpf.o /sys/fs/bpf
ip link set lo up
tc qdisc add dev lo root handle 1: agent31_uaf
ping -c 1 -W 1 127.0.0.1
tc qdisc del dev lo root
sleep 10
For the separate control boot, register qdisc-control.bpf.o and use
qdisc kind agent31_ctl in the same sequence.
On the vulnerable kernel, the two throw runs reproduced the KASAN UAF;
the no-throw control survived. With the patch below, the verifier
rejects bpf_throw() from reset/destroy with -EACCES, before qdisc
registration. The patched control object registered, attached, armed
and deleted normally, then survived the watchdog interval.
Fixes: 7a2dafda950b ("bpf: net_sched: Add a qdisc watchdog timer")
Signed-off-by: shaos <shawnq1@163.com>
---
net/sched/bpf_qdisc.c | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/net/sched/bpf_qdisc.c b/net/sched/bpf_qdisc.c
index 098ca02aed89c..61f0dc93d3856 100644
--- a/net/sched/bpf_qdisc.c
+++ b/net/sched/bpf_qdisc.c
@@ -12,6 +12,7 @@
#define QDISC_MOFF_IDX(moff) (moff / sizeof(void (*)(void)))
static struct bpf_struct_ops bpf_Qdisc_ops;
+static const struct bpf_verifier_ops bpf_qdisc_verifier_ops;
struct bpf_sched_data {
struct qdisc_watchdog watchdog;
@@ -131,6 +132,7 @@ static int bpf_qdisc_btf_struct_access(struct bpf_verifier_log *log,
}
BTF_ID_LIST_SINGLE(bpf_qdisc_init_prologue_ids, func, bpf_qdisc_init_prologue)
+BTF_ID_LIST_SINGLE(bpf_qdisc_throw_ids, func, bpf_throw)
static int bpf_qdisc_gen_prologue(struct bpf_insn *insn_buf, bool direct_write,
const struct bpf_prog *prog)
@@ -307,20 +309,31 @@ enum qdisc_ops_kf_flags {
QDISC_OPS_KF_COMMON = 0,
QDISC_OPS_KF_ENQUEUE = 1 << 0,
QDISC_OPS_KF_DEQUEUE = 1 << 1,
+ QDISC_OPS_KF_TEARDOWN = 1 << 2,
};
static const u32 qdisc_ops_context_flags[] = {
[QDISC_OP_IDX(enqueue)] = QDISC_OPS_KF_ENQUEUE,
[QDISC_OP_IDX(dequeue)] = QDISC_OPS_KF_DEQUEUE,
[QDISC_OP_IDX(init)] = QDISC_OPS_KF_COMMON,
- [QDISC_OP_IDX(reset)] = QDISC_OPS_KF_COMMON,
- [QDISC_OP_IDX(destroy)] = QDISC_OPS_KF_COMMON,
+ [QDISC_OP_IDX(reset)] = QDISC_OPS_KF_TEARDOWN,
+ [QDISC_OP_IDX(destroy)] = QDISC_OPS_KF_TEARDOWN,
};
static int bpf_qdisc_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id)
{
u32 moff, flags;
+ /* bpf_throw() bypasses the generated watchdog-cancellation epilogue. */
+ if (kfunc_id == bpf_qdisc_throw_ids[0] &&
+ prog->aux->st_ops &&
+ prog->aux->st_ops->verifier_ops == &bpf_qdisc_verifier_ops) {
+ moff = prog->aux->attach_st_ops_member_off;
+ flags = qdisc_ops_context_flags[QDISC_MOFF_IDX(moff)];
+ if (flags & QDISC_OPS_KF_TEARDOWN)
+ return -EACCES;
+ }
+
if (!btf_id_set8_contains(&qdisc_kfunc_ids, kfunc_id))
return 0;
--
2.34.1
next reply other threads:[~2026-11-03 17:33 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-03 17:32 shaos [this message]
2026-11-03 18:18 ` Alexei Starovoitov
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=6AEA1AAD.2E7977.08739@m16.mail.163.com \
--to=shawnq1@163.com \
--cc=amery.hung@bytedance.com \
--cc=bpf@vger.kernel.org \
--cc=jhs@mojatatu.com \
--cc=jiri@resnulli.us \
--cc=linux-kernel@vger.kernel.org \
--cc=martin.lau@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=toke@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®