xref: /xnu-8019.80.24/tools/lldbmacros/kasan.py (revision a325d9c4a84054e40bbe985afedcb50ab80993ea)
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
363