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