/*
* Copyright (c) 2021 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 <string.h>
#include <kern/thread.h>
#include <mach/mach_vm.h>
#include <mach/mach_types.h>
#include <vm/vm_map.h>
#include <libkern/libkern.h>
#include <kern/backtrace.h>
#include "kasan_internal.h"
bool report_suppressed_checks = false;
/*
* KASAN violation reporting. Decode the access violation and pretty print
* the violation reason in the panic message.
*/
#define CRASH_CONTEXT_BEFORE 5
#define CRASH_CONTEXT_AFTER 5
#define CONTEXT_BLOCK_SIZE 16
#define CONTEXT_BLOCK_MASK (CONTEXT_BLOCK_SIZE - 1)
/* Pretty print the shadow table describing memory around the faulting access */
static size_t
kasan_dump_shadow(uptr p, char *buf, size_t len)
{
int i, j;
size_t n = 0;
int before = CRASH_CONTEXT_BEFORE;
int after = CRASH_CONTEXT_AFTER;
uptr shadow = (uptr)SHADOW_FOR_ADDRESS(p);
uptr shadow_p = shadow;
uptr shadow_page = vm_map_round_page(shadow_p, PAGE_MASK);
/* rewind to start of context block */
shadow &= ~((uptr)CONTEXT_BLOCK_MASK);
shadow -= CONTEXT_BLOCK_SIZE * before;
n += scnprintf(buf + n, len - n,
" Shadow 0 1 2 3 4 5 6 7 8 9 a b c d e f\n");
for (i = 0; i < 1 + before + after; i++, shadow += CONTEXT_BLOCK_SIZE) {
if ((vm_map_round_page(shadow, PAGE_MASK) != shadow_page) && !kasan_is_shadow_mapped(shadow)) {
/* avoid unmapped shadow when crossing page boundaries */
continue;
}
n += scnprintf(buf + n, len - n, " %16lx:", shadow);
char *left = " ";
char *right;
for (j = 0; j < CONTEXT_BLOCK_SIZE; j++) {
uint8_t *x = (uint8_t *)(shadow + j);
right = " ";
if ((uptr)x == shadow_p) {
left = "[";
right = "]";
} else if ((uptr)(x + 1) == shadow_p) {
right = "";
}
n += scnprintf(buf + n, len - n, "%s%02x%s", left, (unsigned)*x, right);
left = "";
}
n += scnprintf(buf + n, len - n, "\n");
}
n += scnprintf(buf + n, len - n, "\n");
return n;
}
#define KASAN_REPORT_BUFSIZE 4096
static void
kasan_report_internal(uptr p, uptr width, access_t access, violation_t reason, bool dopanic)
{
const size_t len = KASAN_REPORT_BUFSIZE;
static char buf[KASAN_REPORT_BUFSIZE];
size_t n = 0;
buf[0] = '\0';
n += kasan_impl_decode_issue(buf, len, p, width, access, reason);
n += kasan_dump_shadow(p, buf + n, len - n);
dopanic ? panic("%s", buf) : printf("%s", buf);
}
static void
kasan_panic_report_internal(uptr p, uptr width, access_t access, violation_t reason)
{
kasan_report_internal(p, width, access, reason, true);
}
static void
kasan_log_report_internal(uptr p, uptr width, access_t access, violation_t reason)
{
kasan_report_internal(p, width, access, reason, false);
}
/* Pretty print a crash report. */
void NOINLINE OS_NORETURN
kasan_crash_report(uptr p, uptr width, access_t access, violation_t reason)
{
kasan_handle_test();
kasan_panic_report_internal(p, width, access, reason);
__builtin_unreachable(); /* we cant handle this returning anyway */
}
/* Like kasan_crash_report(), but just log a failure. */
static void
kasan_log_report(uptr p, uptr width, access_t access, violation_t reason)
{
const size_t len = 256;
char buf[len];
size_t l = 0;
uintptr_t frames[14];
uint32_t nframes = ARRAY_COUNT(frames);
uintptr_t *bt = frames;
kasan_log_report_internal(p, width, access, reason);
struct backtrace_control ctl = {
/* ignore current frame */
.btc_frame_addr = (uintptr_t)__builtin_frame_address(0),
};
nframes = backtrace(bt, nframes, &ctl, NULL);
buf[0] = '\0';
l += scnprintf(buf + l, len - l, "Backtrace: ");
for (uint32_t i = 0; i < nframes; i++) {
l += scnprintf(buf + l, len - l, "%lx,", VM_KERNEL_UNSLIDE(bt[i]));
}
l += scnprintf(buf + l, len - l, "\n");
printf("%s", buf);
}
/*
* Report a violation that may be disabled and/or blacklisted. This can only be
* called for dynamic checks (i.e. where the fault is recoverable). Use
* kasan_crash_report() for static (unrecoverable) violations.
*
* access: what we were trying to do when the violation occured
* reason: what failed about the access
*/
void
kasan_violation(uintptr_t addr, size_t size, access_t access, violation_t reason)
{
assert(__builtin_popcount(access) == 1);
if (!kasan_check_enabled(access)) {
/*
* A violation happened but the annexed check is disabled. Simply
* report the issue.
*/
if (report_suppressed_checks) {
kasan_log_report(addr, size, access, reason);
}
return;
}
/* Panic as usual */
kasan_crash_report(addr, size, access, reason);
}