xref: /xnu-8792.61.2/tools/lldbmacros/kasan.py (revision 42e220869062b56f8d7d0726fd4c88954f87902c)
1from __future__ import absolute_import, division, print_function
2
3from six import add_metaclass
4from abc import ABCMeta, abstractmethod
5from builtins import range
6from collections import namedtuple
7import itertools
8
9from xnu import *
10from utils import *
11from core.configuration import *
12
13ShadowMapEntry = namedtuple('ShadowMapEntry', ['addr', 'shaddr', 'value'])
14
15
16@add_metaclass(ABCMeta)
17class AbstractShadowMap(object):
18    """ An abstract class serving as a template for KASan variant specific
19        shadow map implementations.
20    """
21
22    def __init__(self, base, scale):
23        self._base = base
24        self._scale = scale
25
26    @abstractmethod
27    def address(self, shaddr):
28        """ Returns an address for a given shadow address. """
29        pass
30
31    @abstractmethod
32    def shadow_address(self, addr):
33        """ Returns a shadow address for a given address. """
34        pass
35
36    @abstractmethod
37    def resolve(self, addr, is_shadow=False):
38        """ Returns an address, a shadow address and a respective value
39            retrieved from a shadow map.
40        """
41        pass
42
43    @property
44    def base(self):
45        """ Returns a shadow map base. """
46        return self._base
47
48    @property
49    def scale(self):
50        """ Returns a scale size. """
51        return self._scale
52
53    @property
54    def granule_size(self):
55        """ Returns a granule size. """
56        return 1 << self.scale
57
58    @property
59    def kexts_loaded(self):
60        return unsigned(kern.globals.kexts_loaded)
61
62    def page_range(self):
63        pbase = unsigned(kern.globals.shadow_pbase)
64        ptop = unsigned(kern.globals.shadow_ptop)
65        pnext = unsigned(kern.globals.shadow_pnext)
66        return (pbase, ptop, pnext)
67
68    def page_usage(self):
69        pages_used = unsigned(kern.globals.shadow_pages_used)
70        pages_total = unsigned(kern.globals.shadow_pages_total)
71        return (pages_used, pages_total)
72
73    def next_addr(self, addr):
74        """ Returns an address corresponding to a next shadow map byte. """
75        return addr + self.granule_size
76
77    def prev_addr(self, addr):
78        """ Returns an address corresponding to a previous shadow map byte. """
79        return addr - self.granule_size
80
81    def get(self, shaddr):
82        """ Returns a value from a shadow map at given shadow address. """
83        try:
84            return unsigned(kern.GetValueFromAddress(shaddr, 'uint8_t *')[0])
85        except:
86            raise ValueError("No shadow mapping for 0x{:x}".format(shaddr))
87
88    def iterator(self, addr, count, step=1):
89        """ Returns an iterator to walk through a specified area of KASan
90            shadow map.
91        """
92        step *= self.granule_size
93        return (self.resolve(addr + d)
94                for d in range(0, count
95                               if step > 0 else -count, step))
96
97    def dropwhile(self, drop_cond, addr, count, step=1):
98        """ Returns an iterator to walk through a specified area of KASan
99            shadow map. The iterator drops elements as long as the predicate is true.
100            Afterwards, returns every element.
101        """
102        return itertools.dropwhile(drop_cond, self.iterator(addr, count, step))
103
104
105class MTEShadowMap(AbstractShadowMap):
106    """ Implements a MTESan shadow map providing access to the map content. """
107    MTE_MASK = 0x0F00000000000000
108    TBI_MASK = 0xFF00000000000000
109    TBI_SHIFT = 56
110
111    @staticmethod
112    def create():
113        base = getattr(kern.globals, '__asan_shadow_memory_dynamic_address')
114        return MTEShadowMap(base, 4)
115
116    def address(self, shaddr):
117        addr = (shaddr - self._base) << self._scale
118        return self.set_mte(addr, self.get(shaddr))
119
120    def shadow_address(self, addr):
121        return self._base + (self.clr_tbi(addr) >> self._scale)
122
123    def resolve(self, addr, is_shadow=False):
124        if is_shadow:
125            shaddr = addr
126            tag = self.get(shaddr)
127            addr = self.address(shaddr)
128        else:
129            shaddr = self.shadow_address(addr)
130            # Fix the address tag in case it was not correct
131            # and preserve the rest of TBI.
132            tag = self.get(shaddr)
133            addr = self.set_mte(addr, tag)
134        return ShadowMapEntry(addr, shaddr, tag)
135
136    @staticmethod
137    def set_mte(addr, tag):
138        """ Sets a given address MTE tag. """
139        tag = (tag << MTEShadowMap.TBI_SHIFT) & MTEShadowMap.MTE_MASK
140        return (addr & ~MTEShadowMap.MTE_MASK) | tag
141
142    @staticmethod
143    def clr_tbi(addr):
144        """ Strips a given address TBI. """
145        return addr | MTEShadowMap.TBI_MASK
146
147
148class ClassicShadowMap(AbstractShadowMap):
149    """ Implements a KASan Classic shadow map providing access to the map content. """
150    @staticmethod
151    def create():
152        base = getattr(kern.globals, '__asan_shadow_memory_dynamic_address')
153        return ClassicShadowMap(base, 3)
154
155    def address(self, shadow_addr):
156        return (shadow_addr - self._base) << self._scale
157
158    def shadow_address(self, addr):
159        return self._base + (addr >> self._scale)
160
161    def resolve(self, addr, is_shadow=False):
162        if is_shadow:
163            shaddr = addr
164            addr = self.address(shaddr)
165        else:
166            shaddr = self.shadow_address(addr)
167        return ShadowMapEntry(addr, shaddr, self.get(shaddr))
168
169
170class MemObject(object):
171    """ Represents a plain memory object. """
172
173    def __init__(self, mo_type, base, size, redzones):
174        self._base = base
175        self._size = size
176        self._mo_type = mo_type
177        self._redzones = redzones
178
179    @property
180    def type(self):
181        """ Returns a memory object type string. """
182        return self._mo_type
183
184    @property
185    def zone(self):
186        """ Returns a zone this memory object is allocated in. """
187        return None
188
189    def total_alloc(self):
190        """ Returns an address and a size of the allocation, including redzones. """
191        return self.valid_alloc()
192
193    def valid_alloc(self):
194        """ Returns an address and a size of the allocation, without redzones. """
195        return (self._base, self._size)
196
197    def redzones(self):
198        """ Returns a tuple of redzone sizes. """
199        return self._redzones
200
201    def backtrace(self):
202        """ Returns the latest known backtrace recorded for a given address. """
203        return None
204
205
206class AllocHeapMemObject(object):
207    """ Represents a memory object allocated on a heap. """
208    alloc_header_sz = 16
209
210    def __init__(self, hdr, ftr):
211        self._hdr = hdr
212        self._ftr = ftr
213
214    @property
215    def type(self):
216        """ Returns a memory object type string. """
217        return "heap"
218
219    @property
220    def zone(self):
221        """ Returns a zone this memory object is allocated in. """
222        return None
223
224    def total_alloc(self):
225        """ Returns an address and a size of the allocation, including redzones. """
226        return (self._alloc_base(), unsigned(self._hdr.alloc_size))
227
228    def valid_alloc(self):
229        return (self._valid_base(), unsigned(self._hdr.user_size))
230
231    def redzones(self):
232        """ Returns a tuple of redzone sizes. """
233        left_rz = unsigned(self._hdr.left_rz)
234        right_rz = unsigned(self._hdr.alloc_size) - unsigned(
235            self._hdr.user_size) - left_rz
236        return (left_rz, right_rz)
237
238    def backtrace(self):
239        """ Returns the latest known backtrace recorded for a given address. """
240        slide = unsigned(kern.globals.vm_kernel_slid_base)
241        n = unsigned(self._hdr.frames)
242        return (slide + unsigned(self._ftr.backtrace[i]) for i in range(0, n))
243
244    def _valid_base(self):
245        return unsigned(self._hdr) + self.alloc_header_sz
246
247    def _alloc_base(self):
248        return self._valid_base() - unsigned(self._hdr.left_rz)
249
250
251class FreeHeapMemObject(object):
252    """ Represents a memory object allocated on a heap. """
253
254    def __init__(self, hdr):
255        self._hdr = hdr
256
257    @property
258    def type(self):
259        """ Returns a memory object type string. """
260        mo_type = "heap:kalloc"
261        if self.zone:
262            mo_type = "heap:zone"
263            if str(self.zone.z_name).startswith("fakestack"):
264                mo_type = "stack"
265        return mo_type
266
267    @property
268    def zone(self):
269        """ Returns a zone this memory object is allocated in. """
270        return self._hdr.zone
271
272    def total_alloc(self):
273        """ Returns an address and a size of the allocation, including redzones. """
274        return (unsigned(self._hdr), unsigned(self._hdr.size))
275
276    def valid_alloc(self):
277        """ Returns an address and a size of the allocation, without redzones. """
278        left_rz, _ = self.redzones()
279        return (unsigned(self._hdr) + left_rz, unsigned(self._hdr.user_size))
280
281    def redzones(self):
282        """ Returns a tuple of redzone sizes. """
283        rzsz = self._hdr.size - self._hdr.user_size
284        left_rz = 16
285
286        if self._hdr.zone:
287            if str(self._hdr.zone.z_name).startswith("fakestack"):
288                left_rz = unsigned(self._hdr.zone.z_kasan_redzone)
289        else:
290            pgsz = unsigned(kern.globals.page_size)
291            if rzsz >= 2 * pgsz:
292                left_rz = pgsz
293
294        return (left_rz, rzsz - left_rz)
295
296    def backtrace(self):
297        """ Returns the latest known backtrace recorded for a given address. """
298        slide = unsigned(kern.globals.vm_kernel_slid_base)
299        n = unsigned(self._hdr.frames)
300        return (slide + unsigned(self._hdr.backtrace[i]) for i in range(0, n))
301
302
303class MTEMemObject(object):
304    """ Represents an allocated or freed memory object. """
305
306    def __init__(self, addr, zone):
307        self._addr = addr
308        self._zone = zone
309        self._btlib = BTLibrary()
310
311    @property
312    def type(self):
313        """ Returns a memory object type string. """
314        return "zone"
315
316    @property
317    def zone(self):
318        """ Returns a zone this memory object is allocated in. """
319        return self._zone
320
321    def valid_alloc(self):
322        """ Returns an address and a size of the allocation, without redzones. """
323        return self.total_alloc()
324
325    def total_alloc(self):
326        """ Returns an address and a size of the allocation, including redzones. """
327        return (self._addr, unsigned(self._zone.z_elem_size))
328
329    def redzones(self):
330        """ Returns a tuple of redzone sizes. """
331        return (0, 0)
332
333    def backtrace(self):
334        """ Returns the latest known backtrace recorded for a given address. """
335        if not self._zone.z_btlog_kasan:
336            return None
337        btlog = BTLog(unsigned(self._zone.z_btlog_kasan))
338        # Addresses are normalized (TBI stripped) in BT logs.
339        stripped_addr = MTEShadowMap.clr_tbi(self._addr)
340        records = btlog.iter_records(wantElement=stripped_addr, reverse=True)
341        record = next(records, None)
342        if not record or record[3] == 0:
343            return None
344        return (f for f in self._btlib.get_stack(record[3]).frames())
345
346
347class MTEMemObjectProvider(object):
348    """ Allows to find and create memory objects on MTESan variant. """
349
350    def __init__(self, shadow_map):
351        self._sm = shadow_map
352
353    def lookup(self, addr):
354        """  Finds and creates a memory object around given address. """
355        stripped_addr = MTEShadowMap.clr_tbi(addr)
356        z_meta = ZoneMeta(stripped_addr)
357        if not z_meta or not z_meta.zone:
358            raise ValueError("Address 0x{:x} not found in zones".format(addr))
359        sme = self._sm.resolve(z_meta.getElementAddress(stripped_addr))
360        return MTEMemObject(sme.addr, z_meta.zone)
361
362
363class ClassicMemObjectProvider(object):
364    """ Allows to find and create memory objects on kasan variant. """
365    LIVE_XOR = 0x3a65
366    FREE_XOR = 0xf233
367
368    def __init__(self, shadow_map):
369        self._sm = shadow_map
370
371    def lookup(self, addr):
372        """  Finds and creates a memory object around given address. """
373        return self._create_mo(addr)
374
375    def _create_mo(self, addr):
376        area = 32 * 1024
377        sme = self._sm.resolve(addr)
378
379        if sme.value == 0xfa:
380            it = self._sm.dropwhile(lambda a: a.value == 0xfa, addr, area)
381            return self._create_heap_mo(next(it).addr)
382        elif sme.value == 0xfb:
383            it = self._sm.dropwhile(lambda a: a.value != 0xfa, addr, area, -1)
384            return self._create_heap_mo(self._sm.next_addr(next(it).addr))
385
386        inner_object_tags = {0, 1, 2, 3, 4, 5, 6, 7, 0xf8}
387
388        if sme.value not in inner_object_tags:
389            # We could do better here and try to find the object,
390            # instead of just saying it is poisoned.
391            return sme
392
393        def consume_until(it, stop_condition, arg):
394            for value in it:
395                stop, arg = stop_condition(arg, value)
396                if stop:
397                    return arg
398            raise StopIteration
399
400        def sum_and_skip(prev, new):
401            mo_base, mo_size = prev
402            if new.value not in inner_object_tags:
403                return (True, (mo_base, mo_size, new.value))
404            mo_size += 8 - new.value if new.value == 0xf8 else 8
405            return (False, (new.addr, mo_size))
406
407        # Find memory object beginning.
408        try:
409            it = self._sm.iterator(self._sm.prev_addr(addr), area, -1)
410            mo_base, mo_size, left_rz = consume_until(it, sum_and_skip,
411                                                      (addr, 0))
412        except StopIteration:
413            raise ValueError("Cannot find left redzone")
414
415        if left_rz == 0xfa:
416            return self._create_heap_mo(mo_base)
417
418        # Next candidates: fakestack and global objects
419        if left_rz not in {0xf1, 0xf2, 0xf9}:
420            raise ValueError("Unknown left redzone 0x{:x}".format(left_rz))
421
422        # Find memory object end.
423        try:
424            it = self._sm.iterator(addr, area)
425            _, mo_size, right_rz = consume_until(it, sum_and_skip,
426                                                 (addr, mo_size))
427        except StopIteration:
428            raise ValueError("Cannot find right redzone")
429
430        if right_rz == 0xf9:
431            return MemObject("global", mo_base, mo_size, None)
432        elif left_rz in {0xf1, 0xf2}:
433            return MemObject("stack", mo_base, mo_size, None)
434        else:
435            raise ValueError(
436                "Unknown redzone combination: 0x{:x}, 0x{:x}".format(
437                    left_rz, right_rz))
438
439    def _create_heap_mo(self, raw_addr):
440        addr = (raw_addr & ~0x7)
441
442        def magic_for_addr(addr, xor):
443            magic = addr & 0xffff
444            magic ^= (addr >> 16) & 0xffff
445            magic ^= (addr >> 32) & 0xffff
446            magic ^= (addr >> 48) & 0xffff
447            magic ^= xor
448            return magic
449
450        alloc_hdr = kern.GetValueFromAddress(
451            addr - AllocHeapMemObject.alloc_header_sz,
452            'struct kasan_alloc_header *')
453        free_hdr = kern.GetValueFromAddress(addr, 'struct freelist_entry *')
454
455        if magic_for_addr(addr, self.LIVE_XOR) == unsigned(alloc_hdr.magic):
456            base = addr - alloc_hdr.left_rz
457            if base <= addr < (base + alloc_hdr.alloc_size):
458                addr += alloc_hdr.user_size
459                footer = kern.GetValueFromAddress(
460                    addr, 'struct kasan_alloc_footer *')
461                return AllocHeapMemObject(alloc_hdr, footer)
462        elif magic_for_addr(addr, self.FREE_XOR) == unsigned(free_hdr.magic):
463            if addr <= raw_addr < addr + free_hdr.size:
464                return FreeHeapMemObject(free_hdr)
465
466        raise ValueError("No heap allocation found at 0x{:x}".format(raw_addr))
467
468
469@add_metaclass(ABCMeta)
470class AbstractKasan(object):
471    """ KASan abstract class serving as a template for respective KASan implementations. """
472    CTLTYPE = 0xf
473    CTLTYPE_NODE = 0x1
474    CTLTYPE_INT = 0x2
475    CTLTYPE_STRING = 0x3
476    _sysctls = None
477
478    def __init__(self, kasan_variant, shadow_map, mo_provider):
479        self._kasan_variant = kasan_variant
480        self._sm = shadow_map
481        self._mo_provider = mo_provider
482
483    @abstractmethod
484    def from_shadow(self, saddr):
485        """ Prints an address for a given shadow address. """
486        sme = self._sm.resolve(saddr, True)
487        print("0x{:016x}".format(sme.addr))
488
489    @abstractmethod
490    def to_shadow(self, addr):
491        """ Prints a shadow address for a given address. """
492        sme = self._sm.resolve(addr, False)
493        print("0x{:016x}".format(sme.shaddr))
494
495    @abstractmethod
496    def shadow(self, addr, line_count):
497        """ Prints content of a shadow map respective to a given address. """
498        sme = self._sm.resolve(addr, False)
499        print("0x{:02x} @ 0x{:016x} [{}]\n\n".format(sme.value, sme.shaddr,
500                                                     self.tag_name(sme.value)))
501        self._print_shadow_map(sme.shaddr, line_count)
502
503    @abstractmethod
504    def whatis(self, addr):
505        """ Prints KASan records for a memory object at a given address. """
506        pass
507
508    @abstractmethod
509    def heap(self, addr):
510        """ Prints KASan records for a heap memory object at a given address. """
511        pass
512
513    @abstractmethod
514    def quarantine(self, qtype, addrs, n=None, show_bt=False, O=None):
515        """ Prints KASan quarantined addresses.
516
517            qtype:
518                Quarantine type
519            addrs:
520                List of addresses to look up in quarantine
521            n:
522                Number of shown quarantined addresses.
523                Searches from a quarantine head if positive, from the end if negative.
524            show_bt:
525                Include backtraces in a listing.
526        """
527        pass
528
529    @abstractmethod
530    def legend(self):
531        """ Prints a shadow map tags legend. """
532        pass
533
534    @abstractmethod
535    def tag_name(self, tag):
536        """ Returns a textual description of a shadow map tag. """
537        pass
538
539    def info(self):
540        """ Prints overal KASan information. """
541        nkexts = self._sm.kexts_loaded
542        pbase, ptop, pnext = self._sm.page_range()
543        pages_used, pages_total = self._sm.page_usage()
544
545        print("{:<21s}: {:>s}".format("Model", self._kasan_variant))
546        print("{:<21s}: {:>d} (1:{})".format("Scale", self._sm.scale,
547                                             1 << self._sm.scale))
548        print("{:<21s}: 0x{:016x}".format("Shadow Offset", self._sm.base))
549        print("{:<21s}: 0x{:x}-0x{:x}".format("Shadow Pages", pbase, ptop))
550        print("{:<21s}: 0x{:x}".format("Shadow RO Valid Page", pbase))
551        print("{:<21s}: 0x{:x}".format("Shadow Next Page", pnext))
552        print("{:<21s}: {} of {} pages ({:.1f}%)".format("Shadow Utilization",
553                                                         pages_used, pages_total, 100.0 * pages_used / pages_total))
554
555        print("{:<21s}: {:d}".format(
556            "Stacks Instrumented", 0 if self._sysctl("light") else 1))
557        print("{:<21s}: {:d}".format(
558            "Zalloc Integration", self._sysctl("zalloc")))
559        print("{:<21s}: {:d}".format(
560            "Kalloc Integration", self._sysctl("kalloc")))
561        print("{:<21s}: {:d}".format(
562            "Dynamic Exclude List", self._sysctl("dynamicbl")))
563        print("{:<21s}: {:d}".format("Kexts Loaded", nkexts))
564        print("{:<21s}: {:d}".format("Debug", self._sysctl("debug")))
565
566    def command(self, cmd, args, opts, O):
567        """ Executes entered "kasan" macro subcommand. """
568        if cmd in ['a2s', 'toshadow', 'fromaddr', 'fromaddress']:
569            if not args:
570                raise ArgumentError("Missing address argument")
571            self.to_shadow(int(args[0], 0))
572        elif cmd in ['s2a', 'toaddr', 'toaddress', 'fromshadow']:
573            if not args:
574                raise ArgumentError("Missing address argument")
575            self.from_shadow(int(args[0], 0))
576        elif cmd == 'shadow':
577            if not args:
578                raise ArgumentError("Missing address argument")
579            self.shadow(int(args[0], 0), int(opts.get("-C", 1)))
580        elif cmd == 'whatis':
581            if not args:
582                raise ArgumentError("Missing address argument")
583            self.whatis(int(args[0], 0))
584        elif cmd in ['alloc', 'heap']:
585            if not args:
586                raise ArgumentError("Missing address argument")
587            self.heap(int(args[0], 0))
588        elif cmd == "quarantine":
589            qtype = opts.get("-T", "zalloc")
590            addrs = set(int(arg, base=16) for arg in args) if args else None
591            count = int(opts.get("-C")) if "-C" in opts else None
592            if addrs and count:
593                raise ArgumentError(
594                    "Address list and -C are mutually exclusive")
595            show_bt = "-S" in opts
596            self.quarantine(qtype, addrs, n=count, show_bt=show_bt, O=O)
597        elif cmd == 'info':
598            self.info()
599        elif cmd in ('key', 'legend'):
600            self.legend()
601        else:
602            raise ArgumentError("Unknown subcommand: `{}'".format(cmd))
603
604    @classmethod
605    def _sysctl(cls, name, default=None):
606        """Returns a value of kern.kasan.<name>, a default value if not found."""
607        if not cls._sysctls:
608            # Let's cache sysctls, as getting them is fairly expensive.
609            cls._sysctls = cls._load_sysctls()
610        return cls._sysctls.get(name, default)
611
612    @staticmethod
613    def _load_sysctls():
614        """ Loads all kern.kasan.<name> values. Strings and unsigned
615            integers are needed and supported only.
616        """
617        def get_value(a, t): return kern.GetValueFromAddress(unsigned(a), t)
618        def prop_type(p): return p.oid_kind & AbstractKasan.CTLTYPE
619
620        def prop_value(prop):
621            if prop_type(prop) == AbstractKasan.CTLTYPE_INT:
622                if not prop.oid_arg1:
623                    return prop.oid_arg2
624                return dereference(get_value(prop.oid_arg1, 'unsigned *'))
625            assert(prop_type(prop) == AbstractKasan.CTLTYPE_STRING)
626            return get_value(prop.oid_arg1, 'char *') if prop.oid_arg1 else None
627
628        return {
629            str(p[0].oid_name): prop_value(p[0])
630            for p in IterateSysctls(kern.globals.sysctl__children, "kern.kasan")
631            if prop_type(p[0]) != AbstractKasan.CTLTYPE_NODE
632        }
633
634    def _print_shadow_map(self, shadow_addr, lines_around=1, line_width=16):
635        base = self._sm.address((shadow_addr & ~0xf) -
636                                line_width * lines_around)
637        scope = 2 * self._sm.granule_size * (
638            (line_width * lines_around) + line_width)
639        print_area = self._sm.iterator(base, scope)
640        line = ""
641
642        print(" " * 19 + "  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f")
643        for i, (_, shaddr, value) in enumerate(print_area):
644            if i % line_width == 0:
645                if i > 0:
646                    space = "" if base == shadow_addr else " "
647                    print("0x{:x}:{}{}".format(shaddr - line_width, space,
648                                               line))
649                line = ""
650                base = shaddr
651            lr = ("", " ")
652            if shaddr == shadow_addr:
653                lr = ("[", "]")
654            elif (shaddr + 1) == shadow_addr:
655                lr = ("", "")
656            line += "{}{:02x}{}".format(lr[0], value, lr[1])
657
658    def _print_mo(self, mo, addr):
659        print("Object Info:")
660
661        mo_base, mo_size = mo.valid_alloc()
662        print(" Valid range: 0x{:x} -- 0x{:x} ({} bytes)".format(
663            mo_base, mo_base + mo_size - 1, mo_size))
664
665        mo_base, mo_size = mo.total_alloc()
666        print(" Total range: 0x{:x} -- 0x{:x} ({} bytes)".format(
667            mo_base, mo_base + mo_size - 1, mo_size))
668
669        if mo.redzones():
670            left_rz, right_rz = mo.redzones()
671            print(" Redzones:    {} / {} bytes".format(left_rz, right_rz))
672
673        print(" Type:        {}".format(mo.type.capitalize()))
674        if mo.zone:
675            print(" Zone:        0x{:x} ({:s})".format(unsigned(mo.zone),
676                                                       mo.zone.z_name))
677
678        print(" \n")
679        sme = self._sm.resolve(addr)
680        print("Address Info:")
681        print(" Address:     0x{:x} (Shadow: 0x{:x})".format(
682            sme.addr, sme.shaddr))
683        print(" Tag:         0x{:X} ({})".format(sme.value,
684                                                 self.tag_name(sme.value)))
685        print(" Offset:      {:d} (Remains: {:d} bytes)".format(
686            addr - mo_base, mo_base + mo_size - addr))
687
688        frames = mo.backtrace()
689        if frames:
690            print(" \n")
691            print("(De)Allocation Backtrace:")
692            for frame in frames:
693                print(" {}".format(GetSourceInformationForAddress(frame)))
694            print("", end=' ')
695
696        self._print_mo_content(mo_base, mo_size, 1)
697
698    def _print_mo_content(self, base, size, ctx):
699        size = max(size, 16)
700        base -= base % 16
701        start = base - 16 * ctx
702        size += size % 16
703        size = min(size + 16 * 2 * ctx, 256)
704
705        try:
706            data_array = kern.GetValueFromAddress(start, "uint8_t *")
707            print(" \n")
708            print_hex_data(data_array[0:size], start, "Object Memory Dump")
709        except Exception as e:
710            print("Object content not available: {}".format(e))
711
712
713class ClassicKasan(AbstractKasan):
714    """ Provides KASan Classic specific implementation of kasan commands. """
715    GUARD_SIZE = 16
716    QUARANTINE_TYPES = {
717        "zalloc": 0,
718        "kalloc": 1,
719        "fakestack": 2
720    }
721    _shadow_strings = {
722        0x00: 'VALID',
723        0x01: 'PARTIAL1',
724        0x02: 'PARTIAL2',
725        0x03: 'PARTIAL3',
726        0x04: 'PARTIAL4',
727        0x05: 'PARTIAL5',
728        0x06: 'PARTIAL6',
729        0x07: 'PARTIAL7',
730        0xac: 'ARRAY_COOKIE',
731        0xf0: 'STACK_RZ',
732        0xf1: 'STACK_LEFT_RZ',
733        0xf2: 'STACK_MID_RZ',
734        0xf3: 'STACK_RIGHT_RZ',
735        0xf5: 'STACK_FREED',
736        0xf8: 'STACK_OOSCOPE',
737        0xf9: 'GLOBAL_RZ',
738        0xe9: 'HEAP_RZ',
739        0xfa: 'HEAP_LEFT_RZ',
740        0xfb: 'HEAP_RIGHT_RZ',
741        0xfd: 'HEAP_FREED'
742    }
743
744    @staticmethod
745    def create():
746        base = getattr(kern.globals, '__asan_shadow_memory_dynamic_address')
747        shadow_map = ClassicShadowMap(base, 3)
748        mo_provider = ClassicMemObjectProvider(shadow_map)
749        return ClassicKasan(shadow_map, mo_provider)
750
751    def __init__(self, shadow_map, mo_provider):
752        super(ClassicKasan, self).__init__(
753            "kasan-classic", shadow_map, mo_provider)
754
755    def from_shadow(self, saddr):
756        super(ClassicKasan, self).from_shadow(saddr)
757
758    def to_shadow(self, addr):
759        super(ClassicKasan, self).to_shadow(addr)
760
761    def shadow(self, addr, line_count):
762        super(ClassicKasan, self).shadow(addr, line_count)
763
764    def whatis(self, addr):
765        mo = self._mo_provider.lookup(addr & ~0x7)
766        if isinstance(mo, ShadowMapEntry):
767            print("Poisoned memory: shadow address: 0x{:x}, "
768                  "tag: 0x{:X} ({:s})".format(mo.shaddr, mo.value,
769                                              self.tag_name(mo.value)))
770            return
771        self._print_mo(mo, addr)
772
773    def heap(self, addr):
774        mo = self._mo_provider.lookup(addr & ~0x7)
775        if mo.type.startswith("heap"):
776            self._print_mo(mo, addr)
777        else:
778            print("Not a heap object")
779
780    def quarantine(self, qtype, addrs=None, n=None, show_bt=False, O=None):
781        qitems = self.quarantined(qtype)
782        if n:
783            from_tail = n < 0
784            qitems = list(qitems)
785            n = min(abs(n), len(qitems))
786            qitems = qitems[-n:] if from_tail else qitems[:n]
787        elif addrs:
788            qitems = (qi for qi in qitems if unsigned(qi) in addrs)
789        self._print_quarantined(qitems, show_bt, O)
790
791    def legend(self):
792        for k in self._shadow_strings:
793            print(" {:02x}: {:s}".format(k, self._shadow_strings[k]))
794
795    def tag_name(self, tag):
796        return self._shadow_strings.get(tag, 'Unknown')
797
798    @staticmethod
799    def quarantined(qtype):
800        """Returns an iterator of memory addresses from a given quarantine.
801        Possible quarantine types are: `"zalloc"`, `"kalloc"` and `"fakestack"`. """
802        try:
803            q = kern.globals.quarantines[ClassicKasan.QUARANTINE_TYPES[qtype]]
804        except:
805            raise ArgumentError("Unknown quarantine type: {}".format(qtype))
806        return IterateTAILQ_HEAD(addressof(q.freelist), "list", 's')
807
808    @staticmethod
809    def _print_quarantined(qitems, show_bt, O):
810        """ Formats and prints quarantine entries. Includes a backtrace
811            if `show_bt` is `True`.
812        """
813        qitems_hdr = "{:>16s} {:>5s} {:>4s} {:>9s} {:>8s} {:>16s}".format(
814            "ADDRESS", "MAGIC", "CRC", "USER_SIZE", "SIZE", "ZONE")
815        qitem_hdr = (
816            "{addr:>16x} {e.magic:>05x} {e.crc:>04x} "
817            "{e.user_size:>9d} {e.size:>8d} {e.zone:>16x} ({zname:>s})"
818        )
819
820        if not show_bt:
821            with O.table(qitems_hdr):
822                for qitem in qitems:
823                    zname = qitem.zone.z_name if qitem.zone else "not in zone"
824                    print(qitem_hdr.format(addr=unsigned(qitem),
825                                           e=qitem, zname=zname))
826            return
827
828        for qitem in qitems:
829            print("  ")
830            with O.table(qitems_hdr):
831                zname = qitem.zone.z_name if qitem.zone else "not in zone"
832                print(qitem_hdr.format(addr=unsigned(qitem),
833                                       e=qitem, zname=zname))
834            print("  ")
835            for frame in FreeHeapMemObject(qitem).backtrace():
836                print("\t{}".format(GetSourceInformationForAddress(frame)))
837
838
839class MTESan(AbstractKasan):
840    """ Provides MTESan specific implementation of kasan commands. """
841    @staticmethod
842    def create():
843        shadow_map = MTEShadowMap.create()
844        mo_provider = MTEMemObjectProvider(shadow_map)
845        return MTESan(shadow_map, mo_provider)
846
847    def __init__(self, shadow_map, mo_provider):
848        super(MTESan, self).__init__("kasan-tbi", shadow_map, mo_provider)
849        pass
850
851    def from_shadow(self, saddr):
852        super(MTESan, self).from_shadow(saddr)
853
854    def to_shadow(self, addr):
855        super(MTESan, self).to_shadow(addr)
856
857    def shadow(self, addr, line_count):
858        super(MTESan, self).shadow(addr, line_count)
859
860    def whatis(self, addr):
861        sme = self._sm.resolve(addr)
862        mo = self._mo_provider.lookup(sme.addr)
863        self._print_mo(mo, sme.addr)
864        if mo.zone.z_btlog_kasan:
865            print(
866                " \nHistory of object (de)allocations is stored in btlog 0x{:x}."
867                .format(mo.zone.z_btlog_kasan))
868
869    def heap(self, addr):
870        self.whatis(addr)
871
872    def quarantine(self, qtype, addrs=None, n=None, show_bt=False, O=None):
873        print("MTESan does not maintain a quarantine.")
874
875    def legend(self):
876        tags = [0x00, 0x80] + list(range(0xF0, 0xFF + 1))
877        for tag in tags:
878            print(" {:02x}: {:s}".format(tag, self.tag_name(tag)))
879
880    @staticmethod
881    def tag_name(tag):
882        if tag == 0xFF:
883            return "Allocated (default)"
884        if 0xF1 <= tag <= 0xFE:
885            return "Allocated"
886        if tag == 0xF0:
887            return "Freed"
888        if tag == 0x80:
889            return "Poisoned"
890        if tag == 0x00:
891            return "Cleared/Unmapped"
892        return "Unknown"
893
894
895def create_kasan():
896    """ Creates a KASan instance for a KASan type detected in a kernel core.
897        None if the core is not a KASan kernel variant.
898    """
899    if not hasattr(kern.globals, 'kasan_enabled'):
900        return None
901    if hasattr(kern.globals, 'kasan_tbi_enabled'):
902        return MTESan.create()
903    return ClassicKasan.create()
904
905
906@lldb_command('kasan', 'C:T:S', fancy=True)
907def Kasan(cmd_args=None, cmd_options=None, O=None):
908    """Allows to inspect metadata KASan maintains for memory/stack objects.
909
910    Usage:
911
912        kasan <cmd> [opts..]
913
914    Subcommands:
915
916        Print general KASan runtime information
917
918            kasan info
919
920        Convert an address to a shadow map address
921
922            kasan toshadow <addr>
923
924        Convert a shadow map address to a respective memory object address
925
926            kasan toaddr <shdw>
927
928        Print a shadow map around provided address
929
930            kasan shadow [-C <num>] <addr>
931
932            -C <num>    Number of lines to print before and after the address
933
934        Show metadata KASan maintains for a given address
935
936            kasan whatis <addr>
937
938        Show metadata of a heap object at a given address
939
940            kasan heap <addr>
941
942        Show quarantined addresses
943
944            kasan quarantine [-T zalloc|kalloc|fakestack][-S][-C +-<num>][<addr1>...<addrN>]
945
946            -T zalloc|kalloc|fakestack  Quarantine type
947            -S                          Show backtraces
948            -C +-<num>                  Show first/last <num> quarantined items
949            <addr1>...<addrN>           List of addresses to look up in quarantine
950
951            Address list and -C option are mutually exclusive.
952
953        Show a shadow map tags legend
954
955            kasan legend
956
957    General Arguments:
958    """
959
960    kasan = create_kasan()
961    if not kasan:
962        print("KASan not enabled in build")
963        return
964
965    if not cmd_args:
966        print(Kasan.__doc__)
967        return
968
969    # Since the VM is not aware of the KASan shadow mapping, accesses to it will
970    # fail. Setting kdp_read_io=1 avoids this check.
971    if GetConnectionProtocol() == "kdp" and unsigned(
972            kern.globals.kdp_read_io) == 0:
973        print("Setting kdp_read_io=1 to allow KASan shadow reads")
974        if sizeof(kern.globals.kdp_read_io) == 4:
975            WriteInt32ToMemoryAddress(1, addressof(kern.globals.kdp_read_io))
976        elif sizeof(kern.globals.kdp_read_io) == 8:
977            WriteInt64ToMemoryAddress(1, addressof(kern.globals.kdp_read_io))
978        readio = unsigned(kern.globals.kdp_read_io)
979        assert readio == 1
980
981    kasan.command(cmd_args[0], cmd_args[1:], cmd_options, O)
982