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