The Linux Kernel Mailing List
 help / color / mirror / Atom feed
* BUG: possible recursive locking detected in sleepable task_vma iterator
@ 2026-07-27 11:40 Junseo Lim
  2026-07-27 11:55 ` Lorenzo Stoakes (ARM)
  0 siblings, 1 reply; 2+ messages in thread
From: Junseo Lim @ 2026-07-27 11:40 UTC (permalink / raw)
  To: Yonghong Song
  Cc: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
	Martin KaFai Lau, Lorenzo Stoakes, Liam R. Howlett, bpf, linux-mm,
	linux-kernel, Junseo Lim, Sechang Lim

Hi,

We found a lockdep warning in the BPF task_vma iterator using our
customized fuzzer. We believe this indicates a potential deadlock.

A program that can cause this issue looks like this:

```
prog_fd = bpf(BPF_PROG_LOAD, {
        .prog_type = BPF_PROG_TYPE_TRACING,
        .expected_attach_type = BPF_TRACE_ITER,
        .attach_btf_id = /* task_vma iterator */,
        .prog_flags = BPF_F_SLEEPABLE,
        .insns = {
                /* even a no-op sleepable program is enough */
        },
});

link_fd = bpf(BPF_LINK_CREATE, {
        .prog_fd = prog_fd,
        .target_fd = 0,
        .attach_type = BPF_TRACE_ITER,
});

iter_fd = bpf(BPF_ITER_CREATE, {
        .link_fd = link_fd,
});

