/* * Copyright (c) 2016 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 "kasan.h" #include "kasan_internal.h" #include "memintrinsics.h" /* move to kasan-test.h */ typedef int jmp_buf[_JBLEN]; void _longjmp(jmp_buf env, int val) OS_NORETURN; int _setjmp(jmp_buf env) __attribute__((returns_twice)); #define STATIC_ARRAY_SZ 66 #define STACK_ARRAY_SZ 9 #define BUFSZ 34 #define LBUFSZ 255 enum { TEST_PASS, TEST_FAIL_NOFAULT, TEST_FAIL_BADFAULT, TEST_SETUP_FAIL = 1, TEST_INVALID, TEST_UNKNOWN }; unsigned long static_array[STATIC_ARRAY_SZ]; static jmp_buf jbuf; static volatile int in_test = 0; #define TEST_BARRIER() do { __asm__ __volatile__ ("" ::: "memory"); } while(0) #define TEST_START(t) do { t->result = 1; TEST_BARRIER(); } while (0) #define TEST_FAULT(t) do { TEST_BARRIER(); t->result = 0; TEST_BARRIER(); } while (0) #define TEST_NOFAULT(t) do { TEST_BARRIER(); t->result = 1; TEST_BARRIER(); } while (0) #define TEST_DONE(t, res) do { t->result = (res); kasan_handle_test(); } while (0) #define DECLARE_TEST(f, s) { .func = f, .name = s } #define DECLARE_TEST3(f, c, s) { .func = f, .cleanup = c, .name = s } static void heap_cleanup(struct kasan_test *t) { if (t->data) { kfree_data(t->data, t->datasz); t->data = NULL; } } static int test_global_overflow(struct kasan_test __unused *t) { int i; /* rookie error */ for (i = 0; i <= STATIC_ARRAY_SZ; i++) { static_array[i] = i; } return 0; } static int test_heap_underflow(struct kasan_test __unused *t) { uint8_t *x = kalloc_data(BUFSZ, Z_WAITOK); if (!x) { return 1; } t->datasz = BUFSZ; t->data = x; x[-1] = 0x12; return 0; } static int test_heap_overflow(struct kasan_test __unused *t) { uint8_t *x = kalloc_data(BUFSZ, Z_WAITOK); if (!x) { return 1; } t->datasz = BUFSZ; t->data = x; x[BUFSZ] = 0x11; return 0; } static int test_heap_uaf(struct kasan_test __unused *t) { uint8_t *x = kalloc_data(LBUFSZ, Z_WAITOK); if (!x) { return 1; } kfree_data(x, LBUFSZ); x[0] = 0x10; return 0; } static int test_heap_inval_free(struct kasan_test __unused *t) { int x; int *ptr = &x; kfree_data(ptr, BUFSZ); return 0; } static int test_heap_double_free(struct kasan_test *t) { TEST_START(t); uint8_t *x = kalloc_data(BUFSZ, Z_WAITOK); if (!x) { return 1; } kfree_data(x, BUFSZ); TEST_FAULT(t); kfree_data(x, BUFSZ); return 0; } static int test_heap_small_free(struct kasan_test *t) { TEST_START(t); uint8_t *x = kalloc_data(BUFSZ, Z_WAITOK); if (!x) { return 1; } t->datasz = BUFSZ; t->data = x; TEST_FAULT(t); kfree_data(x, BUFSZ - 2); t->data = NULL; t->datasz = 0; return 0; } static int test_stack_overflow(struct kasan_test *t) { TEST_START(t); uint8_t i; volatile uint8_t a[STACK_ARRAY_SZ]; for (i = 0; i < STACK_ARRAY_SZ; i++) { a[i] = i; } TEST_FAULT(t); a[i] = i; /* rookie error */ TEST_NOFAULT(t); TEST_BARRIER(); return !(a[0] == 0); } static int test_stack_underflow(struct kasan_test *t) { TEST_START(t); long idx; uint8_t a[STACK_ARRAY_SZ]; __nosan_memset(a, 0, STACK_ARRAY_SZ); /* generate a negative index without the compiler noticing */ #if __x86_64__ __asm__ __volatile__ ("movq $-1, %0" : "=r"(idx) :: "memory"); #else __asm__ __volatile__ ("mov %0, #-1" : "=r"(idx) :: "memory"); #endif TEST_FAULT(t); a[idx] = 0xbd; TEST_NOFAULT(t); TEST_BARRIER(); return a[0] == 0; } static int test_memcpy(struct kasan_test *t) { TEST_START(t); uint8_t a1[STACK_ARRAY_SZ]; uint8_t a2[STACK_ARRAY_SZ]; /* should work */ memcpy(a1, a2, STACK_ARRAY_SZ); TEST_BARRIER(); /* should fail */ TEST_FAULT(t); memcpy(a2, a1, STACK_ARRAY_SZ + 1); TEST_NOFAULT(t); return 0; } static int test_memmove(struct kasan_test *t) { TEST_START(t); uint8_t a1[STACK_ARRAY_SZ]; uint8_t a2[STACK_ARRAY_SZ]; /* should work */ memmove(a1, a2, STACK_ARRAY_SZ); TEST_BARRIER(); /* should fail */ TEST_FAULT(t); memmove(a2, a1, STACK_ARRAY_SZ + 1); TEST_NOFAULT(t); return 0; } static int test_bcopy(struct kasan_test *t) { TEST_START(t); uint8_t a1[STACK_ARRAY_SZ]; uint8_t a2[STACK_ARRAY_SZ]; /* should work */ bcopy(a1, a2, STACK_ARRAY_SZ); TEST_BARRIER(); /* should fail */ TEST_FAULT(t); bcopy(a2, a1, STACK_ARRAY_SZ + 1); TEST_NOFAULT(t); return 0; } static int test_memset(struct kasan_test *t) { TEST_START(t); uint8_t a1[STACK_ARRAY_SZ]; /* should work */ memset(a1, 'e', STACK_ARRAY_SZ); TEST_BARRIER(); /* should fail */ TEST_FAULT(t); memset(a1, 'f', STACK_ARRAY_SZ + 1); TEST_NOFAULT(t); return 0; } static int test_memcmp(struct kasan_test *t) { TEST_START(t); uint8_t *a1; uint8_t *a2; a1 = kalloc_data(STACK_ARRAY_SZ, Z_WAITOK); if (!a1) { return 1; } a2 = kalloc_data(STACK_ARRAY_SZ + 1, Z_WAITOK); if (!a2) { return 1; } /* should work */ memcmp(a1, a2, STACK_ARRAY_SZ); memcmp(a1, a2 + 1, STACK_ARRAY_SZ); TEST_BARRIER(); /* should fail */ TEST_FAULT(t); memcmp(a1, a2, STACK_ARRAY_SZ + 1); TEST_NOFAULT(t); return 0; } static int test_bcmp(struct kasan_test *t) { TEST_START(t); uint8_t *a1; uint8_t *a2; a1 = kalloc_data(STACK_ARRAY_SZ, Z_WAITOK); if (!a1) { return 1; } a2 = kalloc_data(STACK_ARRAY_SZ + 1, Z_WAITOK); if (!a2) { return 1; } /* should work */ bcmp(a1, a2, STACK_ARRAY_SZ); bcmp(a1, a2 + 1, STACK_ARRAY_SZ); TEST_BARRIER(); /* should fail */ TEST_FAULT(t); bcmp(a1, a2, STACK_ARRAY_SZ + 1); TEST_NOFAULT(t); return 0; } static int test_bzero(struct kasan_test *t) { TEST_START(t); uint8_t a1[STACK_ARRAY_SZ]; /* should work */ bzero(a1, STACK_ARRAY_SZ); TEST_BARRIER(); /* should fail */ TEST_FAULT(t); bzero(a1, STACK_ARRAY_SZ + 1); TEST_NOFAULT(t); return 0; } static int test_strlcpy(struct kasan_test *t) { TEST_START(t); char a1[8]; /* should not fault */ strlcpy(a1, "small", 8); strlcpy(a1, "looooonnnnggg", 8); TEST_FAULT(t); strlcpy(a1, "looooooooonnnnggg", 9); TEST_NOFAULT(t); return 0; } static int test_strncpy(struct kasan_test *t) { TEST_START(t); char a1[9]; /* should not fault */ strncpy(a1, "small", 9); strncpy(a1, "looooonnnnggg", 9); TEST_FAULT(t); strncpy(a1, "looooonnnnggg", 10); TEST_NOFAULT(t); return a1[0] != 'l'; } static int test_strlcat(struct kasan_test *t) { TEST_START(t); char a1[9] = {}; /* should not fault */ strlcat(a1, "abcd", 9); strlcat(a1, "efgh", 9); strlcat(a1, "ijkl", 9); a1[0] = '\0'; strlcat(a1, "looooonnnnggg", 9); a1[0] = '\0'; TEST_FAULT(t); strlcat(a1, "looooonnnnggg", 10); TEST_NOFAULT(t); return a1[0] != 'l'; } static int test_strncat(struct kasan_test *t) { TEST_START(t); char a1[9] = {}; /* should not fault */ strncat(a1, "abcd", 4); strncat(a1, "efgh", 4); TEST_FAULT(t); strncat(a1, "i", 1); TEST_NOFAULT(t); return a1[0] != 'a'; } /* we ignore the top *two* frames in backtrace - so add an extra one */ static int OS_NOINLINE test_denylist_helper(void) { return kasan_is_denylisted(TYPE_TEST); } static int OS_NOINLINE test_denylist(struct kasan_test *t) { TEST_START(t); int res = (int)!test_denylist_helper(); TEST_DONE(t, res); return 0; } static int OS_NOINLINE test_denylist_str(struct kasan_test *t) { TEST_START(t); char a1[8]; bcopy("123456", a1, 8); TEST_DONE(t, 0); /* success */ return 0; } static void OS_NOINLINE force_fakestack(char *x) { __asm__ __volatile__ ("" :: "r" (x) : "memory"); } OS_NOINLINE static int test_fakestack_helper(struct kasan_test *t, char *x) { TEST_START(t); x[0] = 0x55; /* ensure that 'x' is on the fakestack */ uintptr_t base = dtrace_get_kernel_stack(current_thread()); uintptr_t p = (uintptr_t)x; if (p >= base && p < base + kernel_stack_size) { return 1; } __asan_handle_no_return(); /* x better still be accessible */ TEST_NOFAULT(t); if (x[0] != 0x55) { TEST_DONE(t, 1); } TEST_DONE(t, 0); return 0; } static int test_fakestack(struct kasan_test *t) { char x[8]; if (!fakestack_enabled) { return 1; } force_fakestack(x); return test_fakestack_helper(t, x); } int *uaf_ptr; static int * NOINLINE stack_uaf_helper(void) { int x; uaf_ptr = &x; return uaf_ptr; } static int test_stack_uaf(struct kasan_test __unused *t) { int *x = stack_uaf_helper(); *x = 0xb4d; TEST_BARRIER(); return !(*x == 0xb4d); } static struct kasan_test xnu_tests[] = { DECLARE_TEST(NULL, NULL), DECLARE_TEST(test_global_overflow, "Global overflow"), DECLARE_TEST3(test_heap_underflow, heap_cleanup, "Heap underflow"), DECLARE_TEST3(test_heap_overflow, heap_cleanup, "Heap overflow"), DECLARE_TEST(test_heap_uaf, "Heap use-after-free"), DECLARE_TEST(test_heap_inval_free, "Heap invalid free"), DECLARE_TEST(test_heap_double_free, "Heap double free"), DECLARE_TEST3(test_heap_small_free, heap_cleanup, "Heap small free"), DECLARE_TEST(test_stack_overflow, "Stack overflow"), DECLARE_TEST(test_stack_underflow, "Stack underflow"), DECLARE_TEST(test_stack_uaf, "Stack use-after-return"), DECLARE_TEST(test_memcpy, "memcpy"), DECLARE_TEST(test_memmove, "memmmove"), DECLARE_TEST(test_bcopy, "bcopy"), DECLARE_TEST(test_memset, "memset"), DECLARE_TEST(test_memcmp, "memcmp"), DECLARE_TEST(test_bcmp, "bcmp"), DECLARE_TEST(test_bzero, "bzero"), DECLARE_TEST(test_strlcpy, "strlcpy"), DECLARE_TEST(test_strlcat, "strlcat"), DECLARE_TEST(test_strncpy, "strncpy"), DECLARE_TEST(test_strncat, "strncat"), DECLARE_TEST(test_denylist, "denylist"), DECLARE_TEST(test_denylist_str, "denylist_str"), DECLARE_TEST(test_fakestack, "fakestack"), }; static int num_xnutests = sizeof(xnu_tests) / sizeof(xnu_tests[0]); static int kasan_run_test(struct kasan_test *test_list, int testno, int fail) { int status = TEST_UNKNOWN; struct kasan_test *t = &test_list[testno]; if (testno < 0 || testno >= num_xnutests || !t->func) { printf("KASan: test.%02d INVALID\n", testno); return TEST_INVALID; } if (!fail) { in_test = 1; } if (_setjmp(jbuf) == 0) { t->result = 0; int ret = t->func(t); if (ret) { printf("KASan: test.%02d SETUP FAIL (%s)\n", testno, t->name); status = ret; } else { /* did not fault when it should have */ printf("KASan: test.%02d FAIL (%s)\n", testno, t->name); status = TEST_FAIL_NOFAULT; } } else { if (t->result) { /* faulted, but at the wrong place */ printf("KASan: test.%02d FAIL %d (%s)\n", testno, t->result, t->name); status = TEST_FAIL_BADFAULT; } else { printf("KASan: test.%02d PASS (%s)\n", testno, t->name); status = TEST_PASS; } } in_test = 0; if (t->cleanup) { t->cleanup(t); } return status; } static void kasan_test(int testno, int fail) { int i = 1; int pass = 0, total = 0; int ret; if (testno == -1) { /* shorthand for all tests */ testno = (1U << (num_xnutests - 1)) - 1; } while (testno) { if (testno & 0x1) { ret = kasan_run_test(xnu_tests, i, fail); if (ret == TEST_PASS) { pass++; } if (ret != TEST_INVALID) { total++; } } i++; testno >>= 1; } printf("KASan: TEST SUMMARY %d/%d passed\n", pass, total); } void kasan_handle_test(void) { if (in_test) { _longjmp(jbuf, 1); /* NOTREACHED */ } } void __kasan_runtests(struct kasan_test *kext_tests, int numtests) { int i; for (i = 0; i < numtests; i++) { kasan_run_test(kext_tests, i, 0); } } static int sysctl_kasan_test(__unused struct sysctl_oid *oidp, __unused void *arg1, int arg2, struct sysctl_req *req) { int ch, err, mask = 0; err = sysctl_io_number(req, 0, sizeof(int), &mask, &ch); if (!err && mask) { kasan_test(mask, arg2); } return err; } SYSCTL_PROC(_kern_kasan, OID_AUTO, test, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED, 0, 0, sysctl_kasan_test, "I", ""); SYSCTL_PROC(_kern_kasan, OID_AUTO, fail, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED, 0, 1, sysctl_kasan_test, "I", "");