Wednesday, September 26, 2018

A cache invalidation bug in Linux memory management

Posted by Jann Horn, Google Project Zero

This blogpost describes a way to exploit a Linux kernel bug (CVE-2018-17182) that exists since kernel version 3.16. While cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug itself is in code that is reachable even from relatively strongly sandboxed contexts, this blogpost only describes a way to exploit it in environments that use Linux kernels that haven't been configured for increased security (specifically, Ubuntu 18.04 with kernel linux-image-4.15.0-34-generic at version 4.15.0-34.37). This demonstrates how cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel configuration can have a big impact on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 difficulty of exploiting a kernel bug.

The bug report and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit are filed in our issue tracker as issue 1664.

Fixes for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 issue are in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 upstream stable releases 4.18.9, 4.14.71, 4.9.128, 4.4.157 and 3.16.58.

The bug

Whenever a userspace page fault occurs because e.g. a page has to be paged in on demand, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Linux kernel has to look up cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 VMA (virtual memory area; struct vm_area_struct) that contains cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fault address to figure out how cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fault should be handled. The slowpath for looking up a VMA (in find_vma()) has to walk a red-black tree of VMAs. To avoid this performance hit, Linux also has a fastpath that can bypass cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 tree walk if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 VMA was recently used.

The implementation of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fastpath has changed over time; since version 3.15, Linux uses per-thread VMA caches with four slots, implemented in mm/vmacache.c and include/linux/vmacache.h. Whenever a successful lookup has been performed through cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 slowpath, vmacache_update() stores a pointer to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 VMA in an entry of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 array current->vmacache.vmas, allowing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 next lookup to use cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fastpath.

