All of lore.kernel.org
 help / color / mirror / Atom feed
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


             reply	other threads:[~2026-11-03 17:33 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-03 17:32 shaos [this message]
2026-11-03 17:51 ` [PATCH] bpf: net_sched: reject bpf_throw() in qdisc teardown callbacks sashiko-bot
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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.