DF-0716 / strdup_test.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | /* * DF-0716 โ kernel module test harness for smb_strdupin. * * smb_strdupin (sys/netproto/smb/smb_subr.c:113-131) is only reachable via * the SMBIOC_T2RQ ioctl, which requires a connected SMB share (SMBIOC_OPENSHARE * โ smb_smb_treeconnect โ live SMB server). No SMB server is available on the * audit guest. This module creates /dev/strdup_test (root-only 0600) that * calls smb_strdupin directly, allowing the userspace driver (strdup_race.c) * to race the user buffer and characterize the ignored-copyin-return defect. * * This is a TEST HARNESS, not an exploit โ the finding is root-only (device * 0700 + kldload) and there is no escalation (TOCTOU info leak of stale slab * contents to an SMB server, not uid0). * * Build: see Makefile (bsd.kmod.mk) * Load: kldload ./strdup_test.ko (requires smbfs.ko already loaded) */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/conf.h> #include <sys/device.h> #include <sys/uio.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/types.h> #include <sys/ioccom.h> #include <sys/fcntl.h> /* Exported by smbfs.ko (global symbols, resolved at kldload time) */ extern char *smb_strdupin(char *s, int maxlen); extern void smb_strfree(char *s); struct strdup_test_args { char *user_ptr; /* user buffer to pass to smb_strdupin */ int maxlen; /* maxlen argument */ char *result_buf; /* where to copy the result bytes */ int result_buflen; /* size of result_buf (== expected len) */ int result_is_null; /* 1 = smb_strdupin returned NULL */ }; #define STRDUP_TEST_IOCTL _IOWR('S', 1, struct strdup_test_args) static d_open_t strdup_test_open; static d_close_t strdup_test_close; static d_ioctl_t strdup_test_ioctl; static struct dev_ops strdup_test_ops = { { "strdup_test", 0, 0, NULL, 0, 0 }, .d_open = strdup_test_open, .d_close = strdup_test_close, .d_ioctl = strdup_test_ioctl, }; static int strdup_test_open(struct dev_open_args *ap) { return 0; } static int strdup_test_close(struct dev_close_args *ap) { return 0; } static int strdup_test_ioctl(struct dev_ioctl_args *ap) { struct strdup_test_args *args; char *result; if (ap->a_cmd != STRDUP_TEST_IOCTL) return ENOTTY; args = (struct strdup_test_args *)ap->a_data; /* Call smb_strdupin โ the function under test */ result = smb_strdupin(args->user_ptr, args->maxlen); if (result == NULL) { args->result_is_null = 1; return 0; } args->result_is_null = 0; /* * Copy result_buflen bytes back to userspace. The caller sets * result_buflen == maxlen, and the user string is crafted to be * exactly maxlen-1 chars + NUL, so the kmalloc allocation is exactly * maxlen bytes. Reading maxlen bytes is safe (within the allocation). */ copyout(result, args->result_buf, args->result_buflen); smb_strfree(result); return 0; } static int strdup_test_load(module_t mod, int cmd, void *arg) { static cdev_t dev = NULL; int error = 0; switch (cmd) { case MOD_LOAD: dev = make_dev(&strdup_test_ops, 0, UID_ROOT, GID_WHEEL, 0600, "strdup_test"); kprintf("strdup_test: loaded (/dev/strdup_test)\n"); break; case MOD_UNLOAD: if (dev != NULL) destroy_dev(dev); dev = NULL; kprintf("strdup_test: unloaded\n"); break; default: error = EINVAL; break; } return error; } DEV_MODULE(strdup_test, strdup_test_load, NULL); |