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