| /*- |
| * Copyright 2015-2017 Rivoreo |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer |
| * in this position and unchanged. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR |
| * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
| * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
| * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
| * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * |
| */ |
| |
| #include <sys/cdefs.h> |
| #include <sys/param.h> |
| #include <sys/systm.h> |
| #include <sys/conf.h> |
| #include <sys/uio.h> |
| #include <sys/kernel.h> |
| #include <sys/malloc.h> |
| #include <sys/module.h> |
| #include <sys/filio.h> |
| #include <sys/lock.h> |
| #include <sys/mutex.h> |
| #include <sys/sysctl.h> |
| |
| #define IOCTL_MYFAULT_DEADLOCK _IO('m', 0x20) |
| #define IOCTL_MYFAULT_HANG _IO('m', 0x21) |
| #define IOCTL_MYFAULT_STACK_CORRUPT _IO('m', 0x22) |
| #define IOCTL_MYFAULT_TRASH_FUNCTION _IO('m', 0x23) |
| #define IOCTL_MYFAULT_BUFFER_OVERFLOW _IO('m', 0x24) |
| #define IOCTL_MYFAULT_MEMORY_LEAK _IOW('m', 0x25, unsigned long int) |
| #define IOCTL_MYFAULT_STACK_OVERFLOW _IO('m', 0x26) |
| #define IOCTL_MYFAULT_DOUBLE_FREE _IO('m', 0x27) |
| #define IOCTL_MYFAULT_BREAKPOINT _IO('m', 0x28) |
| #define IOCTL_MYFAULT_PANIC _IOW('m', 0x29, const char *) |
| |
| static char *_strchr(const char *p, int ch) { |
| union { |
| const char *cp; |
| char *p; |
| } u; |
| |
| u.cp = p; |
| while(1) { |
| if (*u.p == ch) |
| return(u.p); |
| if (*u.p == '\0') |
| return(NULL); |
| u.p++; |
| } |
| } |
| #define strchr _strchr |
| |
| MALLOC_DEFINE(M_MYFAULT, "faultbuffers", "Leaks, overflows and double frees are expected in this buffer"); |
| |
| /* For use with destroy_dev(9). */ |
| static struct cdev *myfault_dev; |
| |
| static d_write_t myfault_write; |
| static d_ioctl_t myfault_ioctl; |
| static d_read_t myfault_read; |
| |
| static struct cdevsw myfault_cdevsw = { |
| .d_version = D_VERSION, |
| .d_read = myfault_read, |
| .d_write = myfault_write, |
| .d_ioctl = myfault_ioctl, |
| .d_name = "myfault", |
| //.d_flags = D_MMAP_ANON, |
| }; |
| |
| static struct mtx mutex; |
| |
| static void deadlock() { |
| mtx_init(&mutex, "myfault mutex", "", MTX_DEF); |
| mtx_lock(&mutex); |
| mtx_lock(&mutex); |
| } |
| |
| static void hang() { |
| static void *chan = &chan; |
| tsleep(chan, 0, "HANG", 0); |
| } |
| |
| static void stack_corrupt() { |
| volatile char buffer[256]; |
| memset((char *)buffer + (sizeof buffer / 2), 1, sizeof buffer * 2); |
| } |
| |
| static void trash_function() { |
| *(unsigned int *)sysctl_find_oid = 0xcccccccc; |
| } |
| |
| static void buffer_overflow() { |
| char *buffer = malloc(1024, M_MYFAULT, M_WAITOK); |
| memset(buffer, 2, 1080); |
| strcpy(buffer + 1080, "Overflow"); |
| } |
| |
| static void **leaked; |
| |
| static void memory_leak(unsigned long int size) { |
| void **buffer = malloc(size, M_MYFAULT, M_WAITOK); |
| *buffer = leaked; |
| leaked = buffer; |
| } |
| |
| static void free_leaked() { |
| while(leaked) { |
| void *n = *leaked; |
| free(leaked, M_MYFAULT); |
| leaked = n; |
| } |
| } |
| |
| static void stack_overflow() { |
| stack_overflow(); |
| } |
| |
| static void double_free() { |
| void *p = malloc(1024, M_MYFAULT, M_WAITOK); |
| free(p, M_MYFAULT); |
| free(p, M_MYFAULT); |
| } |
| |
| static int myfault_write(struct cdev *dev __unused, struct uio *uio, int flags __unused) |
| { |
| char buffer[64]; |
| int len = MIN(uio->uio_resid, sizeof buffer); |
| int error = uiomove(buffer, len, uio); |
| if(error) return error; |
| //uio->uio_resid = 0; |
| buffer[len] = 0; |
| char *newline = strchr(buffer, '\n'); |
| if(newline) *newline = 0; |
| if(!*buffer) return 0; |
| if(strcmp(buffer, "deadlock") == 0) deadlock(); |
| else if(strcmp(buffer, "hang") == 0) hang(); |
| else if(strcmp(buffer, "stack corrupt") == 0) stack_corrupt(); |
| else if(strcmp(buffer, "trash function") == 0) trash_function(); |
| else if(strcmp(buffer, "buffer overflow") == 0) buffer_overflow(); |
| else if(strncmp(buffer, "leak ", 5) == 0) memory_leak(strtoul(buffer + 5, NULL, 0)); |
| else if(strcmp(buffer, "stack overflow") == 0) stack_overflow(); |
| else if(strcmp(buffer, "double free") == 0) double_free(); |
| else if(strcmp(buffer, "breakpoint") == 0) breakpoint(); |
| else if(strncmp(buffer, "panic ", 6) == 0) panic("%s", buffer + 6); |
| else printf("myfault: command '%s' not recognized\n", buffer); |
| uio->uio_resid = 0; |
| return 0; |
| } |
| |
| static int myfault_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t data, int flags __unused, struct thread *td) { |
| char *buffer; |
| int error; |
| |
| switch (cmd) { |
| case FIONBIO: |
| break; |
| case FIOASYNC: |
| if (*(int *)data != 0) return EINVAL; |
| break; |
| case IOCTL_MYFAULT_DEADLOCK: |
| deadlock(); |
| break; |
| case IOCTL_MYFAULT_HANG: |
| hang(); |
| break; |
| case IOCTL_MYFAULT_STACK_CORRUPT: |
| stack_corrupt(); |
| break; |
| case IOCTL_MYFAULT_TRASH_FUNCTION: |
| trash_function(); |
| break; |
| case IOCTL_MYFAULT_BUFFER_OVERFLOW: |
| buffer_overflow(); |
| break; |
| case IOCTL_MYFAULT_MEMORY_LEAK: |
| memory_leak(*(unsigned long int *)data); |
| break; |
| case IOCTL_MYFAULT_STACK_OVERFLOW: |
| stack_overflow(); |
| break; |
| case IOCTL_MYFAULT_DOUBLE_FREE: |
| double_free(); |
| break; |
| case IOCTL_MYFAULT_BREAKPOINT: |
| breakpoint(); |
| break; |
| case IOCTL_MYFAULT_PANIC: |
| buffer = malloc(1024, M_MYFAULT, M_WAITOK); |
| error = copyinstr(*(void **)data, buffer, 1024, NULL); |
| if(error) snprintf(buffer, 1024, "copyinstr: error %d", error); |
| panic("%s", buffer); |
| break; |
| default: |
| return ENOIOCTL; |
| } |
| return 0; |
| } |
| |
| static char *usage; |
| static int usage_len; |
| |
| static int myfault_read(struct cdev *dev __unused, struct uio *uio, int flags __unused) { |
| int error; |
| |
| if(!usage) { |
| usage = malloc(1024, M_TEMP, M_WAITOK); |
| usage_len = snprintf(usage, 1024, |
| "Welcome to the MyFault device\n\n" |
| "Usage:\n\n" |
| "write(2) to the device, following strings are supported as commands:\n" |
| "deadlock\n" |
| "hang\n" |
| "stack corrupt\n" |
| "trash function\n" |
| "buffer overflow\n" |
| "leak <size>\n" |
| "stack overflow\n" |
| "double free\n" |
| "breakpoint\n" |
| "panic <message>\n\n" |
| "ioctl(2) the device, supported commands:\n" |
| "IOCTL_MYFAULT_DEADLOCK = 0x%lx\n" |
| "IOCTL_MYFAULT_HANG = 0x%lx\n" |
| "IOCTL_MYFAULT_STACK_CORRUPT = 0x%lx\n" |
| "IOCTL_MYFAULT_TRASH_FUNCTION = 0x%lx\n" |
| "IOCTL_MYFAULT_BUFFER_OVERFLOW = 0x%lx\n" |
| "IOCTL_MYFAULT_MEMORY_LEAK = 0x%lx (with argument 'unsigned long int size')\n" |
| "IOCTL_MYFAULT_STACK_OVERFLOW = 0x%lx\n" |
| "IOCTL_MYFAULT_DOUBLE_FREE = 0x%lx\n" |
| "IOCTL_MYFAULT_BREAKPOINT = 0x%lx\n" |
| "IOCTL_MYFAULT_PANIC = 0x%lx (with argument 'const char *message')\n\n", |
| IOCTL_MYFAULT_DEADLOCK, |
| IOCTL_MYFAULT_HANG, |
| IOCTL_MYFAULT_STACK_CORRUPT, |
| IOCTL_MYFAULT_TRASH_FUNCTION, |
| IOCTL_MYFAULT_BUFFER_OVERFLOW, |
| IOCTL_MYFAULT_MEMORY_LEAK, |
| IOCTL_MYFAULT_STACK_OVERFLOW, |
| IOCTL_MYFAULT_DOUBLE_FREE, |
| IOCTL_MYFAULT_BREAKPOINT, |
| IOCTL_MYFAULT_PANIC); |
| if(usage_len < 0) panic("snprintf failed"); |
| } |
| |
| KASSERT(uio->uio_rw == UIO_READ, |
| ("Can't be in %s for write", __func__)); |
| if(uio->uio_offset >= usage_len) return 0; |
| error = uiomove(usage + uio->uio_offset, MIN(uio->uio_resid, usage_len), uio); |
| uio->uio_resid = 0; |
| |
| return (error); |
| } |
| |
| static int myfault_modevent(module_t mod __unused, int type, void *data __unused) |
| { |
| switch(type) { |
| case MOD_LOAD: |
| //myfault_dev = make_dev_credf(MAKEDEV_ETERNAL_KLD, &myfault_cdevsw, 0, |
| // NULL, UID_ROOT, GID_WHEEL, 0666, "myfault"); |
| myfault_dev = make_dev(&myfault_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "myfault"); |
| break; |
| |
| case MOD_UNLOAD: |
| free(usage, M_TEMP); |
| free_leaked(); // Free those memory when unloading should be fine |
| destroy_dev(myfault_dev); |
| break; |
| |
| case MOD_SHUTDOWN: |
| break; |
| |
| default: |
| return (EOPNOTSUPP); |
| } |
| |
| return (0); |
| } |
| |
| DEV_MODULE(myfault, myfault_modevent, NULL); |
| MODULE_VERSION(myfault, 1); |