X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/4d15aeb193b2c68f1d38666c317f8d3734f5f083..5ba3f43ea354af8ad55bea84372a2bc834d8757c:/san/kasan-test.c diff --git a/san/kasan-test.c b/san/kasan-test.c new file mode 100644 index 000000000..820af1b7f --- /dev/null +++ b/san/kasan-test.c @@ -0,0 +1,624 @@ +/* + * 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 +#include + +#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; + +struct kasan_test { + int (* func)(struct kasan_test *); + void (* cleanup)(struct kasan_test *); + const char *name; + int result; + void *data; + size_t datasz; +}; + +#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(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(BUFSZ); + 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(BUFSZ); + 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(LBUFSZ); + if (!x) { + return 1; + } + kfree(x, LBUFSZ); + x[0] = 0x10; + return 0; +} + +static int test_heap_inval_free(struct kasan_test __unused *t) +{ + int x; + kfree(&x, BUFSZ); + return 0; +} + +static int test_heap_double_free(struct kasan_test *t) +{ + TEST_START(t); + + uint8_t *x = kalloc(BUFSZ); + if (!x) { + return 1; + } + kfree(x, BUFSZ); + + TEST_FAULT(t); + kfree(x, BUFSZ); + + return 0; +} + +static int test_heap_small_free(struct kasan_test *t) +{ + TEST_START(t); + + uint8_t *x = kalloc(BUFSZ); + if (!x) { + return 1; + } + t->datasz = BUFSZ; + t->data = x; + + TEST_FAULT(t); + kfree(x, BUFSZ-2); + t->data = NULL; + t->datasz = 0; + + return 0; +} + +static int test_stack_overflow(struct kasan_test *t) +{ + TEST_START(t); + + int 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(STACK_ARRAY_SZ); + if (!a1) + return 1; + a2 = kalloc(STACK_ARRAY_SZ+1); + 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(STACK_ARRAY_SZ); + if (!a1) + return 1; + a2 = kalloc(STACK_ARRAY_SZ+1); + 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 NOINLINE test_blacklist_helper(void) +{ + return kasan_is_blacklisted(TYPE_TEST); +} + +static int NOINLINE test_blacklist(struct kasan_test *t) +{ + TEST_START(t); + int res = (int)!test_blacklist_helper(); + TEST_DONE(t, res); + return 0; +} + +static int NOINLINE test_blacklist_str(struct kasan_test *t) +{ + TEST_START(t); + char a1[8]; + + strlcpy(a1, "looooooooonnnnggg", 9); + + TEST_DONE(t, 0); /* success */ + return 0; +} + +#if 0 +static int test_strnlen(struct kasan_test *t) +{ + TEST_START(t); + const char *a1 = "abcdef"; + + /* should not fault */ + if (strnlen(a1, 6) != 6) + return 1; + if (strnlen(a1, 7) != 6) + return 1; + + TEST_FAULT(t); + if (strnlen(a1, 8) != 6) + return 1; + TEST_NOFAULT(t); + + return a1[0] != 'a'; +} +#endif + +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_blacklist, "blacklist"), + DECLARE_TEST(test_blacklist_str, "blacklist_str"), + // DECLARE_TEST(test_strnlen, "strnlen"), +}; +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; + } + + // printf("KASan: test.%02d RUNNING (%s)\n", testno, t->name); + + 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 { + /* Triggering a KASan violation will return here by longjmp, bypassing + * stack unpoisoning, so do it here explicitly. We just hope that + * fakestack free will happen later... */ + kasan_unpoison_curstack(); + + 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; +} + +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); + } +}