Lucene search

K
seebugRootSSV:97180
HistoryMar 15, 2018 - 12:00 a.m.

Chromium: Read-only SharedMemory descriptors on Android are writable(CVE-2018-6057)

2018-03-1500:00:00
Root
www.seebug.org
109

0.015 Low

EPSS

Percentile

85.4%

VULNERABILITY DETAILS

The base::SharedMemory class represents a shared memory resource that processes can map into their virtual address space.

As shared memory mechanisms differ across operating systems, specialised implementations exist for each OS. In Android’s case, the implementation is largely provided by POSIX-specific code (https://cs.chromium.org/chromium/src/base/memory/shared_memory_posix.cc), with some Android-specific parts (https://cs.chromium.org/chromium/src/base/memory/shared_memory_android.cc).

SharedMemory descriptors are often passed via IPC across process boundaries. In some cases, the shared memory descriptors need to be made read-only before being transferred to a different process, in order to ensure that only the owning process can modify the underlying memory. To retrieve a read-only descriptor that can be transferred over IPC, the base::SharedMemory::GetReadOnlyHandle() method (https://cs.chromium.org/chromium/src/base/memory/shared_memory.h?l=211) can be invoked. In Android’s case, this implementation is provided by the POSIX-specific code path.

Normally, the above code relies on “/dev/shm” in order to produce the shared memory descriptors. Using this approach, the code can keep a descriptor to the same memory region, but opened using O_RDONLY. The same descriptor is then used to provide the read-only handle returned by GetReadOnlyHandle, preventing callers from mapping it as writable.

However, on Android the underlying descriptors are provided using Android’s shared memory subsystem - “ashmem”. Unlike the /dev/shm descriptors used in the generic POSIX implementation, ashmem descriptors do not allow setting mapping permissions on a per-descriptor basis, but rather enforce memory protections on the underlying shared memory region using a special ioctl - ASHMEM_SET_PROT_MASK (http://androidxref.com/kernel_3.18/xref/drivers/staging/android/ashmem.c#763).

The above ioctl can be issued on an opened ashmem descriptor to set the “protection mask” associated with the underlying memory region. Subsequent mmap’s are compared against the current protection mask to ensure that they are allowed. Permissions can only be dropped from the mask, but never added.

Here is a snippet from the Android-specific implementation of base::SharedMemory (https://cs.chromium.org/chromium/src/base/memory/shared_memory_android.cc?l=20):

bool SharedMemory::Create(const SharedMemoryCreateOptions& options) {
  ...

  int fd = ashmem_create_region(
      options.name_deprecated == NULL ? "" : options.name_deprecated->c_str(),
      options.size);

  ...

  int err = ashmem_set_prot_region(shm_.GetHandle(),
                                   PROT_READ | PROT_WRITE | PROT_EXEC);
 
  ...
 
  // Android doesn't appear to have a way to drop write access on an ashmem
  // segment for a single descriptor.  http://crbug.com/320865
  readonly_shm_ = shm_.Duplicate();
  ...
} 

As can be seen above (and as noted in the above comment), since ashmem doesn’t allow setting permissions on a per-descriptor basis, this operation is simply skipped on Android, setting the “read-only” copy of the descriptor to a duplicate of the original descriptor, with the same (permissive) protection mask. Android’s base::SharedMemory uses the POSIX-specific implementation of GetReadOnlyHandle, which simply returns the “read-only” descriptor above. Therefore, calling base::SharedMemory::GetReadOnlyHandle() on Android produces an ashmem descriptor which can be mapped as writable.

This issue is somewhat similar to the one discovered by James Forshaw (https://googleprojectzero.blogspot.com/2014/10/did-man-with-no-name-feel-insecure.html). However, in this case user scripts cannot be used to exploit the issue, as Chromium on Android does not support extensions (and therefore doesn’t include user scripts).

Nonetheless, some resources are shared on an inter-process boundary and are reliant on the fact that they are made read-only be calling GetReadOnlyHandle(). For example, field trials are shared from the Zygote process to all processes (privileged and non-privileged), by sharing a read-only descriptor acquired via GetReadOnlyHandle(). On Android, this allows unprivileged processes (i.e. an unprivileged renderer) to remap the field trials buffer as writable, injecting new trials and parameter values to other (privileged) processes.

Similarly, Mojo relies on GetReadOnlyHandle() to produce read-only memory handles in several different bindings – for example, the C++ bindings allow the creation of a read-only handle by supplying the mojo::SharedBufferHandle::AccessMode::READ_ONLY flag to SharedBufferHandle::Clone (see https://cs.chromium.org/chromium/src/mojo/public/cpp/system/buffer.cc?type=cs&l=26).

This is used by several Mojo services to create read-only handles that can be safely sent to their clients, such as the visited links hash table managed by the browser process (shared to renderers via visitedlink::mojom::VisitedLinkNotificationSink), the status of current sensors (via device::mojom::SensorProvider), the current cursor location, etc.

VERSION

  • Chrome Version: 64.0.3242.0
  • Revision: e325c0c757496334161119ec57db960b8e792375
  • Operating System: Android 8.0.0 AOSP on msm8996

REPRODUCTION CASE

I’m attaching a patch which maps the field trials descriptor as writable and corrupts the PersistentMemoryAllocator’s cookie from the renderer process. Applying the patch and opening a webpage should result in the following logcat output:

11-30 13:52:36.225 29571 29586 D SharedMemoryPoC: Corrupting cookie (was: 408305DC)
...
11-30 13:52:36.891 29608 29627 I chromium: [INFO:library_loader_hooks.cc(36)] Chromium logging enabled: level = 0, default verbosity = 0
11-30 13:52:36.911 29608 29627 E chromium: [ERROR:persistent_memory_allocator.cc(857)] Corruption detected in shared-memory segment
...

(where 29571 is the pid of the renderer process, and 29608 is the pid of the privileged (unsandboxed) process).