Note that VMA caches are per-thread, but VMAs are associated with a whole process (more precisely with a struct mm_struct; from now on, this distinction will largely be ignored, since it isn't relevant to this bug). Therefore, when a VMA is freed, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 VMA caches of all threads must be invalidated - ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365rwise, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 next VMA lookup would follow a dangling pointer. However, since a process can have many threads, simply iterating through cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 VMA caches of all threads would be a performance problem.

To solve this, both cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 struct mm_struct and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 per-thread struct vmacache are tagged with sequence numbers; when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 VMA lookup fastpath discovers in vmacache_valid() that current->vmacache.seqnum and current->mm->vmacache_seqnum don't match, it wipes cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 contents of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 current thread's VMA cache and updates its sequence number.

The sequence numbers of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mm_struct and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 VMA cache were only 32 bits wide, meaning that it was possible for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m to overflow. To ensure that a VMA cache can't incorrectly appear to be valid when current->mm->vmacache_seqnum has actually been incremented 232 times, vmacache_invalidate() (cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 helper that increments current->mm->vmacache_seqnum) had a special case: When current->mm->vmacache_seqnum wrapped to zero, it would call vmacache_flush_all() to wipe cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 contents of all VMA caches associated with current->mm. Executing vmacache_flush_all() was very expensive: It would iterate over every thread on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 entire machine, check which struct mm_struct it is associated with, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n if necessary flush cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 thread's VMA cache.

In version 3.16, an optimization was added: If cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 struct mm_struct was only associated with a single thread, vmacache_flush_all() would do nothing, based on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 realization that every VMA cache invalidation is preceded by a VMA lookup; cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365refore, in a single-threaded process, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 VMA cache's sequence number is always close to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mm_struct's sequence number:

/*
* Single threaded tasks need not iterate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 entire
* list of process. We can avoid cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 flushing as well
* since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mm's seqnum was increased and don't have
* to worry about ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r threads' seqnum. Current's
* flush will occur upon cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 next lookup.
*/
if (atomic_read(&mm->mm_users) == 1)
return;

However, this optimization is incorrect because it doesn't take into account what happens if a previously single-threaded process creates a new thread immediately after cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mm_struct's sequence number has wrapped around to zero. In this case, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 sequence number of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first thread's VMA cache will still be 0xffffffff, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 second thread can drive cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mm_struct's sequence number up to 0xffffffff again. At that point, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first thread's VMA cache, which can contain dangling pointers, will be considered valid again, permitting cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 use of freed VMA pointers in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first thread's VMA cache.

The bug was fixed by changing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 sequence numbers to 64 bits, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365reby making an overflow infeasible, and removing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 overflow handling logic.

Reachability and Impact

Fundamentally, this bug can be triggered by any process that can run for a sufficiently long time to overflow cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 reference counter (about an hour if MAP_FIXED is usable) and has cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ability to use mmap()/munmap() (to manage memory mappings) and clone() (to create a thread). These syscalls do not require any privileges, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365y are often permitted even in seccomp-sandboxed contexts, such as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Chrome renderer sandbox (mmap, munmap, clone), cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 sandbox of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 main gVisor host component, and Docker's seccomp policy.

To make things easy, my exploit uses various ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r kernel interfaces, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365refore doesn't just work from inside such sandboxes; in particular, it uses /dev/kmsg to read dmesg logs and uses an eBPF array to spam cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel's page allocator with user-controlled, mutable single-page allocations. However, an attacker willing to invest more time into an exploit would probably be able to avoid using such interfaces.

Interestingly, it looks like Docker in its default config doesn't prevent containers from accessing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 host's dmesg logs if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel permits dmesg access for normal users - while /dev/kmsg doesn't exist in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 container, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 seccomp policy whitelists cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 syslog() syscall for some reason.

BUG_ON(), WARN_ON_ONCE(), and dmesg

The function in which cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first use-after-free access occurs is vmacache_find(). When this function was first added - before cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug was introduced -, it accessed cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 VMA cache as follows:

      for (i = 0; i < VMACACHE_SIZE; i++) {
              struct vm_area_struct *vma = current->vmacache[i];

              if (vma && vma->vm_start <= addr && vma->vm_end > addr) {
                      BUG_ON(vma->vm_mm != mm);
                      return vma;
              }
      }

When this code encountered a cached VMA whose bounds contain cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 supplied address addr, it checked whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 VMA's ->vm_mm pointer matches cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 expected mm_struct - which should always be cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 case, unless a memory safety problem has happened -, and if not, terminated with a BUG_ON() assertion failure. BUG_ON() is intended to handle cases in which a kernel thread detects a severe problem that can't be cleanly handled by bailing out of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 current context. In a default upstream kernel configuration, BUG_ON() will normally print a backtrace with register dumps to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 dmesg log buffer, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n forcibly terminate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 current thread. This can sometimes prevent cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 rest of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 system from continuing to work properly - for example, if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 crashing code held an important lock, any ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r thread that attempts to take that lock will cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n deadlock -, but it is often successful in keeping cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 rest of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 system in a reasonably usable state. Only when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel detects that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 crash is in a critical context such as an interrupt handler, it brings down cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 whole system with a kernel panic.

The same handler code is used for dealing with unexpected crashes in kernel code, like page faults and general protection faults at non-whitelisted addresses: By default, if possible, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel will attempt to terminate only cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 offending thread.

The handling of kernel crashes is a tradeoff between availability, reliability and security. A system owner might want a system to keep running as long as possible, even if parts of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 system are crashing, if a sudden kernel panic would cause data loss or downtime of an important service. Similarly, a system owner might want to debug a kernel bug on a live system, without an external debugger; if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 whole system terminated as soon as cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug is triggered, it might be harder to debug an issue properly.
On cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r hand, an attacker attempting to exploit a kernel bug might benefit from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ability to retry an attack multiple times without triggering system reboots; and an attacker with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ability to read cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 crash log produced by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first attempt might even be able to use that information for a more sophisticated second attempt.

The kernel provides two sysctls that can be used to adjust this behavior, depending on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 desired tradeoff:

  • kernel.panic_on_oops will automatically cause a kernel panic when a BUG_ON() assertion triggers or cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel crashes; its initial value can be configured using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 build configuration variable CONFIG_PANIC_ON_OOPS. It is off by default in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 upstream kernel - and enabling it by default in distributions would probably be a bad idea -, but it is e.g. enabled by Android.
  • kernel.dmesg_restrict controls whecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r non-root users can access dmesg logs, which, among ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r things, contain register dumps and stack traces for kernel crashes; its initial value can be configured using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 build configuration variable CONFIG_SECURITY_DMESG_RESTRICT. It is off by default in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 upstream kernel, but is enabled by some distributions, e.g. Debian. (Android relies on SELinux to block access to dmesg.)

Ubuntu, for example, enables neicá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se.


The code snippet from above was amended in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same month as it was committed:

      for (i = 0; i < VMACACHE_SIZE; i++) {
               struct vm_area_struct *vma = current->vmacache[i];
-               if (vma && vma->vm_start <= addr && vma->vm_end > addr) {
-                       BUG_ON(vma->vm_mm != mm);
+               if (!vma)
+                       continue;
+               if (WARN_ON_ONCE(vma->vm_mm != mm))
+                       break;
+               if (vma->vm_start <= addr && vma->vm_end > addr)
                       return vma;
-               }
       }

This amended code is what distributions like Ubuntu are currently shipping.

The first change here is that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 sanity check for a dangling pointer happens before cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 address comparison. The second change is somewhat more interesting: BUG_ON() is replaced with WARN_ON_ONCE().

WARN_ON_ONCE() prints debug information to dmesg that is similar to what BUG_ON() would print. The differences to BUG_ON() are that WARN_ON_ONCE() only prints debug information cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first time it triggers, and that execution continues: Now when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel detects a dangling pointer in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 VMA cache lookup fastpath - in ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r words, when it heuristically detects that a use-after-free has happened -, it just bails out of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fastpath and falls back to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 red-black tree walk. The process continues normally.

This fits in with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel's policy of attempting to keep cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 system running as much as possible by default; if an accidental use-after-free bug occurs here for some reason, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel can probably heuristically mitigate its effects and keep cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 process working.

The policy of only printing a warning even when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel has discovered a memory corruption is problematic for systems that should kernel panic when cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel notices security-relevant events like kernel memory corruption. Simply making WARN() trigger kernel panics isn't really an option because WARN() is also used for various events that are not important to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel's security. For this reason, a few uses of WARN_ON() in security-relevant places have been replaced with CHECK_DATA_CORRUPTION(), which permits toggling cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 behavior between BUG() and WARN() at kernel configuration time. However, CHECK_DATA_CORRUPTION() is only used in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 linked list manipulation code and in addr_limit_user_check(); cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 check in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 VMA cache, for example, still uses a classic WARN_ON_ONCE().


A third important change was made to this function; however, this change is relatively recent and will first be in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 4.19 kernel, which hasn't been released yet, so it is irrelevant for attacking currently deployed kernels.

      for (i = 0; i < VMACACHE_SIZE; i++) {
-               struct vm_area_struct *vma = current->vmacache.vmas[i];
+               struct vm_area_struct *vma = current->vmacache.vmas[idx];
-               if (!vma)
-                       continue;
-               if (WARN_ON_ONCE(vma->vm_mm != mm))
-                       break;
-               if (vma->vm_start <= addr && vma->vm_end > addr) {
-                       count_vm_vmacache_event(VMACACHE_FIND_HITS);
-                       return vma;
+               if (vma) {
+#ifdef CONFIG_DEBUG_VM_VMACACHE
+                       if (WARN_ON_ONCE(vma->vm_mm != mm))
+                               break;
+#endif
+                       if (vma->vm_start <= addr && vma->vm_end > addr) {
+                               count_vm_vmacache_event(VMACACHE_FIND_HITS);
+                               return vma;
+                       }
               }
+               if (++idx == VMACACHE_SIZE)
+                       idx = 0;
       }

After this change, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 sanity check is skipped altogecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r unless cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel is built with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 debugging option CONFIG_DEBUG_VM_VMACACHE.

The exploit: Incrementing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 sequence number

The exploit has to increment cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 sequence number roughly 233 times. Therefore, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 efficiency of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 primitive used to increment cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 sequence number is important for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 runtime of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 whole exploit.

It is possible to cause two sequence number increments per syscall as follows: Create an anonymous VMA that spans three pages. Then repeatedly use mmap() with MAP_FIXED to replace cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 middle page with an equivalent VMA. This causes mmap() to first split cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 VMA into three VMAs, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n replace cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 middle VMA, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n merge cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 three VMAs togecá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r again, causing VMA cache invalidations for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 two VMAs that are deleted while merging cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 VMAs.

The exploit: Replacing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 VMA

Enumerating all potential ways to attack cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 use-after-free without releasing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 slab's backing page (according to /proc/slabinfo, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Ubuntu kernel uses one page per vm_area_struct slab) back to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 buddy allocator / page allocator:

  1. Get cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vm_area_struct reused in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 same process. The process would cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n be able to use this VMA, but this doesn't result in anything interesting, since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 VMA caches of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 process would be allowed to contain pointers to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 VMA anyway.
  2. Free cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vm_area_struct such that it is on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 slab allocator's freelist, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n attempt to access it. However, at least cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 SLUB allocator that Ubuntu uses replaces cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first 8 bytes of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vm_area_struct (which contain vm_start, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 userspace start address) with a kernel address. This makes it impossible for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 VMA cache lookup function to return it, since cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 condition vma->vm_start <= addr && vma->vm_end > addr can't be fulfilled, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365refore nothing interesting happens.
  3. Free cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vm_area_struct such that it is on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 slab allocator's freelist, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n allocate it in anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r process. This would (with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exception of a very narrow race condition that can't easily be triggered repeatedly) result in hitting cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 WARN_ON_ONCE(), and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365refore cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 VMA cache lookup function wouldn't return cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 VMA.
  4. Free cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vm_area_struct such that it is on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 slab allocator's freelist, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n make an allocation from a slab that has been merged with cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 vm_area_struct slab. This requires cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 existence of an aliasing slab; in a Ubuntu 18.04 VM, no such slab seems to exist.

Therefore, to exploit this bug, it is necessary to release cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 backing page back to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 page allocator, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n reallocate cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 page in some way that permits placing controlled data in it. There are various kernel interfaces that could be used for this; for example:

pipe pages:
  • advantage: not wiped on allocation
  • advantage: permits writing at an arbitrary in-page offset if splice() is available
  • advantage: page-aligned
  • disadvantage: can't do multiple writes without first freeing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 page, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n reallocating it

BPF maps:
  • advantage: can repeatedly read and write contents from userspace
  • advantage: page-aligned
  • disadvantage: wiped on allocation

This exploit uses BPF maps.

The exploit: Leaking pointers from dmesg

The exploit wants to have cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 following information:

  • address of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 mm_struct
  • address of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 use-after-free'd VMA
  • load address of kernel code

At least in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 Ubuntu 18.04 kernel, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 first two of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365se are directly visible in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 register dump triggered by WARN_ON_ONCE(), and can cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365refore easily be extracted from dmesg: The mm_struct's address is in RDI, and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 VMA's address is in RAX. However, an instruction pointer is not directly visible because RIP and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack are symbolized, and none of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 general-purpose registers contain an instruction pointer.

A kernel backtrace can contain multiple sets of registers: When cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 stack backtracing logic encounters an interrupt frame, it generates anocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r register dump. Since we can trigger cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 WARN_ON_ONCE() through a page fault on a userspace address, and page faults on userspace addresses can happen at any userspace memory access in syscall context (via copy_from_user()/copy_to_user()/...), we can pick a call site that has cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 relevant information in a register from a wide range of choices. It turns out that writing to an eventfd triggers a usercopy while R8 still contains cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 pointer to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 eventfd_fops structure.

When cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit runs, it replaces cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 VMA with zeroed memory, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365n triggers a VMA lookup against cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 broken VMA cache, intentionally triggering cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 WARN_ON_ONCE(). This generates a warning that looks as follows - cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 leaks used by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit are highlighted:

[ 3482.271265] WARNING: CPU: 0 PID: 1871 at /build/linux-SlLHxe/linux-4.15.0/mm/vmacache.c:102 vmacache_find+0x9c/0xb0
[...]
[ 3482.271298] RIP: 0010:vmacache_find+0x9c/0xb0
[ 3482.271299] RSP: 0018:ffff9e0bc2263c60 EFLAGS: 00010203
[ 3482.271300] RAX: ffff8c7caf1d61a0 RBX: 00007fffffffd000 RCX: 0000000000000002
[ 3482.271301] RDX: 0000000000000002 RSI: 00007fffffffd000 RDI: ffff8c7c214c7380
[ 3482.271301] RBP: ffff9e0bc2263c60 R08: 0000000000000000 R09: 0000000000000000
[ 3482.271302] R10: 0000000000000000 R11: 0000000000000000 R12: ffff8c7c214c7380
[ 3482.271303] R13: ffff9e0bc2263d58 R14: ffff8c7c214c7380 R15: 0000000000000014
[ 3482.271304] FS:  00007f58c7bf6a80(0000) GS:ffff8c7cbfc00000(0000) knlGS:0000000000000000
[ 3482.271305] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 3482.271305] CR2: 00007fffffffd000 CR3: 00000000a143c004 CR4: 00000000003606f0
[ 3482.271308] DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000
[ 3482.271309] DR3: 0000000000000000 DR6: 00000000fffe0ff0 DR7: 0000000000000400
[ 3482.271309] Call Trace:
[ 3482.271314]  find_vma+0x1b/0x70
[ 3482.271318]  __do_page_fault+0x174/0x4d0
[ 3482.271320]  do_page_fault+0x2e/0xe0
[ 3482.271323]  do_async_page_fault+0x51/0x80
[ 3482.271326]  async_page_fault+0x25/0x50
[ 3482.271329] RIP: 0010:copy_user_generic_unrolled+0x86/0xc0
[ 3482.271330] RSP: 0018:ffff9e0bc2263e08 EFLAGS: 00050202
[ 3482.271330] RAX: 00007fffffffd008 RBX: 0000000000000008 RCX: 0000000000000001
[ 3482.271331] RDX: 0000000000000000 RSI: 00007fffffffd000 RDI: ffff9e0bc2263e30
[ 3482.271332] RBP: ffff9e0bc2263e20 R08: ffffffffa7243680 R09: 0000000000000002
[ 3482.271333] R10: ffff8c7bb4497738 R11: 0000000000000000 R12: ffff9e0bc2263e30
[ 3482.271333] R13: ffff8c7bb4497700 R14: ffff8c7cb7a72d80 R15: ffff8c7bb4497700
[ 3482.271337]  ? _copy_from_user+0x3e/0x60
[ 3482.271340]  eventfd_write+0x74/0x270
[ 3482.271343]  ? common_file_perm+0x58/0x160
[ 3482.271345]  ? wake_up_q+0x80/0x80
[ 3482.271347]  __vfs_write+0x1b/0x40
[ 3482.271348]  vfs_write+0xb1/0x1a0
[ 3482.271349]  SyS_write+0x55/0xc0
[ 3482.271353]  do_syscall_64+0x73/0x130
[ 3482.271355]  entry_SYSCALL_64_after_hwframe+0x3d/0xa2
[ 3482.271356] RIP: 0033:0x55a2e8ed76a6
[ 3482.271357] RSP: 002b:00007ffe71367ec8 EFLAGS: 00000202 ORIG_RAX: 0000000000000001
[ 3482.271358] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 000055a2e8ed76a6
[ 3482.271358] RDX: 0000000000000008 RSI: 00007fffffffd000 RDI: 0000000000000003
[ 3482.271359] RBP: 0000000000000001 R08: 0000000000000000 R09: 0000000000000000
[ 3482.271359] R10: 0000000000000000 R11: 0000000000000202 R12: 00007ffe71367ec8
[ 3482.271360] R13: 00007fffffffd000 R14: 0000000000000009 R15: 0000000000000000
[ 3482.271361] Code: 00 48 8b 84 c8 10 08 00 00 48 85 c0 74 11 48 39 78 40 75 17 48 39 30 77 06 48 39 70 08 77 8d 83 c2 01 83 fa 04 75 ce 31 c0 5d c3 <0f> 0b 31 c0 5d c3 90 90 90 90 90 90 90 90 90 90 90 90 90 90 0f
[ 3482.271381] ---[ end trace bf256b6e27ee4552 ]---

