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