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:

  1. (VMA locked): query VMA fields, grab file reference
  2. (VMA unlocked): parse build-id from the file, copy to user

The transition between phases is at lines 768-769:

c
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:

c
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:

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.