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