1 /* 2 * Copyright (c) 2020 Anton Lindqvist <anton@openbsd.org> 3 * 4 * Permission to use, copy, modify, and distribute this software for any 5 * purpose with or without fee is hereby granted, provided that the above 6 * copyright notice and this permission notice appear in all copies. 7 * 8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 */ 16 17 #include <sys/types.h> 18 #include <sys/param.h> /* PAGE_SIZE */ 19 #include <sys/mman.h> 20 #include <sys/uio.h> 21 #include <sys/wait.h> 22 23 #include <err.h> 24 #include <fcntl.h> 25 #include <signal.h> 26 #include <signal.h> 27 #include <stdio.h> 28 #include <stdlib.h> 29 #include <string.h> 30 #include <unistd.h> 31 32 static void growshrink(const char *); 33 static void writer(const char *, int); 34 35 static void sighandler(int); 36 static void __dead usage(void); 37 38 static int gotsig; 39 static int npages = 2; 40 41 /* 42 * Trigger a deadlock between uvn_flush() and uvn_io() caused by uvn_flush() 43 * holding the vnode lock while waiting for pages to become unbusy; the busy 44 * pages are allocated and marked as busy by uvn_io() which in turn is trying to 45 * lock the same vnode while populating the page(s) using I/O on the vnode. 46 */ 47 int 48 main(int argc, char *argv[]) 49 { 50 const char *path; 51 pid_t pid; 52 int killpip[2]; 53 int status; 54 int iterations = 100; 55 char ch; 56 57 while ((ch = getopt(argc, argv, "I")) != -1) { 58 switch (ch) { 59 case 'I': 60 iterations = -1; 61 break; 62 default: 63 usage(); 64 } 65 } 66 argc -= optind; 67 argv += optind; 68 if (argc != 1) 69 usage(); 70 path = argv[0]; 71 72 if (pipe2(killpip, O_NONBLOCK) == -1) 73 err(1, "pipe2"); 74 75 if (signal(SIGINT, sighandler) == SIG_ERR) 76 err(1, "signal"); 77 78 pid = fork(); 79 if (pid == -1) 80 err(1, "fork"); 81 if (pid == 0) { 82 close(killpip[1]); 83 writer(path, killpip[0]); 84 return 0; 85 } 86 close(killpip[0]); 87 88 fprintf(stderr, "parent = %d, child = %d\n", getpid(), pid); 89 90 while (gotsig == 0) { 91 if (iterations > 0 && --iterations == 0) 92 break; 93 94 growshrink(path); 95 } 96 97 /* Signal shutdown to writer() process. */ 98 write(killpip[1], &ch, 1); 99 close(killpip[1]); 100 101 if (waitpid(pid, &status, 0) == -1) 102 err(1, "waitpid"); 103 if (WIFSIGNALED(status)) 104 return 128 + WTERMSIG(status); 105 if (WIFEXITED(status)) 106 return WEXITSTATUS(status); 107 return 0; 108 } 109 110 static void 111 growshrink(const char *path) 112 { 113 char *p; 114 int fd; 115 116 /* 117 * Open and truncate the file causing uvn_flush() to try evict pages 118 * which are currently being populated by the writer() process. 119 */ 120 fd = open(path, O_RDWR | O_TRUNC); 121 if (fd == -1) 122 err(1, "open: %s", path); 123 124 /* Grow the file again. */ 125 if (ftruncate(fd, npages * PAGE_SIZE) == -1) 126 err(1, "ftruncate"); 127 128 p = mmap(NULL, npages * PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, 129 fd, 0); 130 if (p == MAP_FAILED) 131 err(1, "mmap"); 132 133 /* Populate the last page. */ 134 memset(&p[(npages - 1) * PAGE_SIZE], 'x', PAGE_SIZE); 135 136 if (munmap(p, npages * PAGE_SIZE) == -1) 137 err(1, "munmap"); 138 close(fd); 139 } 140 141 static void 142 writer(const char *path, int killfd) 143 { 144 char *p; 145 int fd; 146 char c; 147 148 fd = open(path, O_RDONLY); 149 if (fd == -1) 150 err(1, "open: %s", path); 151 p = mmap(NULL, npages * PAGE_SIZE, PROT_READ, MAP_SHARED, 152 fd, 0); 153 if (p == MAP_FAILED) 154 err(1, "mmap"); 155 156 for (;;) { 157 const struct iovec *iov = (const struct iovec *)p; 158 159 if (read(killfd, &c, 1) == 1) 160 break; 161 162 /* 163 * This write should never succeed since the file descriptor is 164 * invalid. However, it should cause a page fault during 165 * copyin() which in turn will invoke uvn_io() while trying to 166 * populate the page. At this point, it will try to lock the 167 * vnode, which is potentially already locked by the 168 * growshrink() process. 169 */ 170 pwritev(-1, iov, 1, 0); 171 } 172 } 173 174 static void 175 sighandler(int signo) 176 { 177 gotsig = signo; 178 } 179 180 static void __dead 181 usage(void) 182 { 183 fprintf(stderr, "usage: vnode [-I] path\n"); 184 exit(1); 185 } 186