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