At this point, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 exploit can create a fake VMA that contains cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 correct mm_struct pointer (leaked from RDI). It also populates ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r fields with references to fake data structures (by creating pointers back into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fake VMA using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 leaked VMA pointer from RAX) and with pointers into cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel's code (using cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 leaked R8 from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 page fault exception frame to bypass KASLR).

The exploit: JOP (cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 boring part)

It is probably possible to exploit this bug in some really elegant way by abusing cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ability to overlay a fake writable VMA over existing readonly pages, or something like that; however, this exploit just uses classic jump-oriented programming.

To trigger cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 use-after-free a second time, a writing memory access is performed on an address that has no pagetable entries. At this point, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel's page fault handler comes in via page_fault -> do_page_fault -> __do_page_fault -> handle_mm_fault -> __handle_mm_fault -> handle_pte_fault -> do_fault -> do_shared_fault -> __do_fault, at which point it performs an indirect call:

static int __do_fault(struct vm_fault *vmf)
{
struct vm_area_struct *vma = vmf->vma;
int ret;

ret = vma->vm_ops->fault(vmf);

vma is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 VMA structure we control, so at this point, we can gain instruction pointer control. R13 contains a pointer to vma. The JOP chain that is used from cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365re on follows; it is quite crude (for example, it crashes after having done its job), but it works.

First, to move cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 VMA pointer to RDI:

ffffffff810b5c21: 49 8b 45 70           mov rax,QWORD PTR [r13+0x70]
ffffffff810b5c25: 48 8b 80 88 00 00 00  mov rax,QWORD PTR [rax+0x88]
ffffffff810b5c2c: 48 85 c0              test rax,rax
ffffffff810b5c2f: 74 08                 je ffffffff810b5c39
ffffffff810b5c31: 4c 89 ef              mov rdi,r13
ffffffff810b5c34: e8 c7 d3 b4 00        call ffffffff81c03000 <__x86_indirect_thunk_rax>

Then, to get full control over RDI:

ffffffff810a4aaa: 48 89 fb              mov rbx,rdi
ffffffff810a4aad: 48 8b 43 20           mov rax,QWORD PTR [rbx+0x20]
ffffffff810a4ab1: 48 8b 7f 28           mov rdi,QWORD PTR [rdi+0x28]
ffffffff810a4ab5: e8 46 e5 b5 00        call ffffffff81c03000 <__x86_indirect_thunk_rax>

At this point, we can call into run_cmd(), which spawns a root-privileged usermode helper, using a space-delimited path and argument list as its only argument. This gives us cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 ability to run a binary we have supplied with root privileges. (Thanks to Mark for pointing out that if you control RDI and RIP, you don't have to try to do crazy things like flipping cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 SM*P bits in CR4, you can just spawn a usermode helper...)

After launching cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 usermode helper, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel crashes with a page fault because cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 JOP chain doesn't cleanly terminate; however, since that only kills cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 process in whose context cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fault occured, it doesn't really matter.

Fix timeline

This bug was reported 2018-09-12. Two days later, 2018-09-14, a fix was in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 upstream kernel tree. This is exceptionally fast, compared to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fix times of ocá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365r software vendors. At this point, downstream vendors could cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365oretically backport and apply cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 patch. The bug is essentially public at this point, even if its security impact is obfuscated by cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 commit message, which is frequently demonstrated by grsecurity.

However, a fix being in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 upstream kernel does not automatically mean that users' systems are actually patched. The normal process for shipping fixes to users who use distribution kernels based on upstream stable branches works roughly as follows:

  1. A patch lands in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 upstream kernel.
  2. The patch is backported to an upstream-supported stable kernel.
  3. The distribution merges cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 changes from upstream-supported stable kernels into its kernels.
  4. Users install cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 new distribution kernel.

Note that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 patch becomes public after step 1, potentially allowing attackers to develop an exploit, but users are only protected after step 4.

In this case, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 backport to cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 upstream-supported stable kernels 4.18, 4.14, 4.9 and 4.4 were published 2018-09-19, five days after cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 patch became public, at which point cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 distributions could pull in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 patch.

Upstream stable kernel updates are published very frequently. For example, looking at cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 last few stable releases for cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 4.14 stable kernel, which is cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 newest upstream longterm maintenance release:

4.14.72 on 2018-09-26
4.14.71 on 2018-09-19
4.14.70 on 2018-09-15
4.14.69 on 2018-09-09
4.14.68 on 2018-09-05
4.14.67 on 2018-08-24
4.14.66 on 2018-08-22

The 4.9 and 4.4 longterm maintenance kernels are updated similarly frequently; only cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 3.16 longterm maintenance kernel has not received any updates between cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 most recent update on 2018-09-25 (3.16.58) and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 previous one on 2018-06-16 (3.16.57).

However, Linux distributions often don't publish distribution kernel updates very frequently. For example, Debian stable ships a kernel based on 4.9, but as of 2018-09-26, this kernel was last updated 2018-08-21. Similarly, Ubuntu 16.04 ships a kernel that was last updated 2018-08-27. Android only ships security updates once a month. Therefore, when a security-critical fix is available in an upstream stable kernel, it can still take weeks before cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fix is actually available to users - especially if cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 security impact is not announced publicly.

In this case, cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 security issue was announced on cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 oss-security mailing list on 2018-09-18, with a CVE allocation on 2018-09-19, making cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 need to ship new distribution kernels to users clearer. Still: As of 2018-09-26, both Debian and Ubuntu (in releases 16.04 and 18.04) track cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 bug as unfixed:


Fedora pushed an update to users on 2018-09-22: https://bugzilla.redhat.com/show_bug.cgi?id=1631206#c8

Conclusion

This exploit shows how much impact cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel configuration can have on how easy it is to write an exploit for a kernel bug. While simply turning on every security-related kernel configuration option is probably a bad idea, some of cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365m - like cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel.dmesg_restrict sysctl - seem to provide a reasonable tradeoff when enabled.

The fix timeline shows that cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 kernel's approach to handling severe security bugs is very efficient at quickly landing fixes in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 git master tree, but leaves a window of exposure between cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 time an upstream fix is published and cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 time cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 fix actually becomes available to users - and this time window is sufficiently large that a kernel exploit could be written by an attacker in cá cược thể thao bet365_cách nạp tiền vào bet365_ đăng ký bet365 meantime.

No comments:

Post a Comment