fs/proc/task_mmu: do_procmap_query() double mmput()/teardown on build-id error path
When a PROCMAP_QUERY ioctl requests a build ID and the user-supplied buffer is too small to hold it, the function jumps to the out: cleanup label after it has already called query_vma_teardown() + mmput(). The out: label calls them a second time. The extra mmput drops mm_users to zero, triggering _ mmput which calls exit_mmap, destroying the calling process's VMAs and page tables while it is still running. The process crashes with SIGSEGV on return to userspace.
Any unprivileged user can trigger this with a single ioctl. With access to /proc/<pid>/maps of another process (same UID or CAP_SYS_PTRACE), an attacker can destroy the target's address space while the attacker's own process survives.
Root cause
do_procmap_query() acquires one mm_users reference at entry via mmget_not_zero(mm) but releases it twice on the build-id-too-large error path. The function has two phases:
- (VMA locked): query VMA fields, grab file reference
- (VMA unlocked): parse build-id from the file, copy to user
The transition between phases is at lines 768-769:
query_vma_teardown(&lock_ctx); /* unlock VMA *
/
mmput(mm); /
release mm_users: 2 -> 1 */After this, if build_id_parse_file() succeeds but the build ID is larger than the user buffer, line 783 does "goto out". The out: label at line 808-810 runs:
query_vma_teardown(&lock_ctx); /* no-op, already torn down *
/
mmput(mm); /
mm_users: 1 -> 0 -- BUG! */This second mmput drops mm_users to zero and triggers _ mmput -> exit_mmap, which destroys all VMAs and frees all page tables. The function acquired one mm_users reference but released two.
The refcount trace for a forked child:
- fork:
mm_users=1mm_count=1 - open /proc:
mmgrab(+1)->mm_users=1mm_count=2 - ioctl entry:
mmget_not_zero(+1)->mm_users=2mm_count=2 - 1st
mmputln 769:mmput(-1)->mm_users=1mm_count=2 - 2nd
mmputln 810:mmput(-1)->_ mmput! ->mm_users=0mm_count=2exit_mmap: destroys VMAs + page tablesmmdrop(-1):->mm_users=0mm_count=1
- return to user: page fault ->
SIGSEGV(page tables gone)
The process's address space is destroyed while it is still running. Returning to userspace causes a page fault (page tables are gone) and the kernel delivers SIGSEGV.
- self: any unprivileged user can crash their own process.
- cross: an attacker who can open
/proc/<pid>/mapsof a target (sameUID, orCAP_SYS_PTRACE) can issue the ioctl against the target's mm. The doublemmputdestroys the target's address space while the attacker's process is unaffected. This is a targeted process kill that is more disruptive thanSIGKILLbecause it destroys the address space mid-execution, potentially corrupting in-progress writes to files or shared memory.