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