]>
Commit | Line | Data |
---|---|---|
5ba3f43e A |
1 | from xnu import * |
2 | from utils import * | |
3 | from core.configuration import * | |
4 | ||
5 | shift = None | |
6 | ||
7 | shadow_strings = { | |
8 | 0x00: 'VALID', | |
9 | 0x01: 'PARTIAL1', | |
10 | 0x02: 'PARTIAL2', | |
11 | 0x03: 'PARTIAL3', | |
12 | 0x04: 'PARTIAL4', | |
13 | 0x05: 'PARTIAL5', | |
14 | 0x06: 'PARTIAL6', | |
15 | 0x07: 'PARTIAL7', | |
16 | 0xac: 'ARRAY_COOKIE', | |
17 | 0xf0: 'STACK_RZ', | |
18 | 0xf1: 'STACK_LEFT_RZ', | |
19 | 0xf2: 'STACK_MID_RZ', | |
20 | 0xf3: 'STACK_RIGHT_RZ', | |
21 | 0xf5: 'STACK_FREED', | |
22 | 0xf8: 'STACK_OOSCOPE', | |
23 | 0xf9: 'GLOBAL_RZ', | |
24 | 0xe9: 'HEAP_RZ', | |
25 | 0xfa: 'HEAP_LEFT_RZ', | |
26 | 0xfb: 'HEAP_RIGHT_RZ', | |
27 | 0xfd: 'HEAP_FREED' | |
28 | } | |
29 | ||
30 | def is_kasan_build(): | |
31 | try: | |
32 | enable = kern.globals.kasan_enabled | |
33 | return True | |
34 | except ValueError, e: | |
35 | return False | |
36 | ||
37 | def shadow_for_address(addr, shift): | |
38 | return ((addr >> 3) + shift) | |
39 | ||
40 | def address_for_shadow(addr, shift): | |
41 | return ((addr - shift) << 3) | |
42 | ||
43 | def get_shadow_byte(shadow_addr): | |
44 | return unsigned(kern.GetValueFromAddress(shadow_addr, 'uint8_t *')[0]) | |
45 | ||
46 | def print_legend(): | |
47 | for (k,v) in shadow_strings.iteritems(): | |
48 | print " {:02x}: {}".format(k,v) | |
49 | ||
50 | def print_shadow_context(addr, context): | |
51 | addr = shadow_for_address(addr, shift) | |
52 | base = (addr & ~0xf) - 16 * context | |
53 | shadow = kern.GetValueFromAddress(unsigned(base), "uint8_t *") | |
54 | ||
55 | print " "*17 + " 0 1 2 3 4 5 6 7 8 9 a b c d e f" | |
56 | for x in range(0, 2*context+1): | |
57 | vals = "" | |
58 | l = " " | |
59 | for y in xrange(x*16, (x+1)*16): | |
60 | r = " " | |
61 | if base+y == addr: | |
62 | l = "[" | |
63 | r = "]" | |
64 | elif base+y+1 == addr: | |
65 | r = "" | |
66 | sh = shadow[y] | |
67 | vals += "{}{:02x}{}".format(l, sh, r) | |
68 | l = "" | |
69 | print("{:x}:{}".format(base + 16*x, vals)) | |
70 | ||
71 | kasan_guard_size = 16 | |
72 | def print_alloc_free_entry(addr, orig_ptr): | |
73 | h = kern.GetValueFromAddress(addr, 'struct freelist_entry *') | |
74 | asz = unsigned(h.size) | |
75 | usz = unsigned(h.user_size) | |
76 | pgsz = unsigned(kern.globals.page_size) | |
77 | ||
78 | if h.zone: | |
79 | zone = h.zone | |
80 | if str(zone.zone_name).startswith("fakestack"): | |
81 | alloc_type = "fakestack" | |
82 | leftrz = 16 | |
83 | else: | |
84 | alloc_type = "zone" | |
85 | leftrz = unsigned(zone.kasan_redzone) | |
86 | else: | |
87 | alloc_type = "kalloc" | |
88 | if asz - usz >= 2*pgsz: | |
89 | leftrz = pgsz | |
90 | else: | |
91 | leftrz = kasan_guard_size | |
92 | ||
93 | rightrz = asz - usz - leftrz | |
94 | ||
95 | print "Freed {} object".format(alloc_type) | |
96 | print "Valid range: 0x{:x} -- 0x{:x} ({} bytes)".format(addr + leftrz, addr + leftrz + usz - 1, usz) | |
97 | print "Total range: 0x{:x} -- 0x{:x} ({} bytes)".format(addr, addr + asz - 1, asz) | |
98 | print "Offset: {} bytes".format(orig_ptr - addr - leftrz) | |
99 | print "Redzone: {} / {} bytes".format(leftrz, rightrz) | |
100 | if h.zone: | |
101 | print "Zone: 0x{:x} <{:s}>".format(unsigned(zone), zone.zone_name) | |
102 | ||
103 | btframes = unsigned(h.frames) | |
104 | if btframes > 0: | |
105 | print "", | |
106 | print "Free site backtrace ({} frames):".format(btframes) | |
107 | for i in xrange(0, btframes): | |
108 | fr = unsigned(kern.globals.vm_kernel_slid_base) + unsigned(h.backtrace[i]) | |
109 | print " #{:}: {}".format(btframes-i-1, GetSourceInformationForAddress(fr)) | |
110 | ||
111 | print "", | |
d9a64523 | 112 | print_hexdump(addr, asz, 1) |
5ba3f43e A |
113 | |
114 | alloc_header_sz = 16 | |
115 | ||
a39ff7e2 A |
116 | def magic_for_addr(addr, xor): |
117 | magic = addr & 0xffff | |
118 | magic ^= (addr >> 16) & 0xffff | |
119 | magic ^= (addr >> 32) & 0xffff | |
120 | magic ^= (addr >> 48) & 0xffff | |
121 | magic ^= xor | |
122 | return magic | |
123 | ||
5ba3f43e A |
124 | def print_alloc_info(_addr): |
125 | addr = (_addr & ~0x7) | |
126 | ||
127 | _shp = shadow_for_address(_addr, shift) | |
128 | _shbyte = get_shadow_byte(_shp) | |
129 | _shstr = shadow_byte_to_string(_shbyte) | |
130 | ||
131 | # If we're in a left redzone, scan to the start of the real allocation, where | |
132 | # the header should live | |
133 | shbyte = _shbyte | |
134 | while shbyte == 0xfa: | |
135 | addr += 8 | |
136 | shbyte = get_shadow_byte(shadow_for_address(addr, shift)) | |
137 | ||
138 | # Search backwards for an allocation | |
139 | searchbytes = 0 | |
140 | while searchbytes < 8*4096: | |
141 | ||
142 | shp = shadow_for_address(addr, shift) | |
143 | shbyte = get_shadow_byte(shp) | |
144 | shstr = shadow_byte_to_string(shbyte) | |
145 | ||
146 | headerp = addr - alloc_header_sz | |
147 | liveh = kern.GetValueFromAddress(headerp, 'struct kasan_alloc_header *') | |
148 | freeh = kern.GetValueFromAddress(addr, 'struct freelist_entry *') | |
149 | ||
150 | # heap allocations should only ever have these shadow values | |
151 | if shbyte not in (0,1,2,3,4,5,6,7, 0xfa, 0xfb, 0xfd, 0xf5): | |
152 | print "No allocation found at 0x{:x} (found shadow {:x})".format(_addr, shbyte) | |
153 | return | |
154 | ||
a39ff7e2 | 155 | if magic_for_addr(addr, 0x3a65) == unsigned(liveh.magic): |
5ba3f43e A |
156 | usz = unsigned(liveh.user_size) |
157 | asz = unsigned(liveh.alloc_size) | |
158 | leftrz = unsigned(liveh.left_rz) | |
159 | base = headerp + alloc_header_sz - leftrz | |
160 | ||
161 | if _addr >= base and _addr < base + asz: | |
162 | footer = kern.GetValueFromAddress(addr + usz, 'struct kasan_alloc_footer *') | |
163 | rightrz = asz - usz - leftrz | |
a39ff7e2 | 164 | offset = _addr - addr |
5ba3f43e A |
165 | |
166 | print "Live heap object" | |
167 | print "Valid range: 0x{:x} -- 0x{:x} ({} bytes)".format(addr, addr + usz - 1, usz) | |
168 | print "Total range: 0x{:x} -- 0x{:x} ({} bytes)".format(base, base + asz - 1, asz) | |
a39ff7e2 | 169 | print "Offset: {} bytes (shadow: 0x{:02x} {}, remaining: {} bytes)".format(offset, _shbyte, _shstr, usz - offset) |
5ba3f43e A |
170 | print "Redzone: {} / {} bytes".format(leftrz, rightrz) |
171 | ||
172 | btframes = unsigned(liveh.frames) | |
173 | print "", | |
174 | print "Alloc site backtrace ({} frames):".format(btframes) | |
175 | for i in xrange(0, btframes): | |
176 | fr = unsigned(kern.globals.vm_kernel_slid_base) + unsigned(footer.backtrace[i]) | |
177 | print " #{:}: {}".format(btframes-i-1, GetSourceInformationForAddress(fr)) | |
178 | ||
179 | print "", | |
d9a64523 | 180 | print_hexdump(base, asz, 1) |
5ba3f43e A |
181 | return |
182 | ||
a39ff7e2 | 183 | elif magic_for_addr(addr, 0xf233) == unsigned(freeh.magic): |
5ba3f43e A |
184 | asz = unsigned(freeh.size) |
185 | if _addr >= addr and _addr < addr + asz: | |
186 | print_alloc_free_entry(addr, _addr) | |
187 | return | |
188 | ||
189 | searchbytes += 8 | |
190 | addr -= 8 | |
191 | ||
192 | print "No allocation found at 0x{:x}".format(_addr) | |
193 | ||
194 | def shadow_byte_to_string(sb): | |
195 | return shadow_strings.get(sb, '??') | |
196 | ||
197 | def print_whatis(_addr, ctx): | |
198 | addr = _addr & ~0x7 | |
199 | total_size = 0 | |
200 | base = None | |
201 | leftrz = None | |
202 | rightrz = None | |
203 | extra = "Live" | |
204 | ||
d9a64523 A |
205 | shaddr = shadow_for_address(addr, shift) |
206 | try: | |
207 | shbyte = get_shadow_byte(shaddr) | |
208 | except: | |
209 | print "Unmapped shadow 0x{:x} for address 0x{:x}".format(shaddr, addr) | |
210 | return | |
211 | ||
212 | maxsearch = 8*4096 | |
5ba3f43e A |
213 | |
214 | if shbyte in [0xfa, 0xfb, 0xfd, 0xf5]: | |
215 | print_alloc_info(_addr) | |
216 | return | |
217 | ||
218 | if shbyte not in [0,1,2,3,4,5,6,7,0xf8]: | |
219 | print "Poisoned memory, shadow {:x} [{}]".format(shbyte, shadow_byte_to_string(shbyte)) | |
220 | return | |
221 | ||
222 | if shbyte is 0xf8: | |
223 | extra = "Out-of-scope" | |
224 | ||
225 | # look for the base of the object | |
226 | while shbyte in [0,1,2,3,4,5,6,7,0xf8]: | |
227 | sz = 8 - shbyte | |
228 | if shbyte is 0xf8: | |
229 | sz = 8 | |
230 | total_size += sz | |
231 | addr -= 8 | |
232 | shbyte = get_shadow_byte(shadow_for_address(addr, shift)) | |
233 | maxsearch -= 8 | |
234 | if maxsearch <= 0: | |
235 | print "No object found" | |
236 | return | |
237 | base = addr + 8 | |
238 | leftrz = shbyte | |
239 | ||
240 | # If we did not find a left/mid redzone, we aren't in an object | |
241 | if leftrz not in [0xf1, 0xf2, 0xfa, 0xf9]: | |
242 | print "No object found" | |
243 | return | |
244 | ||
245 | # now size the object | |
246 | addr = (_addr & ~0x7) + 8 | |
247 | shbyte = get_shadow_byte(shadow_for_address(addr, shift)) | |
248 | while shbyte in [0,1,2,3,4,5,6,7,0xf8]: | |
249 | sz = 8 - shbyte | |
250 | if shbyte is 0xf8: | |
251 | sz = 8 | |
252 | total_size += sz | |
253 | addr += 8 | |
254 | shbyte = get_shadow_byte(shadow_for_address(addr, shift)) | |
255 | maxsearch -= 8 | |
256 | if maxsearch <= 0: | |
257 | print "No object found" | |
258 | return | |
259 | rightrz = shbyte | |
260 | ||
261 | # work out the type of the object from its redzone | |
262 | objtype = "Unknown" | |
263 | if leftrz == 0xf1 or leftrz == 0xf2: | |
264 | objtype = "stack" | |
265 | elif leftrz == 0xf9 and rightrz == 0xf9: | |
266 | objtype = "global" | |
267 | elif leftrz == 0xfa and rightrz == 0xfb: | |
268 | print_alloc_info(_addr) | |
269 | return | |
270 | ||
271 | print "{} {} object".format(extra, objtype) | |
272 | print "Valid range: 0x{:x} -- 0x{:x} ({} bytes)".format(base, base+total_size-1, total_size) | |
273 | print "Offset: {} bytes".format(_addr - base) | |
274 | print "", | |
d9a64523 | 275 | print_hexdump(base, total_size, 1) |
5ba3f43e A |
276 | |
277 | def print_hexdump(base, size, ctx): | |
d9a64523 A |
278 | if size < 16: |
279 | size = 16 | |
280 | base -= base % 16 | |
5ba3f43e A |
281 | start = base - 16*ctx |
282 | size += size % 16 | |
283 | size = min(size + 16*2*ctx, 256) | |
284 | ||
285 | try: | |
286 | data_array = kern.GetValueFromAddress(start, "uint8_t *") | |
287 | print_hex_data(data_array[0:size], start, "Hexdump") | |
288 | except: | |
289 | pass | |
290 | ||
291 | def kasan_subcommand(cmd, args, opts): | |
292 | addr = None | |
293 | if len(args) > 0: | |
294 | addr = long(args[0], 0) | |
295 | ||
296 | if cmd in ['a2s', 'toshadow', 'fromaddr', 'fromaddress']: | |
297 | print "0x{:016x}".format(shadow_for_address(addr, shift)) | |
298 | elif cmd in ['s2a', 'toaddr', 'toaddress', 'fromshadow']: | |
299 | print "0x{:016x}".format(address_for_shadow(addr, shift)) | |
300 | elif cmd == 'shadow': | |
301 | shadow = shadow_for_address(addr, shift) | |
302 | sb = get_shadow_byte(shadow) | |
303 | print("0x{:02x} @ 0x{:016x} [{}]\n\n".format(sb, shadow, shadow_byte_to_string(sb))) | |
304 | ctx = long(opts.get("-C", 5)) | |
305 | print_shadow_context(addr, ctx) | |
d9a64523 | 306 | elif cmd == 'key' or cmd == 'legend': |
5ba3f43e A |
307 | print_legend() |
308 | elif cmd == 'info': | |
309 | pages_used = unsigned(kern.globals.shadow_pages_used) | |
310 | pages_total = unsigned(kern.globals.shadow_pages_total) | |
311 | nkexts = unsigned(kern.globals.kexts_loaded) | |
312 | print "Offset: 0x{:016x}".format(shift) | |
313 | print "Shadow used: {} / {} ({:.1f}%)".format(pages_used, pages_total, 100.0*pages_used/pages_total) | |
314 | print "Kexts loaded: {}".format(nkexts) | |
315 | elif cmd == 'whatis': | |
316 | ctx = long(opts.get("-C", 1)) | |
317 | print_whatis(addr, ctx) | |
318 | elif cmd == 'alloc' or cmd == 'heap': | |
319 | print_alloc_info(addr) | |
d9a64523 A |
320 | else: |
321 | print "Unknown subcommand: `{}'".format(cmd) | |
5ba3f43e A |
322 | |
323 | @lldb_command('kasan', 'C:') | |
324 | def Kasan(cmd_args=None, cmd_options={}): | |
325 | """kasan <cmd> [opts..] | |
326 | ||
327 | Commands: | |
328 | ||
329 | info basic KASan information | |
330 | shadow <addr> print shadow around 'addr' | |
331 | heap <addr> show info about heap object at 'addr' | |
332 | whatis <addr> print whatever KASan knows about address | |
333 | toshadow <addr> convert address to shadow pointer | |
334 | toaddr <shdw> convert shadow pointer to address | |
335 | legend print a shadow byte table | |
336 | ||
337 | -C <num> : num lines of context to show""" | |
338 | ||
339 | if not is_kasan_build(): | |
340 | print "KASan not enabled in build" | |
341 | return | |
342 | ||
343 | if len(cmd_args) == 0: | |
344 | print Kasan.__doc__ | |
345 | return | |
346 | ||
347 | global shift | |
348 | shift = unsigned(kern.globals.__asan_shadow_memory_dynamic_address) | |
349 | ||
350 | # Since the VM is not aware of the KASan shadow mapping, accesses to it will | |
351 | # fail. Setting kdp_read_io=1 avoids this check. | |
352 | if GetConnectionProtocol() == "kdp" and unsigned(kern.globals.kdp_read_io) == 0: | |
353 | print "Setting kdp_read_io=1 to allow KASan shadow reads" | |
354 | if sizeof(kern.globals.kdp_read_io) == 4: | |
355 | WriteInt32ToMemoryAddress(1, addressof(kern.globals.kdp_read_io)) | |
356 | elif sizeof(kern.globals.kdp_read_io) == 8: | |
357 | WriteInt64ToMemoryAddress(1, addressof(kern.globals.kdp_read_io)) | |
358 | readio = unsigned(kern.globals.kdp_read_io) | |
359 | assert readio == 1 | |
360 | ||
361 | return kasan_subcommand(cmd_args[0], cmd_args[1:], cmd_options) | |
362 |