]> git.saurik.com Git - apple/xnu.git/blobdiff - san/kasan-test.c
xnu-4570.1.46.tar.gz
[apple/xnu.git] / san / kasan-test.c
diff --git a/san/kasan-test.c b/san/kasan-test.c
new file mode 100644 (file)
index 0000000..820af1b
--- /dev/null
@@ -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 <stdint.h>
+#include <string.h>
+#include <vm/vm_map.h>
+#include <kern/assert.h>
+#include <kern/locks.h>
+#include <kern/kalloc.h>
+#include <kern/simple_lock.h>
+#include <kern/debug.h>
+#include <mach/mach_vm.h>
+#include <mach/vm_param.h>
+#include <libkern/libkern.h>
+#include <libkern/kernel_mach_header.h>
+#include <sys/queue.h>
+#include <kasan.h>
+#include <kasan_internal.h>
+#include <memintrinsics.h>
+
+#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);
+       }
+}