/* * Copyright (c) 2016-2020 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kasan.h" #include "kasan_internal.h" #include "memintrinsics.h" uintptr_t kasan_tbi_tag_range(uintptr_t, size_t, uint8_t); #define P2ALIGN(x, align) ((x) & -(align)) #define P2ROUNDUP(x, align) (-(-(x) & -(align))) /* Configuration options */ bool kasan_tbi_check_tag = false; bool kasan_tbi_enabled = false; /* Reserved tags */ #define KASAN_TBI_DEFAULT_TAG 0xFF #define KASAN_TBI_ZALLOC_FREE_TAG 0xF0 #define KASAN_TBI_REDZONE_POISON 0x80 #if defined(ARM_LARGE_MEMORY) #define KASAN_TBI_SHADOW_MIN (VM_MAX_KERNEL_ADDRESS+1) #define KASAN_TBI_SHADOW_MAX 0xffffffffffffffffULL #else #define KASAN_TBI_SHADOW_MIN 0xfffffffe00000000ULL #define KASAN_TBI_SHADOW_MAX 0xffffffffc0000000ULL #endif #if !CONFIG_KERNEL_TBI #error "KASAN-TBI requires KERNEL DATA TBI enabled" #endif /* CONFIG_KERNEL_TBI */ /* * Untagged kernel addresses start with 0xFF. Match that whenever we create * valid regions. */ void kasan_impl_fill_valid_range(uintptr_t page, size_t size) { (void) __nosan_memset((void *)page, 0xFF, size); } void kasan_impl_init(void) { /* * KASAN depends on CONFIG_KERNEL_TBI, therefore (DATA) TBI has been * set for us already at bootstrap. */ kasan_tbi_enabled = true; /* Enable checking early on */ kasan_tbi_check_tag = true; /* * Sanity check on features that are effectively disabled, but might have * erroneously been setup by legacy boot-args */ if (fakestack_enabled) { fakestack_enabled = 0; } } void NOINLINE kasan_init_globals(vm_offset_t __unused base, vm_size_t __unused size) { /* * KASAN-TBI global support awaits compiler fixes to generate descriptive * structures similar to KASAN-CLASSIC (see rdar://73914854) */ } void kasan_impl_kdp_disable(void) { kasan_tbi_check_tag = false; kasan_tbi_enabled = false; } /* redzones are not necessary with HWASAN */ void kasan_unpoison_cxx_array_cookie(void __unused *ptr) { return; } static char * kasan_tbi_decode_access(access_t access) { if (access & TYPE_LOAD) { return "read from"; } if (access & TYPE_WRITE) { return "write to"; } return "acccess to"; } size_t kasan_impl_decode_issue(char *logbuf, size_t bufsize, uptr p, uptr width, access_t access, violation_t __unused reason) { size_t n = 0; n += scnprintf(logbuf, bufsize, "KASAN_TBI: invalid %lu-byte %s %#lx\n", width, kasan_tbi_decode_access(access), p); return n; } void OS_NORETURN kasan_handle_brk_failure(vm_offset_t addr, uint16_t esr) { uptr width = KASAN_TBI_GET_SIZE(esr); access_t access; if (esr & KASAN_TBI_ESR_WRITE) { access = TYPE_STORE; } else { access = TYPE_LOAD; } kasan_crash_report(addr, width, access, REASON_MOD_OOB); } /* * To a large extent, KASAN TBI doesn't require any poisoning, since versions * mismatch is enough of a sentinel. Notwithstanding this, kasan_poison() is * maintained for compatibility and to detect unexpected usages. And is still * at the base of our initial global variables support for feature parity * with KASAN CLASSIC. */ void NOINLINE kasan_poison(vm_offset_t base, vm_size_t size, vm_size_t leftrz, vm_size_t rightrz, uint8_t __unused flags) { /* ensure base, leftrz and total allocation size are granule-aligned */ assert(kasan_granule_partial(base) == 0); assert(kasan_granule_partial(leftrz) == 0); assert(kasan_granule_partial(leftrz + size + rightrz) == 0); kasan_tbi_tag_range(base, leftrz, KASAN_TBI_REDZONE_POISON); kasan_tbi_tag_range(base + leftrz, size, KASAN_TBI_DEFAULT_TAG); kasan_tbi_tag_range(base + leftrz + size, rightrz, KASAN_TBI_REDZONE_POISON); } void OS_NOINLINE kasan_impl_late_init() { } uintptr_t kasan_tbi_tag_range(uintptr_t addr, size_t sz, uint8_t tag) { if (sz == 0) { return addr; } uint8_t *shadow_first = SHADOW_FOR_ADDRESS(addr); uint8_t *shadow_last = SHADOW_FOR_ADDRESS(addr + P2ROUNDUP(sz, 16)); __nosan_memset((void *)shadow_first, tag, shadow_last - shadow_first); return (uintptr_t)kasan_tbi_tag_ptr((long)addr, tag); } /* * This is a simplified, slightly inefficient and not randomized implementation * of an odd/even tagging model. Tags 0 and 15 are reserved. */ uint8_t kasan_tbi_odd_tags[] = {1, 3, 5, 7, 9, 11, 13}; uint8_t kasan_tbi_even_tags[] = {2, 4, 6, 8, 10, 12, 14}; uint8_t kasan_tbi_odd_index = 0; uint8_t kasan_tbi_even_index = 0; static uint8_t kasan_tbi_odd_tag() { uint8_t tag = kasan_tbi_odd_tags[kasan_tbi_odd_index++ % sizeof(kasan_tbi_odd_tags)]; return tag | 0xF0; } static uint8_t kasan_tbi_even_tag() { uint8_t tag = kasan_tbi_even_tags[kasan_tbi_even_index++ % sizeof(kasan_tbi_even_tags)]; return tag | 0xF0; } static vm_offset_t kasan_tbi_do_tag_zone_object(vm_offset_t addr, vm_offset_t elem_size, uint8_t tag, boolean_t zxcpu) { vm_offset_t retaddr = kasan_tbi_tag_range(addr, elem_size, tag); /* * If the allocation comes from the per-cpu zones, extend the tag to all * the adjacent, per cpu, instances. */ if (zxcpu) { zpercpu_foreach_cpu(index) { (void)kasan_tbi_tag_range(addr + ptoa(index), elem_size, tag); } } return retaddr; } vm_offset_t kasan_tbi_tag_zalloc(vm_offset_t addr, vm_offset_t elem_size, boolean_t zxcpu) { uint8_t tag; if ((addr / elem_size) % 2) { tag = kasan_tbi_odd_tag(); } else { tag = kasan_tbi_even_tag(); } return kasan_tbi_do_tag_zone_object(addr, elem_size, tag, zxcpu); } vm_offset_t kasan_tbi_tag_zalloc_default(vm_offset_t addr, vm_offset_t elem_size, boolean_t zxcpu) { return kasan_tbi_do_tag_zone_object(addr, elem_size, KASAN_TBI_DEFAULT_TAG, zxcpu); } vm_offset_t kasan_tbi_tag_zfree(vm_offset_t addr, vm_offset_t elem_size, boolean_t zxcpu) { return kasan_tbi_do_tag_zone_object(addr, elem_size, KASAN_TBI_ZALLOC_FREE_TAG, zxcpu); } void __hwasan_tag_memory(uintptr_t p, unsigned char tag, uintptr_t sz) { if (kasan_tbi_enabled) { (void)kasan_tbi_tag_range(p, sz, tag); } } static uint8_t tag_cycle = 0; unsigned char __hwasan_generate_tag() { uint8_t tag; if (kasan_tbi_enabled) { tag = (tag_cycle++ & 0xF) | 0xF0; } else { tag = 0xFF; } return tag; } /* Query the shadow table and tag the address accordingly */ vm_offset_t kasan_tbi_fix_address_tag(vm_offset_t addr) { uint8_t *shadow = SHADOW_FOR_ADDRESS(addr); return (uintptr_t)kasan_tbi_tag_ptr((long)addr, *shadow); } /* Single out accesses to the reserve free tag */ static violation_t kasan_tbi_estimate_reason(uint8_t __unused access_tag, uint8_t stored_tag) { if (stored_tag == KASAN_TBI_ZALLOC_FREE_TAG) { return REASON_MOD_AFTER_FREE; } return REASON_MOD_OOB; } void OS_NOINLINE kasan_check_range(const void *a, size_t sz, access_t access) { uintptr_t addr = (uintptr_t)a; if (!kasan_tbi_check_tag) { return; } /* * Inlining code expects to match the topmost 8 bits, while we only use * four. Unconditionally set to one the others. */ uint8_t tag = kasan_tbi_get_tag(addr) | 0xF0; uint8_t *shadow_first = SHADOW_FOR_ADDRESS(addr); uint8_t *shadow_last = SHADOW_FOR_ADDRESS(addr + P2ROUNDUP(sz, 16)); for (uint8_t *p = shadow_first; p < shadow_last; p++) { if (tag == *p) { continue; } /* Tag mismatch, prepare the reporting */ violation_t reason = kasan_tbi_estimate_reason(tag, *p); kasan_violation(addr, sz, access, reason); } }