[PATCH bpf-next 0/9] uprobes: Don't stall fork() when mmap_lock is busy

From: Alexei Starovoitov

Date: Wed Oct 21 2026 - 21:57:34 EST


From: Alexei Starovoitov <ast@xxxxxxxxxx>

uprobe_register() and uprobe_unregister() stall every fork() in the system
when some process keeps its mmap_lock busy:
- register_for_each_vma() sleeps on that mmap_lock with dup_mmap_sem held
for writing.
- fork() of that process sleeps on that mmap_lock with dup_mmap_sem held
for reading. The next register_for_each_vma() waits for it, and other
forks wait for the writer. The process doesn't have to map the file.

We found it the hard way by using uprobes in production. uprobes were
attached to libraries and binaries that many processes map, on a large
number of hosts. On some of them every fork() was stuck for minutes while
uprobe was being registered.

Patch 5 fixes the first one. Patch 6 fixes the second one. Patch 1 is
a helper for them and patch 9 is the selftest.

The rest fix old bugs that patches 5 and 6 make easier to hit:
- Patch 2: exit_mmap() runs under dup_mmap_sem.
- Patches 3, 4: x86. The removal of optimized uprobe that failed half-way
is never finished, and SIGKILL triggers WARN_ON_ONCE().
- Patch 7: the child loses the breakpoint of an inherited perf event.
- Patch 8: the tracer that is killed leaves its breakpoints behind.

A process loops over mmap(4G, MAP_POPULATE) and munmap(). Another process
calls fork() in a loop. The longest fork() while uprobes are being attached
and the time to attach, in msec. 32 cpu VM, HZ=1000, no debug options,
median of 15-45 runs:

fork() attach
before after before after
the busy process maps the file
uprobe_multi, 1 uprobe 201 0.25 217 232
uprobe_multi, 4 uprobes 422 0.34 1483 1558
perf_event, 4 uprobes 421 0.47 3150 3322
it forks and doesn't map the file
uprobe_multi, 1 uprobe 198 0.07 212 9.6
uprobe_multi, 4 uprobes 203 0.08 213 13.4
perf_event, 4 uprobes 419 0.08 630 12.3
nobody keeps mmap_lock busy
uprobe_multi, 1000 processes 1.93 1.83 4.24 4.49
uprobe_multi, 5000 processes 8.88 8.60 8.33 9.23

I don't care which tree it goes through. It targets bpf-next only because
BPF CI will run the patches, including the new selftest.

Signed-off-by: Alexei Starovoitov <ast@xxxxxxxxxx>

Alexei Starovoitov (9):
mm: Bring back mmap_write_trylock()
uprobes: Don't run exit_mmap() under dup_mmap_sem
uprobes/x86: Finish an unoptimization which failed half-way
uprobes/x86: Don't WARN if an optimized probe can't be written
uprobes: Don't sleep on mmap_lock under dup_mmap_sem
uprobes: Don't sleep on mmap_lock with dup_mmap_sem held in dup_mmap()
fork: Let the new task point to its mm before uprobes can find it
uprobes: Don't leak the breakpoints if the unregistering task is
killed
selftests/bpf: Test uprobes and fork() with busy mmap_lock

arch/x86/kernel/uprobes.c | 23 +-
include/linux/mmap_lock.h | 12 +
include/linux/uprobes.h | 5 +
kernel/events/uprobes.c | 260 ++++++++--
kernel/fork.c | 13 +-
mm/internal.h | 3 +-
mm/mmap.c | 33 +-
mm/nommu.c | 3 +-
.../selftests/bpf/prog_tests/uprobe_busy_mm.c | 447 ++++++++++++++++++
.../selftests/bpf/progs/uprobe_busy_mm.c | 22 +
10 files changed, 766 insertions(+), 55 deletions(-)
create mode 100644 tools/testing/selftests/bpf/prog_tests/uprobe_busy_mm.c
create mode 100644 tools/testing/selftests/bpf/progs/uprobe_busy_mm.c

--
2.55.0