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(int(args[0], 0)) 579 elif cmd in ['s2a', 'toaddr', 'toaddress', 'fromshadow']: 580 if not args: 581 raise ArgumentError("Missing address argument") 582 self.from_shadow(int(args[0], 0)) 583 elif cmd == 'shadow': 584 if not args: 585 raise ArgumentError("Missing address argument") 586 self.shadow(int(args[0], 0), int(opts.get("-C", 1))) 587 elif cmd == 'whatis': 588 if not args: 589 raise ArgumentError("Missing address argument") 590 self.whatis(int(args[0], 0)) 591 elif cmd in ['alloc', 'heap']: 592 if not args: 593 raise ArgumentError("Missing address argument") 594 self.heap(int(args[0], 0)) 595 elif cmd == "quarantine": 596 addrs = set(int(arg, base=16) 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(ClassicKasan, self).__init__( 753 "kasan-classic", shadow_map, mo_provider) 754 755 def from_shadow(self, saddr): 756 super(ClassicKasan, self).from_shadow(saddr) 757 758 def to_shadow(self, addr): 759 super(ClassicKasan, self).to_shadow(addr) 760 761 def shadow(self, addr, line_count): 762 super(ClassicKasan, self).shadow(addr, line_count) 763 764 def whatis(self, addr): 765 mo = self._mo_provider.lookup(addr & ~0x7) 766 if isinstance(mo, ShadowMapEntry): 767 print("Poisoned memory: shadow address: {:#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(MTESan, self).__init__("kasan-tbi", shadow_map, mo_provider) 866 pass 867 868 def from_shadow(self, saddr): 869 super(MTESan, self).from_shadow(saddr) 870 871 def to_shadow(self, addr): 872 super(MTESan, self).to_shadow(addr) 873 874 def shadow(self, addr, line_count): 875 super(MTESan, self).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