The Pedigree Project 0.1
api.c
1#define _GNU_SOURCE
2#include <string.h>
3#include <unistd.h>
4
5#include "contract.h"
6#include <sys/mman.h>
7#include <sys/syscall.h>
8#include <sys/sysinfo.h>
9
10static int affinity_abi(void) {
11 int failed = 0;
12 cpu_set_t raw, public, original, singleton;
13 void* bad = mmap(NULL, sc_page, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
14 CHECK(bad != MAP_FAILED);
15 CHECK(sched_getaffinity(0, sizeof(original), &original) == 0);
16 CHECK(sysconf(_SC_NPROCESSORS_CONF) == sc_count && sysconf(_SC_NPROCESSORS_ONLN) == sc_count);
17 CHECK(get_nprocs_conf() == sc_count && get_nprocs() == sc_count);
18 memset(&raw, 0xa7, sizeof(raw));
19 CHECK(syscall(SYS_sched_getaffinity, 0, sizeof(raw), &raw) == (long)sc_bytes);
20 for (size_t n = sc_bytes; n < sizeof(raw); ++n)
21 CHECK(((unsigned char*)&raw)[n] == 0xa7);
22 memset(&public, 0xa7, sizeof(public));
23 CHECK(sched_getaffinity(0, sizeof(public), &public) == 0);
24 CHECK(CPU_EQUAL(&original, &public));
25 for (size_t n = sc_bytes; n < sizeof(public); ++n)
26 CHECK(((unsigned char*)&public)[n] == 0);
27 CHECK(pthread_getaffinity_np(pthread_self(), sizeof(public), &public) == 0);
28 CHECK(CPU_EQUAL(&original, &public));
29 CHECK(syscall(SYS_sched_getaffinity, syscall(SYS_gettid), sc_bytes, &raw) == (long)sc_bytes);
30 errno = 0;
31 CHECK(syscall(SYS_sched_getaffinity, 0, sc_bytes - 1, &raw) == -1 && errno == EINVAL);
32 errno = 0;
33 CHECK(syscall(SYS_sched_getaffinity, 0, sc_bytes + 1, &raw) == -1 && errno == EINVAL);
34 errno = 0;
35 CHECK(syscall(SYS_sched_getaffinity, -1, sizeof(raw), &raw) == -1 && errno == ESRCH);
36 errno = 0;
37 CHECK(syscall(SYS_sched_getaffinity, 0, sc_bytes, bad) == -1 && errno == EFAULT);
38 CHECK(pthread_getaffinity_np(pthread_self(), sc_bytes - 1, &raw) == EINVAL);
39 CPU_ZERO(&singleton);
40 CPU_SET(sc_cpus[0], &singleton);
41 size_t short_size = (size_t)sc_cpus[0] / 8 + 1;
42 CHECK(syscall(SYS_sched_setaffinity, 0, short_size, &singleton) == 0);
43 CHECK(sc_mask(0, sc_cpus[0]) == 0 && sc_sample(sc_cpus[0]) == 0);
44 CHECK(sysconf(_SC_NPROCESSORS_CONF) == 1 && sysconf(_SC_NPROCESSORS_ONLN) == 1);
45 CHECK(get_nprocs_conf() == 1 && get_nprocs() == 1);
46 // Ignored suffix bytes must not cause an unnecessary user access.
47 unsigned char* edge =
48 mmap(NULL, 2 * sc_page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
49 CHECK(edge != MAP_FAILED);
50 if (mprotect(edge + sc_page, sc_page, PROT_NONE)) {
51 munmap(edge, 2 * sc_page);
52 CHECK(0);
53 }
54 memcpy(edge + sc_page - sc_bytes, &singleton, sc_bytes);
55 long copied = syscall(SYS_sched_setaffinity, 0, 128, edge + sc_page - sc_bytes);
56 munmap(edge, 2 * sc_page);
57 CHECK(copied == 0);
58 errno = 0;
59 CHECK(syscall(SYS_sched_setaffinity, 0, sc_bytes, bad) == -1 && errno == EFAULT);
60 CHECK(sc_mask(0, sc_cpus[0]) == 0);
61 CPU_ZERO(&raw);
62 errno = 0;
63 CHECK(sched_setaffinity(0, sizeof(raw), &raw) == -1 && errno == EINVAL);
64 CPU_SET(CPU_SETSIZE - 1, &raw);
65 CHECK(!CPU_ISSET(CPU_SETSIZE - 1, &original));
66 errno = 0;
67 CHECK(sched_setaffinity(0, sizeof(raw), &raw) == -1 && errno == EINVAL);
68 errno = 0;
69 CHECK(syscall(SYS_sched_setaffinity, 0, 0, NULL) == -1 && errno == EINVAL);
70 errno = 0;
71 CHECK(syscall(SYS_sched_setaffinity, -1, sizeof(singleton), &singleton) == -1 && errno == ESRCH);
72 CHECK(sc_mask(0, sc_cpus[0]) == 0);
73 CHECK(pthread_setaffinity_np(pthread_self(), sizeof(original), &original) == 0);
74 CHECK(sched_getaffinity(0, sizeof(public), &public) == 0 && CPU_EQUAL(&public, &original));
75out:
76 if (bad != MAP_FAILED)
77 munmap(bad, sc_page);
78 return failed;
79}
80
81static int cpu_abi(void) {
82 int failed = 0;
83 unsigned cpu = UINT32_MAX, node = UINT32_MAX;
84 void* bad = mmap(NULL, sc_page, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
85 CHECK(bad != MAP_FAILED && sc_pin(0, sc_cpus[0]) == 0);
86 CHECK(syscall(SYS_getcpu, &cpu, &node, bad) == 0 && cpu == (unsigned)sc_cpus[0] && node == 0);
87 CHECK(syscall(SYS_getcpu, NULL, NULL, bad) == 0);
88 node = UINT32_MAX;
89 CHECK(syscall(SYS_getcpu, NULL, &node, NULL) == 0 && node == 0);
90 cpu = UINT32_MAX;
91 errno = 0;
92 CHECK(syscall(SYS_getcpu, &cpu, bad, NULL) == -1 && errno == EFAULT &&
93 cpu == (unsigned)sc_cpus[0]);
94 node = UINT32_MAX;
95 errno = 0;
96 CHECK(syscall(SYS_getcpu, bad, &node, NULL) == -1 && errno == EFAULT && node == 0);
97 CHECK(sc_sample(sc_cpus[0]) == 0);
98out:
99 if (bad != MAP_FAILED)
100 munmap(bad, sc_page);
101 return failed;
102}
103
104static atomic_uint entries;
105static void* explicit_entry(void* argument) {
106 atomic_fetch_add_explicit(&entries, 1, memory_order_relaxed);
107 int policy = -1;
108 struct sched_param param;
109 int error = pthread_getschedparam(pthread_self(), &policy, &param);
110 return (void*)(intptr_t)(error || policy != SCHED_OTHER || param.sched_priority ||
111 sc_mask(0, *(int*)argument) || sc_sample(*(int*)argument));
112}
113static int policy_abi(void) {
114 int failed = 0, attr_ready = 0;
115 pthread_attr_t attr;
116 struct sched_param param = {.sched_priority = 0};
117 void* bad = mmap(NULL, sc_page, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
118 CHECK(bad != MAP_FAILED);
119 int words[4] = {-1, 0x126abc, 0x35c9ed, 0x47ad8b};
120 CHECK(syscall(SYS_sched_getparam, 0, words) == 0 && words[0] == 0 && words[1] == 0x126abc &&
121 words[2] == 0x35c9ed && words[3] == 0x47ad8b);
122 CHECK(syscall(SYS_sched_getscheduler, 0) == SCHED_OTHER);
123 CHECK(syscall(SYS_sched_setparam, 0, &param) == 0);
124 CHECK(syscall(SYS_sched_setscheduler, 0, SCHED_OTHER, &param) == 0);
125 errno = 0;
126 CHECK(syscall(SYS_sched_getscheduler, -1) == -1 && errno == EINVAL);
127 for (size_t i = 0; i < 2; ++i) {
128 long number = i ? SYS_sched_setparam : SYS_sched_getparam;
129 errno = 0;
130 CHECK(syscall(number, 0, NULL) == -1 && errno == EINVAL);
131 errno = 0;
132 CHECK(syscall(number, -1, &param) == -1 && errno == EINVAL);
133 errno = 0;
134 CHECK(syscall(number, 0, bad) == -1 && errno == EFAULT);
135 }
136 errno = 0;
137 CHECK(syscall(SYS_sched_setscheduler, 0, -1, bad) == -1 && errno == EINVAL);
138 param.sched_priority = 1;
139 errno = 0;
140 CHECK(syscall(SYS_sched_setparam, 0, &param) == -1 && errno == EINVAL);
141 CHECK(pthread_setschedprio(pthread_self(), 1) == EINVAL);
142 param.sched_priority = 0;
143 CHECK(pthread_setschedparam(pthread_self(), SCHED_OTHER, &param) == 0);
144 CHECK(pthread_setschedprio(pthread_self(), 0) == 0);
145 const int policies[] = {SCHED_OTHER, SCHED_FIFO, SCHED_RR,
146 SCHED_BATCH, SCHED_IDLE, SCHED_DEADLINE};
147 for (size_t i = 0; i < sizeof(policies) / sizeof(policies[0]); ++i) {
148 const int realtime = policies[i] == SCHED_FIFO || policies[i] == SCHED_RR;
149 CHECK(sched_get_priority_min(policies[i]) == (realtime ? 1 : 0));
150 CHECK(sched_get_priority_max(policies[i]) == (realtime ? 99 : 0));
151 if (policies[i] == SCHED_OTHER)
152 continue;
153 param.sched_priority = realtime ? 1 : 0;
154 errno = 0;
155 CHECK(syscall(SYS_sched_setscheduler, 0, policies[i], &param) == -1 && errno == EOPNOTSUPP);
156 param.sched_priority = realtime ? 0 : 1;
157 errno = 0;
158 CHECK(syscall(SYS_sched_setscheduler, 0, policies[i], &param) == -1 && errno == EINVAL);
159 }
160 param.sched_priority = 0;
161 errno = 0;
162 CHECK(syscall(SYS_sched_setscheduler, 0, SCHED_OTHER | SCHED_RESET_ON_FORK, &param) == -1 &&
163 errno == EOPNOTSUPP);
164 errno = 0;
165 CHECK(syscall(SYS_sched_setscheduler, 0, 0x20000000, &param) == -1 && errno == EINVAL);
166 errno = 0;
167 CHECK(sched_get_priority_min(77) == -1 && errno == EINVAL);
168 errno = 0;
169 CHECK(sched_get_priority_max(77) == -1 && errno == EINVAL);
170 CHECK(syscall(SYS_sched_getscheduler, 0) == SCHED_OTHER);
171 CHECK(sc_pin(0, sc_cpus[0]) == 0 && pthread_attr_init(&attr) == 0);
172 attr_ready = 1;
173 CHECK(pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED) == 0);
174 CHECK(pthread_attr_setschedpolicy(&attr, SCHED_OTHER) == 0);
175 CHECK(pthread_attr_setschedparam(&attr, &param) == 0);
176 pthread_t thread;
177 CHECK(pthread_create(&thread, &attr, explicit_entry, &sc_cpus[0]) == 0);
178 void* result;
179 CHECK(pthread_join(thread, &result) == 0 && result == NULL && atomic_load(&entries) == 1);
180 param.sched_priority = 1;
181 CHECK(pthread_attr_setschedpolicy(&attr, SCHED_FIFO) == 0);
182 CHECK(pthread_attr_setschedparam(&attr, &param) == 0);
183 CHECK(pthread_create(&thread, &attr, explicit_entry, &sc_cpus[0]) == EOPNOTSUPP);
184 CHECK(atomic_load(&entries) == 1);
185out:
186 if (attr_ready)
187 pthread_attr_destroy(&attr);
188 if (bad != MAP_FAILED)
189 munmap(bad, sc_page);
190 return failed;
191}
192
194 atomic_uint ready, stop;
195 pid_t tid;
196};
198 pid_t tid;
199 struct timespec expected;
200};
201static void* interval_entry(void* argument) {
202 struct interval_peer* peer = argument;
203 peer->tid = (pid_t)syscall(SYS_gettid);
204 atomic_store_explicit(&peer->ready, 1, memory_order_release);
205 while (!atomic_load_explicit(&peer->stop, memory_order_acquire))
206 sched_yield();
207 return NULL;
208}
209static int interval_unprivileged(int command, int report, void* argument) {
210 (void)command;
211 (void)report;
212 const struct interval_query* query = argument;
213 struct timespec value;
214 return setresuid(51991, 51991, 51991) || sched_rr_get_interval(query->tid, &value) ||
215 value.tv_sec != query->expected.tv_sec || value.tv_nsec != query->expected.tv_nsec;
216}
217static int interval_abi(void) {
218 _Static_assert(sizeof(struct timespec) == 16, "amd64 Linux timespec ABI");
219 int failed = 0, created = 0;
220 pthread_t thread;
221 struct interval_peer peer = {0};
222 struct sc_peer child = SC_PEER_INITIALIZER;
223 struct timespec baseline, value;
224 void* bad = mmap(NULL, sc_page, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
225 CHECK(bad != MAP_FAILED);
226 CHECK(sched_rr_get_interval(0, &baseline) == 0);
227 CHECK(baseline.tv_sec >= 0 && baseline.tv_nsec >= 0 && baseline.tv_nsec < 1000000000 &&
228 (baseline.tv_sec || baseline.tv_nsec));
229 printf("SCHEDULING-CONTRACT: nominal interval=%llds+%lldns\n", (long long)baseline.tv_sec,
230 (long long)baseline.tv_nsec);
231 CHECK(pthread_create(&thread, NULL, interval_entry, &peer) == 0);
232 created = 1;
233 CHECK(sc_wait(&peer.ready, 1) == 0);
234 for (int i = 0; i < sc_count; ++i) {
235 CHECK(sc_pin(0, sc_cpus[i]) == 0 && sc_pin(peer.tid, sc_cpus[i]) == 0);
236 CHECK(sc_sample(sc_cpus[i]) == 0);
237 CHECK(sched_rr_get_interval(0, &value) == 0 && value.tv_sec == baseline.tv_sec &&
238 value.tv_nsec == baseline.tv_nsec);
239 CHECK(sched_rr_get_interval(peer.tid, &value) == 0 && value.tv_sec == baseline.tv_sec &&
240 value.tv_nsec == baseline.tv_nsec);
241 struct {
242 int64_t seconds, nanoseconds;
243 uint64_t guard[2];
244 } raw = {-1, -1, {UINT64_C(0x48fd8270ab65139c), UINT64_C(0x95814aece263bf70)}};
245 CHECK(syscall(SYS_sched_rr_get_interval, peer.tid, &raw) == 0);
246 CHECK(raw.seconds == baseline.tv_sec && raw.nanoseconds == baseline.tv_nsec &&
247 raw.guard[0] == UINT64_C(0x48fd8270ab65139c) &&
248 raw.guard[1] == UINT64_C(0x95814aece263bf70));
249 }
250 struct interval_query query = {peer.tid, baseline};
251 CHECK(sc_spawn(&child, interval_unprivileged, &query) == 0 && sc_join(&child) == 0);
252 errno = 0;
253 CHECK(sched_rr_get_interval(-1, &value) == -1 && errno == EINVAL);
254 errno = 0;
255 CHECK(syscall(SYS_sched_rr_get_interval, 0, bad) == -1 && errno == EFAULT);
256 errno = 0;
257 CHECK(syscall(SYS_sched_rr_get_interval, 0, NULL) == -1 && errno == EFAULT);
258 atomic_store_explicit(&peer.stop, 1, memory_order_release);
259 CHECK(pthread_join(thread, NULL) == 0);
260 created = 0;
261 CHECK(sc_wait_for_task_retirement(peer.tid, &baseline) == 0);
262 errno = 0;
263 CHECK(sched_rr_get_interval(peer.tid, &value) == -1 && errno == ESRCH);
264out:
265 sc_cleanup(&child);
266 if (created) {
267 atomic_store_explicit(&peer.stop, 1, memory_order_release);
268 pthread_join(thread, NULL);
269 }
270 if (bad != MAP_FAILED)
271 munmap(bad, sc_page);
272 return failed;
273}
274
275int sc_api(void) {
276 return affinity_abi() || cpu_abi() || policy_abi() || interval_abi();
277}