/* Running the task_vma iterator executes the BPF program. */
read(iter_fd, buf, sizeof(buf));
```

The bpf_seq_read() uses the vma iterator task_vma_seq_get_next() which
grabs the mmap_lock of the process. It documents that when it returns
a non-NULL VMA, it holds a task reference, an mm_users reference,
and the read side of vma->mm->mmap_lock. That lock is kept across
task_vma_seq_show() so that the VMA pointer passed to the BPF iterator
program remains valid. It is released later from task_vma_seq_stop().

After task_vma_seq_get_next() returns a VMA with mmap_lock still held for
read, task_vma_seq_show() invokes bpf_iter_run_prog() before the lock is
released by task_vma_seq_stop(). The problematic part is that task_vma
advertises BPF_ITER_RESCHED, and bpf_iter_link_attach() uses that bit to
allow a sleepable iterator program to attach to task_vma. This makes it
possible to run the sleepable bpf_iter_run_prog() path while task_vma still
holds mmap_lock. Since the mmap_lock is being held by task_vma_seq_get_next(),
it can possibly deadlock if the program triggers a page fault.

Our current understanding of the sequence is:

---
bpf_seq_read()
  task_vma_seq_start()
    task_vma_seq_get_next()
      mmap_read_lock_killable(curr_mm)

  task_vma_seq_show()
    __task_vma_seq_show()
      bpf_iter_run_prog()
        if (prog->sleepable)
          might_fault()
            might_lock_read(&current->mm->mmap_lock)
---

Below is the excerpt from the kernel log that shows the lockdep warning:

 ============================================
 WARNING: possible recursive locking detected
 7.2.0-rc4-00471-g0ce37745d4bf #10 Not tainted
 --------------------------------------------
 task_vma_sleepa/55 is trying to acquire lock:
 ffff8881046450b0 (&mm->mmap_lock){++++}-{4:4}, at: __might_fault+0xc4/0x170
 
 but task is already holding lock:
 ffff88810007d0b0 (&mm->mmap_lock){++++}-{4:4}, at: task_vma_seq_get_next+0x5be/0x9a0
 
 other info that might help us debug this:
  Possible unsafe locking scenario:
 
        CPU0
        ----
   lock(&mm->mmap_lock);
   lock(&mm->mmap_lock);
 
  *** DEADLOCK ***
 
  May be due to missing lock nesting notation
 
 3 locks held by task_vma_sleepa/55:
  #0: ffff888104629c98 (&p->lock){+.+.}-{4:4}, at: bpf_seq_read+0x64/0x12c0
  #1: ffff88810007d0b0 (&mm->mmap_lock){++++}-{4:4}, at: task_vma_seq_get_next+0x5be/0x9a0
  #2: ffffffff841b60f8 (rcu_tasks_trace_srcu_struct){....}-{0:0}, at: bpf_iter_run_prog+0x467/0x10d0

In the observed trace, the two concrete locks have different addresses
with the same class. So this trace itself demonstrates the unsafe lockdep
chain rather than an already-hung task. However, the same allowed execution
model can become a real rwsem deadlock if the second acquisition targets
the same mm and a writer is pending between the first read lock and the
second read lock:

---
T0: holds mm->mmap_lock for read from task_vma_seq_get_next()
T1: waits for mm->mmap_lock for write
T0: sleepable BPF program faults or uses a user-copy helper
T0: tries to acquire mm->mmap_lock for read again
T0: blocks behind the pending writer
T1: waits for T0's first read lock
---

Thanks,
Junseo Lim

Found-by: Sechang Lim <rhkrqnwk98@gmail.com>
Analyzed-by: Junseo Lim <zirajs7@gmail.com>

Below is the log and reproducer for the issue.
====
Kernel tested:
  Tree: bpf/master
  Commit: 0ce37745d4bfbc493f718169c3974898ffec8ee7
  Version: 7.2.0-rc4-00471-g0ce37745d4bf
  Environment: QEMU x86_64
  Command line: console=ttyS0 net.ifnames=0 biosdevname=0 selinux=0 rdinit=/init nokaslr panic=0 printk.time=1

Relevant config options:
  CONFIG_BPF=y
  CONFIG_BPF_SYSCALL=y
  CONFIG_PREEMPT=y
  CONFIG_PREEMPT_DYNAMIC=y
  CONFIG_KASAN=y
  CONFIG_PROVE_LOCKING=y
  CONFIG_LOCKDEP=y
---
[    3.704087] ============================================
[    3.704417] WARNING: possible recursive locking detected
[    3.704792] 7.2.0-rc4-00471-g0ce37745d4bf #10 Not tainted
[    3.705128] --------------------------------------------
[    3.705470] task_vma_sleepa/55 is trying to acquire lock:
[    3.705805] ffff8881046450b0 (&mm->mmap_lock){++++}-{4:4}, at: __might_fault+0xc4/0x170
[    3.706328] 
[    3.706328] but task is already holding lock:
[    3.706947] ffff88810007d0b0 (&mm->mmap_lock){++++}-{4:4}, at: task_vma_seq_get_next+0x5be/0x9a0
[    3.709536] 
[    3.709536] other info that might help us debug this:
[    3.709920]  Possible unsafe locking scenario:
[    3.709920] 
[    3.710284]        CPU0
[    3.710445]        ----
[    3.710596]   lock(&mm->mmap_lock);
[    3.710810]   lock(&mm->mmap_lock);
[    3.711026] 
[    3.711026]  *** DEADLOCK ***
[    3.711026] 
[    3.712842]  May be due to missing lock nesting notation
[    3.712842] 
[    3.713254] 3 locks held by task_vma_sleepa/55:
[    3.713540]  #0: ffff888104629c98 (&p->lock){+.+.}-{4:4}, at: bpf_seq_read+0x64/0x12c0
[    3.714047]  #1: ffff88810007d0b0 (&mm->mmap_lock){++++}-{4:4}, at: task_vma_seq_get_next+0x5be/0x9a0
[    3.714634]  #2: ffffffff841b60f8 (rcu_tasks_trace_srcu_struct){....}-{0:0}, at: bpf_iter_run_prog+0x467/0x10d0
[    3.715274] 
[    3.715274] stack backtrace:
[    3.715547] CPU: 0 UID: 0 PID: 55 Comm: task_vma_sleepa Not tainted 7.2.0-rc4-00471-g0ce37745d4bf #10 PREEMPT(full) 
[    3.716179] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Arch Linux 1.17.0-2-2 04/01/2014
[    3.716774] Call Trace:
[    3.716934]  <TASK>
[    3.717076]  dump_stack_lvl+0x55/0xb0
[    3.717322]  print_deadlock_bug.cold+0xc0/0xcd
[    3.717610]  __lock_acquire+0x12c7/0x1d70
[    3.717880]  lock_acquire+0x118/0x320
[    3.718123]  ? __might_fault+0xc4/0x170
[    3.718374]  ? srso_alias_return_thunk+0x5/0xfbef5
[    3.718678]  ? srso_alias_return_thunk+0x5/0xfbef5
[    3.718980]  __might_fault+0xe4/0x170
[    3.719218]  ? __might_fault+0xc4/0x170
[    3.719467]  ? __might_fault+0xc4/0x170
[    3.719719]  bpf_iter_run_prog+0x5ce/0x10d0
[    3.719992]  ? __pfx_bpf_iter_run_prog+0x10/0x10
[    3.720293]  ? srso_alias_return_thunk+0x5/0xfbef5
[    3.720600]  task_vma_seq_show+0x15b/0x1e0
[    3.720871]  ? __pfx_task_vma_seq_show+0x10/0x10
[    3.721171]  ? srso_alias_return_thunk+0x5/0xfbef5
[    3.721483]  ? __pfx_bpf_seq_read+0x10/0x10
[    3.721753]  ? __pfx_bpf_seq_read+0x10/0x10
[    3.722021]  bpf_seq_read+0x1df/0x12c0
[    3.722267]  ? security_file_permission+0x34/0x70
[    3.722566]  ? srso_alias_return_thunk+0x5/0xfbef5
[    3.722867]  ? __pfx_bpf_seq_read+0x10/0x10
[    3.723138]  vfs_read+0x19a/0x9b0
[    3.723371]  ? __pfx_vfs_read+0x10/0x10
[    3.723629]  ksys_read+0x129/0x250
[    3.723857]  ? __pfx_ksys_read+0x10/0x10
[    3.724114]  ? srso_alias_return_thunk+0x5/0xfbef5
[    3.724424]  ? __x64_sys_bpf+0xc9/0x1a0
[    3.724755]  do_syscall_64+0xad/0x5c0
[    3.725005]  ? srso_alias_return_thunk+0x5/0xfbef5
[    3.725323]  ? exc_page_fault+0x85/0xa0
[    3.725594]  entry_SYSCALL_64_after_hwframe+0x74/0x7c
[    3.725920] RIP: 0033:0x42846d
[    3.726129] Code: d5 48 8d 3c 0a eb 91 66 0f 1f 44 00 00 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 f0 ff ff ff f7 d8 64 89 01 48
[    3.727351] RSP: 002b:00007fffc51eb9b8 EFLAGS: 00000246 ORIG_RAX: 0000000000000000
[    3.727868] RAX: ffffffffffffffda RBX: 0000000000000005 RCX: 000000000042846d
[    3.728594] RDX: 0000000000000100 RSI: 00007fffc51ebb00 RDI: 0000000000000005
[    3.729067] RBP: 0000000000000003 R08: 0000000000000000 R09: 0000000000000000
[    3.729551] R10: 00007fffc51eb890 R11: 0000000000000246 R12: 000000000048ac28
[    3.730612] R13: 00007f3cc2795078 R14: 000000000000c3da R15: 0000000000000000
[    3.731029]  </TASK>
---
#define _GNU_SOURCE

#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>

#ifndef __NR_bpf
#define __NR_bpf 321
#endif

#define BPF_PROG_LOAD 5
#define BPF_LINK_CREATE 28
#define BPF_ITER_CREATE 33

#define BPF_PROG_TYPE_TRACING 26
#define BPF_TRACE_ITER 28
#define BPF_F_SLEEPABLE (1U << 4)

#define BPF_OBJ_NAME_LEN 16

#define BTF_MAGIC 0xeb9f
#define BTF_KIND_INT 1
#define BTF_KIND_PTR 2
#define BTF_KIND_ARRAY 3
#define BTF_KIND_STRUCT 4
#define BTF_KIND_UNION 5
#define BTF_KIND_ENUM 6
#define BTF_KIND_FWD 7
#define BTF_KIND_TYPEDEF 8
#define BTF_KIND_VOLATILE 9
#define BTF_KIND_CONST 10
#define BTF_KIND_RESTRICT 11
#define BTF_KIND_FUNC 12
#define BTF_KIND_FUNC_PROTO 13
#define BTF_KIND_VAR 14
#define BTF_KIND_DATASEC 15
#define BTF_KIND_FLOAT 16
#define BTF_KIND_DECL_TAG 17
#define BTF_KIND_TYPE_TAG 18
#define BTF_KIND_ENUM64 19

struct bpf_insn {
	uint8_t code;
	uint8_t dst_reg:4;
	uint8_t src_reg:4;
	int16_t off;
	int32_t imm;
};

union bpf_attr_local {
	struct {
		uint32_t prog_type;
		uint32_t insn_cnt;
		uint64_t insns;
		uint64_t license;
		uint32_t log_level;
		uint32_t log_size;
		uint64_t log_buf;
		uint32_t kern_version;
		uint32_t prog_flags;
		char prog_name[BPF_OBJ_NAME_LEN];
		uint32_t prog_ifindex;
		uint32_t expected_attach_type;
		uint32_t prog_btf_fd;
		uint32_t func_info_rec_size;
		uint64_t func_info;
		uint32_t func_info_cnt;
		uint32_t line_info_rec_size;
		uint64_t line_info;
		uint32_t line_info_cnt;
		uint32_t attach_btf_id;
	} prog_load;
	struct {
		uint32_t prog_fd;
		uint32_t target_fd;
		uint32_t attach_type;
		uint32_t flags;
		uint64_t iter_info;
		uint32_t iter_info_len;
	} link_create;
	struct {
		uint32_t link_fd;
		uint32_t flags;
	} iter_create;
	uint8_t pad[256];
};

struct bpf_iter_link_info_task {
	uint32_t tid;
	uint32_t pid;
	uint32_t pid_fd;
};

struct btf_header {
	uint16_t magic;
	uint8_t version;
	uint8_t flags;
	uint32_t hdr_len;
	uint32_t type_off;
	uint32_t type_len;
	uint32_t str_off;
	uint32_t str_len;
};

struct btf_type {
	uint32_t name_off;
	uint32_t info;
	union {
		uint32_t size;
		uint32_t type;
	};
};

static uint64_t ptr_to_u64(const void *ptr)
{
	return (uint64_t)(uintptr_t)ptr;
}

static long sys_bpf(int cmd, union bpf_attr_local *attr)
{
	return syscall(__NR_bpf, cmd, attr, sizeof(*attr));
}

static void bump_memlock_rlimit(void)
{
	struct rlimit rlim = {
		.rlim_cur = RLIM_INFINITY,
		.rlim_max = RLIM_INFINITY,
	};

	syscall(SYS_setrlimit, RLIMIT_MEMLOCK, &rlim);
}

static void *read_file(const char *path, size_t *size_out)
{
	ssize_t n;
	size_t done = 0;
	off_t size;
	char *buf;
	int fd;

	fd = syscall(SYS_openat, AT_FDCWD, path, O_RDONLY);
	if (fd < 0) {
		perror("openat(/sys/kernel/btf/vmlinux)");
		return NULL;
	}

	size = syscall(SYS_lseek, fd, 0, SEEK_END);
	if (size <= 0) {
		perror("lseek(SEEK_END)");
		syscall(SYS_close, fd);
		return NULL;
	}
	if (syscall(SYS_lseek, fd, 0, SEEK_SET) < 0) {
		perror("lseek(SEEK_SET)");
		syscall(SYS_close, fd);
		return NULL;
	}

	buf = malloc(size);
	if (!buf) {
		perror("malloc");
		syscall(SYS_close, fd);
		return NULL;
	}

	while (done < (size_t)size) {
		n = syscall(SYS_read, fd, buf + done, (size_t)size - done);
		if (n < 0) {
			if (errno == EINTR)
				continue;
			perror("read(/sys/kernel/btf/vmlinux)");
			free(buf);
			syscall(SYS_close, fd);
			return NULL;
		}
		if (n == 0)
			break;
		done += (size_t)n;
	}

	syscall(SYS_close, fd);
	*size_out = done;
	return buf;
}

static uint32_t btf_kind(uint32_t info)
{
	return (info >> 24) & 0x1f;
}

static uint32_t btf_vlen(uint32_t info)
{
	return info & 0xffff;
}

static size_t btf_extra_size(const struct btf_type *t)
{
	uint32_t kind = btf_kind(t->info);
	uint32_t vlen = btf_vlen(t->info);

	switch (kind) {
	case BTF_KIND_INT:
	case BTF_KIND_VAR:
	case BTF_KIND_DECL_TAG:
		return 4;
	case BTF_KIND_ARRAY:
		return 12;
	case BTF_KIND_STRUCT:
	case BTF_KIND_UNION:
		return (size_t)vlen * 12;
	case BTF_KIND_ENUM:
		return (size_t)vlen * 8;
	case BTF_KIND_FUNC_PROTO:
		return (size_t)vlen * 8;
	case BTF_KIND_DATASEC:
		return (size_t)vlen * 12;
	case BTF_KIND_ENUM64:
		return (size_t)vlen * 12;
	case BTF_KIND_PTR:
	case BTF_KIND_FWD:
	case BTF_KIND_TYPEDEF:
	case BTF_KIND_VOLATILE:
	case BTF_KIND_CONST:
	case BTF_KIND_RESTRICT:
	case BTF_KIND_FUNC:
	case BTF_KIND_FLOAT:
	case BTF_KIND_TYPE_TAG:
		return 0;
	default:
		fprintf(stderr, "unknown BTF kind %u\n", kind);
		exit(1);
	}
}

static int find_btf_func_id(const char *name)
{
	const struct btf_header *hdr;
	const uint8_t *types, *types_end;
	const char *strs;
	size_t btf_size;
	void *btf;
	uint32_t id = 1;

	btf = read_file("/sys/kernel/btf/vmlinux", &btf_size);
	if (!btf)
		return -1;
	if (btf_size < sizeof(*hdr)) {
		fprintf(stderr, "short BTF header\n");
		free(btf);
		return -1;
	}

	hdr = btf;
	if (hdr->magic != BTF_MAGIC) {
		fprintf(stderr, "unexpected BTF magic 0x%x\n", hdr->magic);
		free(btf);
		return -1;
	}
	if ((size_t)hdr->hdr_len + hdr->type_off + hdr->type_len > btf_size ||
	    (size_t)hdr->hdr_len + hdr->str_off + hdr->str_len > btf_size) {
		fprintf(stderr, "invalid BTF section bounds\n");
		free(btf);
		return -1;
	}

	types = (const uint8_t *)btf + hdr->hdr_len + hdr->type_off;
	types_end = types + hdr->type_len;
	strs = (const char *)btf + hdr->hdr_len + hdr->str_off;

	while (types + sizeof(struct btf_type) <= types_end) {
		const struct btf_type *t = (const struct btf_type *)types;
		size_t rec_size = sizeof(*t) + btf_extra_size(t);

		if (types + rec_size > types_end) {
			fprintf(stderr, "truncated BTF type section\n");
			free(btf);
			return -1;
		}

		if (btf_kind(t->info) == BTF_KIND_FUNC &&
		    t->name_off < hdr->str_len &&
		    strcmp(strs + t->name_off, name) == 0) {
			free(btf);
			return (int)id;
		}

		types += rec_size;
		id++;
	}

	fprintf(stderr, "BTF FUNC %s not found\n", name);
	free(btf);
	return -1;
}

static int load_prog(uint32_t attach_btf_id)
{
	static char log_buf[1 << 20];
	static char license[] = "GPL";
	struct bpf_insn insns[] = {
		{ .code = 0xb7, .dst_reg = 0, .src_reg = 0, .off = 0, .imm = 0 },
		{ .code = 0x95, .dst_reg = 0, .src_reg = 0, .off = 0, .imm = 0 },
	};
	union bpf_attr_local attr;
	int fd;

	memset(&attr, 0, sizeof(attr));
	attr.prog_load.prog_type = BPF_PROG_TYPE_TRACING;
	attr.prog_load.insn_cnt = sizeof(insns) / sizeof(insns[0]);
	attr.prog_load.insns = ptr_to_u64(insns);
	attr.prog_load.license = ptr_to_u64(license);
	attr.prog_load.log_level = 1;
	attr.prog_load.log_size = sizeof(log_buf);
	attr.prog_load.log_buf = ptr_to_u64(log_buf);
	attr.prog_load.prog_flags = BPF_F_SLEEPABLE;
	memcpy(attr.prog_load.prog_name, "tvma_sleep", sizeof("tvma_sleep"));
	attr.prog_load.expected_attach_type = BPF_TRACE_ITER;
	attr.prog_load.attach_btf_id = attach_btf_id;

	fd = (int)sys_bpf(BPF_PROG_LOAD, &attr);
	if (fd < 0) {
		perror("BPF_PROG_LOAD");
		if (log_buf[0])
			fprintf(stderr, "verifier log:\n%s\n", log_buf);
		return -1;
	}

	return fd;
}

static int create_iter_fd(int prog_fd, uint32_t pid)
{
	struct bpf_iter_link_info_task iter_info = {
		.tid = 0,
		.pid = pid,
		.pid_fd = 0,
	};
	union bpf_attr_local attr;
	int link_fd, iter_fd;

	memset(&attr, 0, sizeof(attr));
	attr.link_create.prog_fd = prog_fd;
	attr.link_create.target_fd = 0;
	attr.link_create.attach_type = BPF_TRACE_ITER;
	attr.link_create.flags = 0;
	attr.link_create.iter_info = ptr_to_u64(&iter_info);
	attr.link_create.iter_info_len = sizeof(iter_info);

	link_fd = (int)sys_bpf(BPF_LINK_CREATE, &attr);
	if (link_fd < 0) {
		perror("BPF_LINK_CREATE");
		return -1;
	}

	memset(&attr, 0, sizeof(attr));
	attr.iter_create.link_fd = link_fd;
	attr.iter_create.flags = 0;

	iter_fd = (int)sys_bpf(BPF_ITER_CREATE, &attr);
	if (iter_fd < 0) {
		perror("BPF_ITER_CREATE");
		return -1;
	}

	return iter_fd;
}

int main(int argc, char **argv)
{
	char buf[256];
	uint32_t pid = 1;
	int attach_btf_id;
	int prog_fd;
	int iter_fd;
	ssize_t n;

	if (argc > 1)
		pid = (uint32_t)strtoul(argv[1], NULL, 0);

	printf("[repro] target task_vma pid=%u\n", pid);
	bump_memlock_rlimit();

	attach_btf_id = find_btf_func_id("bpf_iter_task_vma");
	if (attach_btf_id < 0)
		return 1;
	printf("[repro] bpf_iter_task_vma attach_btf_id=%d\n", attach_btf_id);

	prog_fd = load_prog((uint32_t)attach_btf_id);
	if (prog_fd < 0)
		return 1;
	printf("[repro] prog_fd=%d\n", prog_fd);

	iter_fd = create_iter_fd(prog_fd, pid);
	if (iter_fd < 0)
		return 1;
	printf("[repro] iter_fd=%d\n", iter_fd);

	n = syscall(SYS_read, iter_fd, buf, sizeof(buf));
	if (n < 0) {
		perror("read(iter_fd)");
		return 1;
	}

	printf("[repro] read returned %zd\n", n);
	return 0;
}

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

end of thread, other threads:[~2026-07-27 11:55 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-27 11:40 BUG: possible recursive locking detected in sleepable task_vma iterator Junseo Lim
2026-07-27 11:55 ` Lorenzo Stoakes (ARM)

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