The Pedigree Project 0.1
shared-memory.c
1#define _GNU_SOURCE
2#include <errno.h>
3#include <signal.h>
4#include <stdio.h>
5#include <stdlib.h>
6#include <string.h>
7#include <unistd.h>
8
9#include <sys/ipc.h>
10#include <sys/mman.h>
11#include <sys/shm.h>
12#include <sys/wait.h>
13
14#define CHECK(condition) \
15 do { \
16 if (!(condition)) { \
17 fprintf(stderr, "shared-memory:%d: %s (errno=%d)\n", __LINE__, #condition, errno); \
18 failed = 1; \
19 goto out; \
20 } \
21 } while (0)
22
23static int reap(pid_t child) {
24 int status = 0;
25 for (int n = 0; n < 500; ++n) {
26 pid_t result = waitpid(child, &status, WNOHANG);
27 if (result == child)
28 return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
29 if (result < 0 && errno != EINTR)
30 return -1;
31 usleep(10000);
32 }
33 kill(child, SIGKILL);
34 waitpid(child, &status, 0);
35 return -1;
36}
37
38int ipc_test_shared_memory_exec(int argc, char** argv) {
39 if (argc != 4)
40 return 1;
41 int id = atoi(argv[2]);
42 unsigned long expected = strtoul(argv[3], NULL, 10);
43 struct shmid_ds status;
44 alarm(5);
45 if (shmctl(id, IPC_STAT, &status) || status.shm_nattch != expected)
46 return 2;
47 char* view = shmat(id, NULL, SHM_RDONLY);
48 if (view == (void*)-1 || strcmp(view, "child-shared"))
49 return 3;
50 if (shmdt(view) || shmctl(id, IPC_STAT, &status) || status.shm_nattch != expected)
51 return 4;
52 return 0;
53}
54
55int ipc_test_shared_memory(void) {
56 int failed = 0, id = -1, separate = -1, split_id = -1;
57 char *view = (void*)-1, *readonly = (void*)-1, *remapped = (void*)-1;
58 char *split = (void*)-1, *replacement = MAP_FAILED, *middle = MAP_FAILED;
59 void *reserved = MAP_FAILED, *bad = MAP_FAILED;
60 const size_t page = (size_t)sysconf(_SC_PAGESIZE);
61 const size_t length = page * 3;
62 key_t key = (key_t)(0x62000000U | (unsigned)getpid());
63 struct shmid_ds status, changed;
64 struct shminfo limits;
65 struct shm_info usage;
66 alarm(30);
67
68 CHECK(shmget(IPC_PRIVATE, 0, 0600) == -1 && errno == EINVAL);
69 id = shmget(key, length - 7, IPC_CREAT | IPC_EXCL | 0600);
70 CHECK(id >= 0);
71 CHECK(shmget(key, 0, 0600) == id);
72 CHECK(shmget(key, length, 0600) == -1 && errno == EINVAL);
73 CHECK(shmget(key, 1, IPC_CREAT | IPC_EXCL | 0600) == -1 && errno == EEXIST);
74 CHECK(shmctl(id, IPC_STAT, &status) == 0 && status.shm_segsz == length - 7 &&
75 status.shm_nattch == 0 && status.shm_cpid == getpid());
76 view = shmat(id, NULL, 0);
77 readonly = shmat(id, NULL, SHM_RDONLY);
78 CHECK(view != (void*)-1 && readonly != (void*)-1 && view != readonly);
79 CHECK(view[0] == 0 && view[length - 1] == 0);
80 strcpy(view, "parent-shared");
81 view[page * 2] = 42;
82 CHECK(!strcmp(readonly, "parent-shared") && readonly[page * 2] == 42);
83 CHECK(shmctl(id, IPC_STAT, &status) == 0 && status.shm_nattch == 2 && status.shm_atime != 0);
84 CHECK(mprotect(readonly, length, PROT_READ | PROT_WRITE) == -1 && errno == EACCES);
85 CHECK(shmdt(view + page) == -1 && errno == EINVAL);
86
87 pid_t child = fork();
88 CHECK(child >= 0);
89 if (!child) {
90 alarm(5);
91 if (strcmp(readonly, "parent-shared"))
92 _exit(1);
93 strcpy(view, "child-shared");
94 char* extra = shmat(id, NULL, 0);
95 if (extra == (void*)-1)
96 _exit(2);
97 if (shmctl(id, IPC_STAT, &status) || status.shm_nattch != 5)
98 _exit(3);
99 extra[page] = 73;
100 if (shmdt(extra))
101 _exit(4);
102 _exit(0);
103 }
104 CHECK(reap(child) == 0);
105 CHECK(!strcmp(view, "child-shared") && readonly[page] == 73);
106 CHECK(shmctl(id, IPC_STAT, &status) == 0 && status.shm_nattch == 2 && status.shm_dtime != 0);
107
108 child = fork();
109 CHECK(child >= 0);
110 if (!child) {
111 char identifier[32];
112 snprintf(identifier, sizeof(identifier), "%d", id);
113 execl("/applications/ipc-contract-test", "ipc-contract-test", "shm-exec", identifier, "2",
114 NULL);
115 _exit(127);
116 }
117 CHECK(reap(child) == 0);
118 CHECK(shmctl(id, IPC_STAT, &status) == 0 && status.shm_nattch == 2);
119
120 reserved = mmap(NULL, length, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
121 CHECK(reserved != MAP_FAILED);
122 CHECK(shmat(id, reserved, 0) == (void*)-1 && errno == EINVAL);
123 CHECK(shmat(id, (char*)reserved + 3, 0) == (void*)-1 && errno == EINVAL);
124 CHECK(shmat(id, NULL, SHM_REMAP) == (void*)-1 && errno == EINVAL);
125 remapped = shmat(id, (char*)reserved + 3, SHM_RND | SHM_REMAP);
126 CHECK(remapped == reserved && !strcmp(remapped, "child-shared"));
127 reserved = MAP_FAILED;
128 CHECK(shmctl(id, IPC_STAT, &status) == 0 && status.shm_nattch == 3);
129 CHECK(shmdt(remapped) == 0);
130 remapped = (void*)-1;
131
132 bad = mmap(NULL, page, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
133 CHECK(bad != MAP_FAILED);
134 CHECK(shmctl(id, IPC_STAT, bad) == -1 && errno == EFAULT);
135 CHECK(shmctl(id, IPC_SET, bad) == -1 && errno == EFAULT);
136 CHECK(shmctl(id, IPC_STAT, &changed) == 0 && (changed.shm_perm.mode & 0777) == 0600);
137 changed.shm_perm.mode = 0640;
138 CHECK(shmctl(id, IPC_SET, &changed) == 0);
139 CHECK(shmctl(id, IPC_STAT, &status) == 0 && (status.shm_perm.mode & 0777) == 0640);
140 int highest = shmctl(0, IPC_INFO, (struct shmid_ds*)&limits);
141 CHECK(highest >= 0 && limits.shmmax >= length && limits.shmmni > 0);
142 int found_index = -1;
143 for (int index = 0; index <= highest; ++index) {
144 if (shmctl(index, SHM_STAT, &status) == id)
145 found_index = index;
146 }
147 CHECK(found_index >= 0);
148 CHECK(shmctl(found_index, SHM_STAT_ANY, &status) == id);
149 CHECK(shmctl(0, SHM_INFO, (struct shmid_ds*)&usage) >= 0 && usage.used_ids >= 1 &&
150 usage.shm_tot >= 3 && usage.shm_rss >= 3);
151 CHECK(shmctl(id, SHM_LOCK, NULL) == 0);
152 CHECK(shmctl(id, IPC_STAT, &status) == 0 && (status.shm_perm.mode & SHM_LOCKED));
153 CHECK(shmctl(id, SHM_UNLOCK, NULL) == 0);
154 CHECK(shmctl(id, IPC_STAT, &status) == 0 && !(status.shm_perm.mode & SHM_LOCKED));
155
156 if (geteuid() == 0) {
157 child = fork();
158 CHECK(child >= 0);
159 if (!child) {
160 alarm(5);
161 if (setgid(65534) || setuid(65534))
162 _exit(1);
163 if (shmat(id, NULL, SHM_RDONLY) != (void*)-1 || errno != EACCES)
164 _exit(2);
165 if (shmget(key, 0, 0400) != -1 || errno != EACCES)
166 _exit(3);
167 if (shmctl(id, IPC_STAT, &status) != -1 || errno != EACCES)
168 _exit(4);
169 if (shmctl(id, IPC_RMID, NULL) != -1 || errno != EPERM)
170 _exit(5);
171 _exit(shmctl(found_index, SHM_STAT_ANY, &status) == id ? 0 : 6);
172 }
173 CHECK(reap(child) == 0);
174 }
175
176 CHECK(shmctl(id, IPC_RMID, NULL) == 0);
177 CHECK(shmctl(id, IPC_STAT, &status) == 0 && (status.shm_perm.mode & SHM_DEST));
178 CHECK(shmget(key, 0, 0600) == -1 && errno == ENOENT);
179 separate = shmget(key, page, IPC_CREAT | IPC_EXCL | 0600);
180 CHECK(separate >= 0 && separate != id);
181 remapped = shmat(id, NULL, 0);
182 CHECK(remapped != (void*)-1 && !strcmp(remapped, "child-shared"));
183 CHECK(shmdt(remapped) == 0);
184 remapped = (void*)-1;
185 CHECK(shmdt(readonly) == 0);
186 readonly = (void*)-1;
187 CHECK(shmctl(id, IPC_STAT, &status) == 0 && status.shm_nattch == 1);
188 // Replacing the last older attachment must not retire a segment while the
189 // same shmat call is installing its successor.
190 CHECK(shmat(id, view, SHM_REMAP) == view && !strcmp(view, "child-shared"));
191 CHECK(shmctl(id, IPC_STAT, &status) == 0 && status.shm_nattch == 1);
192 CHECK(shmdt(view) == 0);
193 view = (void*)-1;
194 CHECK(shmctl(id, IPC_STAT, &status) == -1 && errno == EINVAL);
195 id = -1;
196
197 split_id = shmget(IPC_PRIVATE, length, 0600);
198 CHECK(split_id >= 0);
199 split = shmat(split_id, NULL, 0);
200 CHECK(split != (void*)-1);
201 CHECK(shmctl(split_id, IPC_RMID, NULL) == 0);
202 CHECK(mprotect(split + page, page, PROT_READ) == 0);
203 CHECK(shmctl(split_id, IPC_STAT, &status) == 0 && status.shm_nattch == 1);
204 child = fork();
205 CHECK(child >= 0);
206 if (!child) {
207 alarm(5);
208 _exit(shmctl(split_id, IPC_STAT, &status) == 0 && status.shm_nattch == 2 ? 0 : 1);
209 }
210 CHECK(reap(child) == 0);
211 CHECK(shmctl(split_id, IPC_STAT, &status) == 0 && status.shm_nattch == 1);
212 CHECK(munmap(split, page) == 0);
213 replacement =
214 mmap(split, page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
215 CHECK(replacement == split);
216 replacement[0] = 19;
217 middle = mmap(split + page, page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
218 -1, 0);
219 CHECK(middle == split + page);
220 middle[0] = 29;
221 CHECK(shmdt(split) == 0);
222 split = (void*)-1;
223 CHECK(replacement[0] == 19 && middle[0] == 29);
224 CHECK(shmctl(split_id, IPC_STAT, &status) == -1 && errno == EINVAL);
225 split_id = -1;
226
227 split_id = shmget(IPC_PRIVATE, length, 0600);
228 CHECK(split_id >= 0);
229 split = shmat(split_id, NULL, 0);
230 CHECK(split != (void*)-1);
231 split[page] = 31;
232 CHECK(munmap(split, page) == 0);
233 remapped = shmat(separate, split, 0);
234 CHECK(remapped == split);
235 remapped[0] = 43;
236 CHECK(shmdt(remapped) == 0);
237 remapped = (void*)-1;
238 CHECK(shmctl(separate, IPC_STAT, &status) == 0 && status.shm_nattch == 0);
239 CHECK(shmctl(split_id, IPC_STAT, &status) == 0 && status.shm_nattch == 1);
240 CHECK(split[page] == 31);
241 CHECK(shmdt(split) == 0);
242 split = (void*)-1;
243 CHECK(shmctl(split_id, IPC_RMID, NULL) == 0);
244 split_id = -1;
245
246out:
247 if (view != (void*)-1)
248 shmdt(view);
249 if (readonly != (void*)-1)
250 shmdt(readonly);
251 if (remapped != (void*)-1)
252 shmdt(remapped);
253 if (split != (void*)-1)
254 shmdt(split);
255 if (id >= 0)
256 shmctl(id, IPC_RMID, NULL);
257 if (separate >= 0)
258 shmctl(separate, IPC_RMID, NULL);
259 if (split_id >= 0)
260 shmctl(split_id, IPC_RMID, NULL);
261 if (reserved != MAP_FAILED)
262 munmap(reserved, length);
263 if (replacement != MAP_FAILED)
264 munmap(replacement, page);
265 if (middle != MAP_FAILED)
266 munmap(middle, page);
267 if (bad != MAP_FAILED)
268 munmap(bad, page);
269 alarm(0);
270 if (!failed)
271 puts("ipc-contract-test: shared-memory passed");
272 return failed;
273}