The Pedigree Project 0.1
memory-lock-contract-test/lifetime.c
1#define _GNU_SOURCE
2#include <pthread.h>
3#include <signal.h>
4#include <unistd.h>
5
6#include "contract.h"
7#include <sys/mman.h>
8#include <sys/resource.h>
9
11 int command;
12 int report;
13 void* page;
14};
15
16static void* change_shared_limit(void* argument) {
17 struct shared_wait* context = argument;
18 int failed = 0;
19 struct rlimit limit;
20 CHECK(ml_receive(context->command, 'G') == 0);
21 CHECK(getrlimit(RLIMIT_MEMLOCK, &limit) == 0 && limit.rlim_cur == 2 * ml_page);
22 CHECK(mlock(context->page, ml_page) == 0);
23 CHECK(ml_limit(ml_page) == 0 && ml_send(context->report, 'C') == 0);
24 CHECK(ml_receive(context->command, 'R') == 0);
25 CHECK(munlock(context->page, ml_page) == 0);
26out:
27 if (failed)
28 ml_send(context->report, 'E');
29 return (void*)(uintptr_t)failed;
30}
31
32static int shared_threads(void) {
33 int failed = 0;
34 int command[2] = {-1, -1};
35 int report[2] = {-1, -1};
36 pthread_t thread;
37 int thread_live = 0;
38 unsigned char* pages = MAP_FAILED;
39 struct shared_wait context;
40 void* result = NULL;
41 struct rlimit limit;
42 CHECK(ml_limit(2 * ml_page) == 0 && ml_unprivileged() == 0);
43 CHECK(pipe(command) == 0 && pipe(report) == 0);
44 pages = mmap(NULL, 3 * ml_page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
45 CHECK(pages != MAP_FAILED);
46 context = (struct shared_wait){command[0], report[1], pages + ml_page};
47 CHECK(pthread_create(&thread, NULL, change_shared_limit, &context) == 0);
48 thread_live = 1;
49 CHECK(mlock(pages, ml_page) == 0 && ml_send(command[1], 'G') == 0);
50 CHECK(ml_receive(report[0], 'C') == 0);
51 CHECK(getrlimit(RLIMIT_MEMLOCK, &limit) == 0 && limit.rlim_cur == ml_page);
52 errno = 0;
53 CHECK(madvise(pages, 2 * ml_page, MADV_DONTNEED) == -1 && errno == EINVAL);
54 CHECK(munlock(pages, ml_page) == 0);
55 errno = 0;
56 CHECK(mlock(pages + 2 * ml_page, ml_page) == -1 && errno == ENOMEM);
57 CHECK(ml_send(command[1], 'R') == 0);
58 CHECK(pthread_join(thread, &result) == 0);
59 thread_live = 0;
60 CHECK(result == NULL && mlock(pages + 2 * ml_page, ml_page) == 0);
61out:
62 if (thread_live) {
63 ml_send(command[1], 'R');
64 pthread_join(thread, NULL);
65 }
66 munlockall();
67 for (int n = 0; n < 2; ++n) {
68 if (command[n] >= 0)
69 close(command[n]);
70 if (report[n] >= 0)
71 close(report[n]);
72 }
73 if (pages != MAP_FAILED)
74 munmap(pages, 3 * ml_page);
75 return failed;
76}
77
78static void* leave_locked_stack(void* argument) {
79 (void)argument;
80 volatile unsigned char storage[2 * ml_page];
81 void* aligned = (void*)(((uintptr_t)storage + ml_page - 1) & ~(uintptr_t)(ml_page - 1));
82 storage[0] = 0x65;
83 return (void*)(uintptr_t)(mlock(aligned, ml_page) != 0);
84}
85
86static int thread_retirement(void) {
87 int failed = 0;
88 int live = 0;
89 pthread_t thread;
90 void* result = NULL;
91 void* page = MAP_FAILED;
92 CHECK(ml_limit(ml_page) == 0);
93 page = mmap(NULL, ml_page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
94 CHECK(page != MAP_FAILED && pthread_create(&thread, NULL, leave_locked_stack, NULL) == 0);
95 live = 1;
96 CHECK(pthread_join(thread, &result) == 0);
97 live = 0;
98 CHECK(result == NULL && mlock(page, ml_page) == 0);
99out:
100 if (live)
101 pthread_join(thread, NULL);
102 munlockall();
103 if (page != MAP_FAILED)
104 munmap(page, ml_page);
105 return failed;
106}
107
108static int fork_child(unsigned char* inherited) {
109 int failed = 0;
110 struct rlimit limit;
111 void* fresh = MAP_FAILED;
112 CHECK(getrlimit(RLIMIT_MEMLOCK, &limit) == 0 && limit.rlim_cur == 2 * ml_page);
113 CHECK(madvise(inherited, 2 * ml_page, MADV_DONTNEED) == 0 && inherited[0] == 0);
114 fresh = mmap(NULL, 3 * ml_page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
115 CHECK(fresh != MAP_FAILED && mlock(fresh, 2 * ml_page) == 0);
116 errno = 0;
117 CHECK(mlock((unsigned char*)fresh + 2 * ml_page, ml_page) == -1 && errno == ENOMEM);
118 inherited[0] = 0x77;
119out:
120 munlockall();
121 if (fresh != MAP_FAILED)
122 munmap(fresh, 3 * ml_page);
123 return failed;
124}
125
126static int fork_policy(void) {
127 int failed = 0;
128 pid_t child = -1;
129 unsigned char* pages = MAP_FAILED;
130 void* extra = MAP_FAILED;
131 CHECK(ml_limit(2 * ml_page) == 0);
132 pages = mmap(NULL, 2 * ml_page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
133 extra = mmap(NULL, ml_page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
134 CHECK(pages != MAP_FAILED && extra != MAP_FAILED);
135 pages[0] = 0x31;
136 CHECK(mlock(pages, 2 * ml_page) == 0 && mlockall(MCL_FUTURE | MCL_ONFAULT) == 0);
137 child = fork();
138 CHECK(child >= 0);
139 if (!child) {
140 alarm(8);
141 _exit(fork_child(pages));
142 }
143 int status = ml_reap(child, 10000);
144 child = -1;
145 CHECK(status == 0 && pages[0] == 0x31);
146 errno = 0;
147 CHECK(madvise(pages, 2 * ml_page, MADV_DONTNEED) == -1 && errno == EINVAL);
148 errno = 0;
149 CHECK(mlock(extra, ml_page) == -1 && errno == ENOMEM);
150out:
151 if (child > 0) {
152 kill(child, SIGKILL);
153 ml_reap(child, 2000);
154 }
155 munlockall();
156 if (extra != MAP_FAILED)
157 munmap(extra, ml_page);
158 if (pages != MAP_FAILED)
159 munmap(pages, 2 * ml_page);
160 return failed;
161}
162
163int ml_exec(int argc, char** argv) {
164 (void)argv;
165 int failed = 0;
166 struct rlimit limit;
167 void* pages = MAP_FAILED;
168 alarm(8);
169 CHECK(argc == 2 && geteuid() != 0);
170 CHECK(getrlimit(RLIMIT_MEMLOCK, &limit) == 0 && limit.rlim_cur == 2 * ml_page &&
171 limit.rlim_max == 16 * 1024 * 1024);
172 pages = mmap(NULL, 3 * ml_page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
173 CHECK(pages != MAP_FAILED && mlock(pages, 2 * ml_page) == 0);
174 errno = 0;
175 CHECK(mlock((unsigned char*)pages + 2 * ml_page, ml_page) == -1 && errno == ENOMEM);
176out:
177 munlockall();
178 if (pages != MAP_FAILED)
179 munmap(pages, 3 * ml_page);
180 printf("MEMORY-LOCK-CONTRACT: EXEC %s\n", failed ? "FAIL" : "PASS");
181 return failed;
182}
183
184static int exec_policy(void) {
185 pid_t child = fork();
186 if (child < 0)
187 return 1;
188 if (!child) {
189 alarm(8);
190 void* pages =
191 mmap(NULL, 2 * ml_page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
192 if (pages == MAP_FAILED || ml_limit(2 * ml_page) || mlock(pages, 2 * ml_page) ||
193 mlockall(MCL_FUTURE | MCL_ONFAULT))
194 _exit(2);
195 execl("/applications/memory-lock-contract-test", "memory-lock-contract-test",
196 "memory-lock-exec", NULL);
197 _exit(3);
198 }
199 int result = ml_reap(child, 10000);
200 if (result)
201 fprintf(stderr, "MEMORY-LOCK-CONTRACT: exec child status=%d\n", result);
202 return result != 0;
203}
204
205int ml_lifetime(void) {
206 return shared_threads() || thread_retirement() || fork_policy() || exec_policy();
207}