master
c 208 lines 5.66 KB
Raw
1 /*
2 * Test that VMA updates do not race.
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 *
6 * Map a contiguous chunk of RWX memory. Split it into 8 equally sized
7 * regions, each of which is guaranteed to have a certain combination of
8 * protection bits set.
9 *
10 * Reader, writer and executor threads perform the respective operations on
11 * pages, which are guaranteed to have the respective protection bit set.
12 * Two mutator threads change the non-fixed protection bits randomly.
13 */
14 #define _GNU_SOURCE
15 #include <assert.h>
16 #include <fcntl.h>
17 #include <pthread.h>
18 #include <stdbool.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <stdio.h>
22 #include <sys/mman.h>
23 #include <unistd.h>
24
25 #include "nop_func.h"
26
27 #define PAGE_IDX_BITS 10
28 #define PAGE_COUNT (1 << PAGE_IDX_BITS)
29 #define PAGE_IDX_MASK (PAGE_COUNT - 1)
30 #define REGION_IDX_BITS 3
31 #define PAGE_IDX_R_MASK (1 << 7)
32 #define PAGE_IDX_W_MASK (1 << 8)
33 #define PAGE_IDX_X_MASK (1 << 9)
34 #define REGION_MASK (PAGE_IDX_R_MASK | PAGE_IDX_W_MASK | PAGE_IDX_X_MASK)
35 #define PAGES_PER_REGION (1 << (PAGE_IDX_BITS - REGION_IDX_BITS))
36
37 struct context {
38 int pagesize;
39 char *ptr;
40 int dev_null_fd;
41 volatile int mutator_count;
42 };
43
44 static void *thread_read(void *arg)
45 {
46 struct context *ctx = arg;
47 ssize_t sret;
48 size_t i, j;
49 int ret;
50
51 for (i = 0; ctx->mutator_count; i++) {
52 char *p;
53
54 j = (i & PAGE_IDX_MASK) | PAGE_IDX_R_MASK;
55 p = &ctx->ptr[j * ctx->pagesize];
56
57 /* Read directly. */
58 ret = memcmp(p, nop_func, sizeof(nop_func));
59 if (ret != 0) {
60 fprintf(stderr, "fail direct read %p\n", p);
61 abort();
62 }
63
64 /* Read indirectly. */
65 sret = write(ctx->dev_null_fd, p, 1);
66 if (sret != 1) {
67 if (sret < 0) {
68 fprintf(stderr, "fail indirect read %p (%m)\n", p);
69 } else {
70 fprintf(stderr, "fail indirect read %p (%zd)\n", p, sret);
71 }
72 abort();
73 }
74 }
75
76 return NULL;
77 }
78
79 static void *thread_write(void *arg)
80 {
81 struct context *ctx = arg;
82 struct timespec *ts;
83 size_t i, j;
84 int ret;
85
86 for (i = 0; ctx->mutator_count; i++) {
87 j = (i & PAGE_IDX_MASK) | PAGE_IDX_W_MASK;
88
89 /* Write directly. */
90 memcpy(&ctx->ptr[j * ctx->pagesize], nop_func, sizeof(nop_func));
91
92 /* Write using a syscall. */
93 ts = (struct timespec *)(&ctx->ptr[(j + 1) * ctx->pagesize] -
94 sizeof(struct timespec));
95 ret = clock_gettime(CLOCK_REALTIME, ts);
96 if (ret != 0) {
97 fprintf(stderr, "fail indirect write %p (%m)\n", ts);
98 abort();
99 }
100 }
101
102 return NULL;
103 }
104
105 static void *thread_execute(void *arg)
106 {
107 struct context *ctx = arg;
108 size_t i, j;
109
110 for (i = 0; ctx->mutator_count; i++) {
111 j = (i & PAGE_IDX_MASK) | PAGE_IDX_X_MASK;
112 ((void(*)(void))&ctx->ptr[j * ctx->pagesize])();
113 }
114
115 return NULL;
116 }
117
118 static void *thread_mutate(void *arg)
119 {
120 size_t i, start_idx, end_idx, page_idx, tmp;
121 struct context *ctx = arg;
122 unsigned int seed;
123 int prot, ret;
124
125 seed = (unsigned int)time(NULL) + (unsigned int)gettid();
126 for (i = 0; i < 10000; i++) {
127 start_idx = rand_r(&seed) & PAGE_IDX_MASK;
128 end_idx = rand_r(&seed) & PAGE_IDX_MASK;
129 if (start_idx > end_idx) {
130 tmp = start_idx;
131 start_idx = end_idx;
132 end_idx = tmp;
133 }
134 prot = rand_r(&seed) & (PROT_READ | PROT_WRITE | PROT_EXEC);
135 for (page_idx = start_idx & REGION_MASK; page_idx <= end_idx;
136 page_idx += PAGES_PER_REGION) {
137 if (page_idx & PAGE_IDX_R_MASK) {
138 prot |= PROT_READ;
139 }
140 if (page_idx & PAGE_IDX_W_MASK) {
141 /* FIXME: qemu syscalls check for both read+write. */
142 prot |= PROT_WRITE | PROT_READ;
143 }
144 if (page_idx & PAGE_IDX_X_MASK) {
145 prot |= PROT_EXEC;
146 }
147 }
148 ret = mprotect(&ctx->ptr[start_idx * ctx->pagesize],
149 (end_idx - start_idx + 1) * ctx->pagesize, prot);
150 assert(ret == 0);
151 }
152
153 __atomic_fetch_sub(&ctx->mutator_count, 1, __ATOMIC_SEQ_CST);
154
155 return NULL;
156 }
157
158 int main(void)
159 {
160 pthread_t threads[5];
161 struct context ctx;
162 size_t i;
163 int ret;
164
165 /* Without a template, nothing to test. */
166 if (sizeof(nop_func) == 0) {
167 return EXIT_SUCCESS;
168 }
169
170 /* Initialize memory chunk. */
171 ctx.pagesize = getpagesize();
172 ctx.ptr = mmap(NULL, PAGE_COUNT * ctx.pagesize,
173 PROT_READ | PROT_WRITE | PROT_EXEC,
174 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
175 assert(ctx.ptr != MAP_FAILED);
176 for (i = 0; i < PAGE_COUNT; i++) {
177 memcpy(&ctx.ptr[i * ctx.pagesize], nop_func, sizeof(nop_func));
178 }
179 ctx.dev_null_fd = open("/dev/null", O_WRONLY);
180 assert(ctx.dev_null_fd >= 0);
181 ctx.mutator_count = 2;
182
183 /* Start threads. */
184 ret = pthread_create(&threads[0], NULL, thread_read, &ctx);
185 assert(ret == 0);
186 ret = pthread_create(&threads[1], NULL, thread_write, &ctx);
187 assert(ret == 0);
188 ret = pthread_create(&threads[2], NULL, thread_execute, &ctx);
189 assert(ret == 0);
190 for (i = 3; i <= 4; i++) {
191 ret = pthread_create(&threads[i], NULL, thread_mutate, &ctx);
192 assert(ret == 0);
193 }
194
195 /* Wait for threads to stop. */
196 for (i = 0; i < sizeof(threads) / sizeof(threads[0]); i++) {
197 ret = pthread_join(threads[i], NULL);
198 assert(ret == 0);
199 }
200
201 /* Destroy memory chunk. */
202 ret = close(ctx.dev_null_fd);
203 assert(ret == 0);
204 ret = munmap(ctx.ptr, PAGE_COUNT * ctx.pagesize);
205 assert(ret == 0);
206
207 return EXIT_SUCCESS;
208 }