* Re: [PATCH net] tls: fix use-after-free in tls_sw_sendmsg() cork error path
2026-06-05 21:48 [PATCH net] tls: fix use-after-free in tls_sw_sendmsg() cork error path Sechang Lim
@ 2026-06-09 15:25 ` abaci-kreproducer
2026-06-09 21:57 ` Jakub Kicinski
2026-06-09 21:58 ` Jakub Kicinski
1 sibling, 1 reply; 4+ messages in thread
From: abaci-kreproducer @ 2026-06-09 15:25 UTC (permalink / raw)
To: Sechang Lim
Cc: John Fastabend, Jakub Kicinski, Sabrina Dubroca, David S . Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Jiayuan Chen,
Alexei Starovoitov, netdev, bpf, linux-kernel, Oliver
From: abaci-kreproducer <abaci@linux.alibaba.com>
This is an AI-generated validation of this patch. The issue was
reproduced on the unpatched kernel, and the original symptoms were no
longer observed on the patched kernel. However, additional issues were
identified during validation (details below).
Tested-by: abaci-kreproducer <abaci@linux.alibaba.com>
---
We reproduced this issue on the unpatched kernel:
KASAN reported a slab-use-after-free in tls_sw_sendmsg+0x1df5/0x1f40:
a 4-byte read of the freed cork_bytes field inside a freed kmalloc-4k
object.
In tls_sw_sendmsg_locked(), msg_pl is cached before bpf_exec_tx_verdict().
When a BPF SK_MSG program forces a record split via apply_bytes, the
original tls_rec is freed but msg_pl remains stale. The subsequent
-ENOSPC error path dereferences msg_pl->cork_bytes, triggering UAF.
The reproducer uses a sockmap with an SK_MSG program that sets
apply_bytes=100 and cork_bytes=32768. Sending 8KB with MSG_MORE followed
by 8KB without MSG_MORE triggers the split and stale pointer dereference.
On the patched kernel, no KASAN slab-use-after-free is reported.
Additional issues were identified during validation: the patched kernel
hits a fatal kernel BUG at lib/iov_iter.c:635 (Oops: invalid opcode) in
iov_iter_revert+0x39b/0x3e0, called from tls_sw_sendmsg+0x1aac/0x20f0.
This appears to be a new failure mode exposed by the fix.
kernel BUG at lib/iov_iter.c:635!
Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI
RIP: 0010:iov_iter_revert+0x39b/0x3e0
Call Trace:
tls_sw_sendmsg+0x1aac/0x20f0
__sys_sendto+0x35b/0x3e0
__x64_sys_sendto+0xdc/0x1b0
do_syscall_64+0x10d/0x510
entry_SYSCALL_64_after_hwframe+0x77/0x7f
---
Key configuration
* kconfig:
CONFIG_TLS=y
CONFIG_BPF_SYSCALL=y
CONFIG_INET=y
CONFIG_KASAN=y
CONFIG_KASAN_GENERIC=y
CONFIG_KASAN_INLINE=y
CONFIG_NET_SOCK_MSG=y
CONFIG_CRYPTO_AES=y
CONFIG_CRYPTO_GCM=y
* kernel_cmdline: -
* rpm package: -
--
run.sh
#!/bin/bash
set -eu
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BIN_DIR="${SCRIPT_DIR}/bin"
RESULTS_DIR="${SCRIPT_DIR}/results"
REPRODUCER_SRC="${BIN_DIR}/reproducer.c"
REPRODUCER_BIN="${BIN_DIR}/reproducer"
mkdir -p "${RESULTS_DIR}"
echo "=== TLS kTLS sendmsg cork UAF reproducer ==="
echo "Commit: 7723129261d09c9328bd1b332598b9fd465ad823"
echo ""
echo "[*] Checking kernel config..."
KCONFIG="$(zcat /proc/config.gz 2>/dev/null || cat /boot/config-$(uname -r) 2>/dev/null || echo "")"
check_config() {
echo "$KCONFIG" | grep -cE "$1=[ym]" > /dev/null 2>&1
}
if ! check_config "CONFIG_TLS"; then
echo "[!] CONFIG_TLS not enabled. Cannot reproduce."
exit 1
fi
if ! check_config "CONFIG_BPF_SYSCALL"; then
echo "[!] CONFIG_BPF_SYSCALL not enabled. Cannot reproduce."
exit 1
fi
if ! check_config "CONFIG_NET_SOCK_MSG"; then
echo "[!] CONFIG_NET_SOCK_MSG not enabled. Cannot reproduce."
exit 1
fi
if check_config "CONFIG_KASAN"; then
echo "[*] KASAN is enabled (will detect UAF)"
else
echo "[*] WARNING: KASAN not enabled. UAF may not be detected by kernel."
fi
modprobe tls || true
echo "[*] Compiling reproducer..."
gcc -o "${REPRODUCER_BIN}" "${REPRODUCER_SRC}" -Wall -O2 -lpthread
echo "[*] Compiled: ${REPRODUCER_BIN}"
dmesg -C
echo "[*] Running reproducer..."
"${REPRODUCER_BIN}" 2>&1 | tee "${RESULTS_DIR}/test-output.txt" || true
echo ""
echo "[*] Collecting dmesg output..."
dmesg > "${RESULTS_DIR}/dmesg-errors.txt" 2>&1 || true
if dmesg | grep -ciE "KASAN.*use-after-free|slab-use-after-free" | grep -qv "^0$"; then
echo ""
echo "============================================"
echo " BUG REPRODUCED: KASAN detected UAF!"
echo "============================================"
echo ""
echo "Relevant dmesg lines:"
dmesg | grep -iE "BUG:|KASAN:|use-after-free|slab-use-after-free|tls_sw_sendmsg" | tail -20
exit 0
fi
if dmesg | grep -ciE "Oops|general protection|kernel panic" | grep -qv "^0$"; then
echo ""
echo "============================================"
echo " BUG REPRODUCED: kernel error detected!"
echo "============================================"
echo ""
dmesg | grep -iE "Oops|general protection|kernel panic" | tail -10
exit 0
fi
echo ""
echo "============================================"
echo " Bug NOT detected (KASAN may be required)"
echo "============================================"
exit 1
--
reproducer.c
/*
* Reproducer for: tls: fix use-after-free in tls_sw_sendmsg() cork error path
* Commit: 7723129261d09c9328bd1b332598b9fd465ad823
*
* Bug: In tls_sw_sendmsg_locked(), msg_pl is cached from ctx->open_rec before
* calling bpf_exec_tx_verdict(). When the attached SK_MSG BPF program calls
* bpf_msg_apply_bytes() + bpf_msg_push_data(), tls_push_record() splits the
* open record: the original record is encrypted, queued and freed by
* tls_tx_records(), while ctx->open_rec is replaced by the split remainder.
* If the BPF program also sets cork_bytes, the second iteration in
* bpf_exec_tx_verdict() returns -ENOSPC, and the error path dereferences the
* now-dangling msg_pl (use-after-free).
*
* Fix: Re-fetch msg_pl from ctx->open_rec before accessing cork_bytes in
* the -ENOSPC error path.
*
* Trigger:
* 1. Create TCP socket, add to sockmap with SK_MSG BPF program
* 2. Enable kTLS TX (AES-GCM-128)
* 3. BPF program: apply_bytes(100) + push_data(0, 200) + cork_bytes(32768)
* 4. Send 8KB with MSG_MORE (accumulates data in open record)
* 5. Send 8KB without MSG_MORE (fills TLS record -> split + cork ENOSPC -> UAF)
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/tls.h>
#include <linux/bpf.h>
#ifndef SOL_TLS
#define SOL_TLS 300
#endif
#ifndef TLS_1_2_VERSION
#define TLS_1_2_VERSION 0x0303
#endif
/* BPF helper function IDs */
#define BPF_FUNC_msg_apply_bytes 61
#define BPF_FUNC_msg_cork_bytes 62
#define BPF_FUNC_msg_push_data 90
#define BPF_JMP 0x05
#define BPF_ALU 0x04
#define BPF_ALU64 0x07
#define BPF_K 0x00
#define BPF_X 0x08
#define BPF_MOV 0xb0
#define BPF_CALL 0x80
#define BPF_EXIT 0x90
#define SK_PASS 1
#define NUM_ITERATIONS 3
#define SEND_SIZE 8192
/*
* SK_MSG BPF program (raw insns, no clang required):
* R6 = R1; // save ctx (callee-saved)
* R2 = 100; bpf_msg_apply_bytes(ctx, 100); // force record split at 100 bytes
* R1 = R6; // restore ctx after call
* R2 = 0; R3 = 200; R4 = 0;
* bpf_msg_push_data(ctx, 0, 200, 0); // grow sg.size beyond encrypted buffer
* R1 = R6; // restore ctx
* R2 = 32768; bpf_msg_cork_bytes(ctx, 32768); // cork at 32KB
* R0 = SK_PASS; return SK_PASS;
*
* How the UAF is triggered:
* 1st BPF invocation: apply_bytes=100 causes tls_push_record() to split the
* open record. The front part (100 bytes) is encrypted, queued, and freed
* by tls_tx_records(). ctx->open_rec now points to the split remainder.
* push_data(0,200) grows sg.size, ensuring the split triggers correctly.
* 2nd BPF invocation (on remainder): apply_bytes was consumed (0), so BPF
* re-runs. cork_bytes=32768 is set. Since 32768 > remainder size and not
* a full record, bpf_exec_tx_verdict() returns -ENOSPC.
* Back in tls_sw_sendmsg_locked(): msg_pl still points to the OLD record
* (freed in step 1). Accessing msg_pl->cork_bytes is a use-after-free.
*/
static struct bpf_insn bpf_prog[] = {
/* R6 = R1 (save ctx to callee-saved register) */
{ .code = BPF_ALU64 | BPF_MOV | BPF_X, .dst_reg = 6, .src_reg = 1, .off = 0, .imm = 0 },
/* bpf_msg_apply_bytes(ctx, 100) */
{ .code = BPF_ALU | BPF_MOV | BPF_K, .dst_reg = 2, .src_reg = 0, .off = 0, .imm = 100 },
{ .code = BPF_JMP | BPF_CALL, .dst_reg = 0, .src_reg = 0, .off = 0, .imm = BPF_FUNC_msg_apply_bytes },
/* R1 = R6 (restore ctx after call clobbered R1) */
{ .code = BPF_ALU64 | BPF_MOV | BPF_X, .dst_reg = 1, .src_reg = 6, .off = 0, .imm = 0 },
/* bpf_msg_push_data(ctx, offset=0, length=200, flags=0) */
{ .code = BPF_ALU | BPF_MOV | BPF_K, .dst_reg = 2, .src_reg = 0, .off = 0, .imm = 0 },
{ .code = BPF_ALU | BPF_MOV | BPF_K, .dst_reg = 3, .src_reg = 0, .off = 0, .imm = 200 },
{ .code = BPF_ALU | BPF_MOV | BPF_K, .dst_reg = 4, .src_reg = 0, .off = 0, .imm = 0 },
{ .code = BPF_JMP | BPF_CALL, .dst_reg = 0, .src_reg = 0, .off = 0, .imm = BPF_FUNC_msg_push_data },
/* R1 = R6 (restore ctx) */
{ .code = BPF_ALU64 | BPF_MOV | BPF_X, .dst_reg = 1, .src_reg = 6, .off = 0, .imm = 0 },
/* bpf_msg_cork_bytes(ctx, 32768) */
{ .code = BPF_ALU | BPF_MOV | BPF_K, .dst_reg = 2, .src_reg = 0, .off = 0, .imm = 32768 },
{ .code = BPF_JMP | BPF_CALL, .dst_reg = 0, .src_reg = 0, .off = 0, .imm = BPF_FUNC_msg_cork_bytes },
/* return SK_PASS */
{ .code = BPF_ALU | BPF_MOV | BPF_K, .dst_reg = 0, .src_reg = 0, .off = 0, .imm = SK_PASS },
{ .code = BPF_JMP | BPF_EXIT, .dst_reg = 0, .src_reg = 0, .off = 0, .imm = 0 },
};
static int bpf_prog_fd = -1;
static int map_fd_global = -1;
static volatile int server_done = 0;
static int sys_bpf(int cmd, union bpf_attr *attr)
{
return syscall(__NR_bpf, cmd, attr, sizeof(*attr));
}
static void *server_thread(void *arg)
{
int listen_fd = *(int *)arg;
char buf[4096];
int conn_fd = accept(listen_fd, NULL, NULL);
if (conn_fd < 0) {
perror("accept");
return NULL;
}
while (!server_done) {
ssize_t n = read(conn_fd, buf, sizeof(buf));
if (n <= 0)
break;
}
close(conn_fd);
return NULL;
}
static void cleanup(int client_fd, int listen_fd)
{
server_done = 1;
if (client_fd >= 0)
shutdown(client_fd, SHUT_RDWR);
usleep(100000);
if (client_fd >= 0)
close(client_fd);
if (listen_fd >= 0)
close(listen_fd);
if (map_fd_global >= 0) {
close(map_fd_global);
map_fd_global = -1;
}
if (bpf_prog_fd >= 0) {
close(bpf_prog_fd);
bpf_prog_fd = -1;
}
}
static int setup_and_trigger(int iteration)
{
struct sockaddr_in addr;
socklen_t addrlen = sizeof(addr);
pthread_t tid;
int listen_fd = -1, client_fd = -1, map_fd = -1;
printf("[%d] Setting up TCP connection on loopback...\n", iteration);
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0) {
perror("socket(listen)");
return 1;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.sin_port = 0;
if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind");
close(listen_fd);
return 1;
}
if (listen(listen_fd, 1) < 0) {
perror("listen");
close(listen_fd);
return 1;
}
addrlen = sizeof(addr);
getsockname(listen_fd, (struct sockaddr *)&addr, &addrlen);
pthread_create(&tid, NULL, server_thread, &listen_fd);
client_fd = socket(AF_INET, SOCK_STREAM, 0);
if (client_fd < 0) {
perror("socket(client)");
cleanup(client_fd, listen_fd);
pthread_join(tid, NULL);
return 1;
}
if (connect(client_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("connect");
cleanup(client_fd, listen_fd);
pthread_join(tid, NULL);
return 1;
}
/* STEP 1: Create sockmap and attach BPF BEFORE setting up TLS ULP.
* sk_psock_init() rejects sockets that already have ULP (TLS) attached. */
printf("[%d] Creating sockmap and loading BPF program...\n", iteration);
union bpf_attr map_attr;
memset(&map_attr, 0, sizeof(map_attr));
map_attr.map_type = BPF_MAP_TYPE_SOCKMAP;
map_attr.key_size = sizeof(int);
map_attr.value_size = sizeof(int);
map_attr.max_entries = 1;
map_fd = sys_bpf(BPF_MAP_CREATE, &map_attr);
if (map_fd < 0) {
perror("bpf(BPF_MAP_CREATE)");
cleanup(client_fd, listen_fd);
pthread_join(tid, NULL);
return 1;
}
map_fd_global = map_fd;
char log_buf[4096] = {0};
union bpf_attr prog_attr;
memset(&prog_attr, 0, sizeof(prog_attr));
prog_attr.prog_type = BPF_PROG_TYPE_SK_MSG;
prog_attr.insns = (unsigned long long)bpf_prog;
prog_attr.insn_cnt = sizeof(bpf_prog) / sizeof(bpf_prog[0]);
prog_attr.license = (unsigned long long)"GPL";
prog_attr.log_buf = (unsigned long long)log_buf;
prog_attr.log_size = sizeof(log_buf);
prog_attr.log_level = 1;
prog_attr.kern_version = 0;
bpf_prog_fd = sys_bpf(BPF_PROG_LOAD, &prog_attr);
if (bpf_prog_fd < 0) {
perror("bpf(BPF_PROG_LOAD)");
fprintf(stderr, "BPF verifier log:\n%s\n", log_buf);
cleanup(client_fd, listen_fd);
pthread_join(tid, NULL);
return 1;
}
union bpf_attr attach_attr;
memset(&attach_attr, 0, sizeof(attach_attr));
attach_attr.attach_type = BPF_SK_MSG_VERDICT;
attach_attr.target_fd = map_fd;
attach_attr.attach_bpf_fd = bpf_prog_fd;
if (sys_bpf(BPF_PROG_ATTACH, &attach_attr) < 0) {
perror("bpf(BPF_PROG_ATTACH)");
cleanup(client_fd, listen_fd);
pthread_join(tid, NULL);
return 1;
}
/* Add client socket to sockmap BEFORE TLS setup */
int key = 0;
union bpf_attr update_attr;
memset(&update_attr, 0, sizeof(update_attr));
update_attr.map_fd = map_fd;
update_attr.key = (unsigned long long)&key;
update_attr.value = (unsigned long long)&client_fd;
update_attr.flags = 0;
if (sys_bpf(BPF_MAP_UPDATE_ELEM, &update_attr) < 0) {
perror("bpf(BPF_MAP_UPDATE_ELEM)");
cleanup(client_fd, listen_fd);
pthread_join(tid, NULL);
return 1;
}
/* STEP 2: Set up TLS ULP and kTLS TX */
printf("[%d] Enabling kTLS TX (AES-GCM-128)...\n", iteration);
/* Attach TLS ULP to the socket (TCP_ULP = 31) */
if (setsockopt(client_fd, IPPROTO_TCP, 31, "tls", 3) < 0) {
perror("setsockopt(TCP_ULP, tls)");
cleanup(client_fd, listen_fd);
pthread_join(tid, NULL);
return 1;
}
struct tls12_crypto_info_aes_gcm_128 crypto_info;
memset(&crypto_info, 0, sizeof(crypto_info));
crypto_info.info.version = TLS_1_2_VERSION;
crypto_info.info.cipher_type = TLS_CIPHER_AES_GCM_128;
if (setsockopt(client_fd, SOL_TLS, TLS_TX, &crypto_info, sizeof(crypto_info)) < 0) {
perror("setsockopt(TLS_TX)");
fprintf(stderr, "Hint: CONFIG_TLS=y required\n");
cleanup(client_fd, listen_fd);
pthread_join(tid, NULL);
return 1;
}
printf("[%d] Setup complete. Triggering UAF...\n", iteration);
char *buf = calloc(1, SEND_SIZE);
if (!buf) {
perror("calloc");
cleanup(client_fd, listen_fd);
pthread_join(tid, NULL);
return 1;
}
/* First send with MSG_MORE: accumulates data in the open record
* without triggering bpf_exec_tx_verdict (eor=false, full_record=false) */
printf("[%d] Sending %d bytes with MSG_MORE (accumulating)...\n", iteration, SEND_SIZE);
ssize_t n = send(client_fd, buf, SEND_SIZE, MSG_MORE);
if (n < 0) {
fprintf(stderr, "[%d] First send failed: %s\n", iteration, strerror(errno));
free(buf);
cleanup(client_fd, listen_fd);
pthread_join(tid, NULL);
return 1;
}
printf("[%d] Sent %zd bytes (MSG_MORE)\n", iteration, n);
/* Second send without MSG_MORE: eor=true triggers bpf_exec_tx_verdict.
* BPF program sets apply_bytes(100) -> split -> push_data(200) -> cork_bytes(32768).
* Split frees the original record, remainder goes to 2nd BPF call which
* sets cork_bytes > remainder size -> -ENOSPC -> UAF on dangling msg_pl. */
printf("[%d] Sending %d bytes without MSG_MORE (triggering UAF)...\n", iteration, SEND_SIZE);
n = send(client_fd, buf, SEND_SIZE, 0);
if (n < 0) {
fprintf(stderr, "[%d] Second send: %s (errno=%d)\n", iteration, strerror(errno), errno);
} else {
printf("[%d] Sent %zd bytes\n", iteration, n);
}
free(buf);
usleep(50000);
printf("[%d] Checking dmesg for KASAN reports...\n", iteration);
fflush(stdout);
system("dmesg | grep -iE 'BUG:|KASAN:|use-after-free|slab-use-after-free|tls_sw_sendmsg' | tail -10");
cleanup(client_fd, listen_fd);
pthread_join(tid, NULL);
return 0;
}
int main(void)
{
printf("[*] Reproducer: TLS kTLS sendmsg cork UAF (7723129261d0)\n");
printf("[*] Trigger: bpf_msg_apply_bytes() + bpf_msg_push_data() + bpf_msg_cork_bytes()\n");
printf("[*] BPF helpers: apply=%d, cork=%d, push=%d\n",
BPF_FUNC_msg_apply_bytes, BPF_FUNC_msg_cork_bytes, BPF_FUNC_msg_push_data);
printf("[*] Running %d iterations\n\n", NUM_ITERATIONS);
alarm(60);
system("dmesg -C 2>/dev/null");
for (int i = 0; i < NUM_ITERATIONS; i++) {
int ret = setup_and_trigger(i);
if (ret != 0) {
fprintf(stderr, "[%d] Setup/trigger failed with code %d\n", i, ret);
continue;
}
int kasan_found = 0;
FILE *fp = popen("dmesg | grep -ciE 'KASAN:.*use-after-free|slab-use-after-free' 2>/dev/null", "r");
if (fp) {
int count = 0;
if (fscanf(fp, "%d", &count) == 1 && count > 0)
kasan_found = 1;
pclose(fp);
}
if (kasan_found) {
printf("\n[!] KASAN use-after-free detected at iteration %d!\n", i);
break;
}
}
printf("\n[*] Final dmesg check:\n");
fflush(stdout);
system("dmesg | grep -iE 'BUG:|KASAN:|use-after-free|slab-use-after-free|tls_sw_sendmsg' | tail -30");
return 0;
}
^ permalink raw reply [flat|nested] 4+ messages in thread