]> git.saurik.com Git - apple/xnu.git/blame_incremental - tools/lldbmacros/kasan.py
xnu-7195.101.1.tar.gz
[apple/xnu.git] / tools / lldbmacros / kasan.py
... / ...
CommitLineData
1from xnu import *
2from utils import *
3from core.configuration import *
4
5shift = None
6
7shadow_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
30def is_kasan_build():
31 try:
32 enable = kern.globals.kasan_enabled
33 return True
34 except ValueError, e:
35 return False
36
37def shadow_for_address(addr, shift):
38 return ((addr >> 3) + shift)
39
40def address_for_shadow(addr, shift):
41 return ((addr - shift) << 3)
42
43def get_shadow_byte(shadow_addr):
44 return unsigned(kern.GetValueFromAddress(shadow_addr, 'uint8_t *')[0])
45
46def print_legend():
47 for (k,v) in shadow_strings.iteritems():
48 print " {:02x}: {}".format(k,v)
49
50def 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
71kasan_guard_size = 16
72def 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.z_name).startswith("fakestack"):
81 alloc_type = "fakestack"
82 leftrz = 16
83 else:
84 alloc_type = "zone"
85 leftrz = unsigned(zone.z_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.z_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 "",
112 print_hexdump(addr, asz, 1)
113
114alloc_header_sz = 16
115
116def 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
124def 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
155 if magic_for_addr(addr, 0x3a65) == unsigned(liveh.magic):
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
164 offset = _addr - addr
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)
169 print "Offset: {} bytes (shadow: 0x{:02x} {}, remaining: {} bytes)".format(offset, _shbyte, _shstr, usz - offset)
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 "",
180 print_hexdump(base, asz, 1)
181 return
182
183 elif magic_for_addr(addr, 0xf233) == unsigned(freeh.magic):
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
194def shadow_byte_to_string(sb):
195 return shadow_strings.get(sb, '??')
196
197def 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
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
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 "",
275 print_hexdump(base, total_size, 1)
276
277def print_hexdump(base, size, ctx):
278 if size < 16:
279 size = 16
280 base -= base % 16
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
291def 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)
306 elif cmd == 'key' or cmd == 'legend':
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)
320 else:
321 print "Unknown subcommand: `{}'".format(cmd)
322
323@lldb_command('kasan', 'C:')
324def 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