1 /*
2 * Copyright 2021 Google LLC
3 * SPDX-License-Identifier: MIT
4 */
5
6 #include "render_worker.h"
7
8 /* One and only one of ENABLE_RENDER_SERVER_WORKER_* must be set.
9 *
10 * With ENABLE_RENDER_SERVER_WORKER_PROCESS, each worker is a subprocess
11 * forked from the server process.
12 *
13 * With ENABLE_RENDER_SERVER_WORKER_THREAD, each worker is a thread of the
14 * server process.
15 *
16 * With ENABLE_RENDER_SERVER_WORKER_MINIJAIL, each worker is a subprocess
17 * forked from the server process, jailed with minijail.
18 */
19 #if (ENABLE_RENDER_SERVER_WORKER_PROCESS + ENABLE_RENDER_SERVER_WORKER_THREAD + \
20 ENABLE_RENDER_SERVER_WORKER_MINIJAIL) != 1
21 #error "no worker defined"
22 #endif
23
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <signal.h>
27 #include <sys/signalfd.h>
28 #include <sys/types.h>
29 #include <sys/wait.h>
30 #include <threads.h>
31 #include <unistd.h>
32
33 struct minijail;
34
35 struct render_worker_jail {
36 int max_worker_count;
37
38 int sigchld_fd;
39 struct minijail *minijail;
40
41 struct list_head workers;
42 int worker_count;
43 };
44
45 struct render_worker {
46 #ifdef ENABLE_RENDER_SERVER_WORKER_THREAD
47 thrd_t thread;
48 #else
49 pid_t pid;
50 #endif
51 bool destroyed;
52 bool reaped;
53
54 struct list_head head;
55
56 char thread_data[];
57 };
58
59 #ifdef ENABLE_RENDER_SERVER_WORKER_MINIJAIL
60
61 #include <fcntl.h>
62 #include <libminijail.h>
63 #include <linux/filter.h>
64 #include <linux/seccomp.h>
65 #include <stdio.h>
66 #include <sys/stat.h>
67
68 static bool
load_bpf_program(struct sock_fprog * prog,const char * path)69 load_bpf_program(struct sock_fprog *prog, const char *path)
70 {
71 int fd = -1;
72 void *data = NULL;
73
74 fd = open(path, O_RDONLY);
75 if (fd < 0)
76 goto fail;
77
78 const off_t size = lseek(fd, 0, SEEK_END);
79 if (size <= 0 || size % sizeof(struct sock_filter))
80 goto fail;
81 lseek(fd, 0, SEEK_SET);
82
83 data = malloc(size);
84 if (!data)
85 goto fail;
86
87 off_t cur = 0;
88 while (cur < size) {
89 const ssize_t r = read(fd, (char *)data + cur, size - cur);
90 if (r <= 0)
91 goto fail;
92 cur += r;
93 }
94
95 close(fd);
96
97 prog->len = size / sizeof(struct sock_filter);
98 prog->filter = data;
99
100 return true;
101
102 fail:
103 if (data)
104 free(data);
105 if (fd >= 0)
106 close(fd);
107 return false;
108 }
109
110 static struct minijail *
create_minijail(enum render_worker_jail_seccomp_filter seccomp_filter,const char * seccomp_path)111 create_minijail(enum render_worker_jail_seccomp_filter seccomp_filter,
112 const char *seccomp_path)
113 {
114 struct minijail *j = minijail_new();
115
116 /* TODO namespaces and many more */
117 minijail_no_new_privs(j);
118
119 if (seccomp_filter != RENDER_WORKER_JAIL_SECCOMP_NONE) {
120 if (seccomp_filter == RENDER_WORKER_JAIL_SECCOMP_BPF) {
121 struct sock_fprog prog;
122 if (!load_bpf_program(&prog, seccomp_path)) {
123 minijail_destroy(j);
124 return NULL;
125 }
126
127 minijail_set_seccomp_filters(j, &prog);
128 free(prog.filter);
129 } else {
130 if (seccomp_filter == RENDER_WORKER_JAIL_SECCOMP_MINIJAIL_POLICY_LOG)
131 minijail_log_seccomp_filter_failures(j);
132 minijail_parse_seccomp_filters(j, seccomp_path);
133 }
134
135 minijail_use_seccomp_filter(j);
136 }
137
138 return j;
139 }
140
141 static pid_t
fork_minijail(const struct minijail * template)142 fork_minijail(const struct minijail *template)
143 {
144 struct minijail *j = minijail_new();
145 if (!j)
146 return -1;
147
148 /* is this faster? */
149 if (minijail_copy_jail(template, j)) {
150 minijail_destroy(j);
151 return -1;
152 }
153
154 pid_t pid = minijail_fork(j);
155 minijail_destroy(j);
156
157 return pid;
158 }
159
160 #endif /* ENABLE_RENDER_SERVER_WORKER_MINIJAIL */
161
162 #ifndef ENABLE_RENDER_SERVER_WORKER_THREAD
163
164 static int
create_sigchld_fd(void)165 create_sigchld_fd(void)
166 {
167 const int signum = SIGCHLD;
168
169 sigset_t set;
170 if (sigemptyset(&set) || sigaddset(&set, signum)) {
171 render_log("failed to initialize sigset_t");
172 return -1;
173 }
174
175 int fd = signalfd(-1, &set, SFD_NONBLOCK | SFD_CLOEXEC);
176 if (fd < 0) {
177 render_log("failed to create signalfd");
178 return -1;
179 }
180
181 if (sigprocmask(SIG_BLOCK, &set, NULL)) {
182 render_log("failed to call sigprocmask");
183 close(fd);
184 return -1;
185 }
186
187 return fd;
188 }
189
190 #endif /* !ENABLE_RENDER_SERVER_WORKER_THREAD */
191
192 static void
render_worker_jail_add_worker(struct render_worker_jail * jail,struct render_worker * worker)193 render_worker_jail_add_worker(struct render_worker_jail *jail,
194 struct render_worker *worker)
195 {
196 list_add(&worker->head, &jail->workers);
197 jail->worker_count++;
198 }
199
200 static void
render_worker_jail_remove_worker(struct render_worker_jail * jail,struct render_worker * worker)201 render_worker_jail_remove_worker(struct render_worker_jail *jail,
202 struct render_worker *worker)
203 {
204 list_del(&worker->head);
205 jail->worker_count--;
206
207 free(worker);
208 }
209
210 static struct render_worker *
render_worker_jail_reap_any_worker(struct render_worker_jail * jail,bool block)211 render_worker_jail_reap_any_worker(struct render_worker_jail *jail, bool block)
212 {
213 #ifdef ENABLE_RENDER_SERVER_WORKER_THREAD
214 (void)jail;
215 (void)block;
216 return NULL;
217 #else
218 const int options = WEXITED | (block ? 0 : WNOHANG);
219 siginfo_t siginfo = { 0 };
220 const int ret = waitid(P_ALL, 0, &siginfo, options);
221 const pid_t pid = ret ? 0 : siginfo.si_pid;
222 if (!pid)
223 return NULL;
224
225 list_for_each_entry (struct render_worker, worker, &jail->workers, head) {
226 if (worker->pid == pid) {
227 worker->reaped = true;
228 return worker;
229 }
230 }
231
232 render_log("unknown child process %d", pid);
233 return NULL;
234 #endif
235 }
236
237 struct render_worker_jail *
render_worker_jail_create(int max_worker_count,enum render_worker_jail_seccomp_filter seccomp_filter,const char * seccomp_path)238 render_worker_jail_create(int max_worker_count,
239 enum render_worker_jail_seccomp_filter seccomp_filter,
240 const char *seccomp_path)
241 {
242 struct render_worker_jail *jail = calloc(1, sizeof(*jail));
243 if (!jail)
244 return NULL;
245
246 jail->max_worker_count = max_worker_count;
247 jail->sigchld_fd = -1;
248 list_inithead(&jail->workers);
249
250 #ifndef ENABLE_RENDER_SERVER_WORKER_THREAD
251 jail->sigchld_fd = create_sigchld_fd();
252 if (jail->sigchld_fd < 0)
253 goto fail;
254 #endif
255
256 #if defined(ENABLE_RENDER_SERVER_WORKER_MINIJAIL)
257 jail->minijail = create_minijail(seccomp_filter, seccomp_path);
258 if (!jail->minijail)
259 goto fail;
260 #else
261 /* TODO RENDER_WORKER_JAIL_SECCOMP_BPF */
262 if (seccomp_filter != RENDER_WORKER_JAIL_SECCOMP_NONE)
263 goto fail;
264 (void)seccomp_path;
265 #endif
266
267 return jail;
268
269 fail:
270 free(jail);
271 return NULL;
272 }
273
274 static void
render_worker_jail_wait_workers(struct render_worker_jail * jail)275 render_worker_jail_wait_workers(struct render_worker_jail *jail)
276 {
277 while (jail->worker_count) {
278 struct render_worker *worker =
279 render_worker_jail_reap_any_worker(jail, true /* block */);
280 if (worker) {
281 assert(worker->destroyed && worker->reaped);
282 render_worker_jail_remove_worker(jail, worker);
283 }
284 }
285 }
286
287 void
render_worker_jail_destroy(struct render_worker_jail * jail)288 render_worker_jail_destroy(struct render_worker_jail *jail)
289 {
290 render_worker_jail_wait_workers(jail);
291
292 #if defined(ENABLE_RENDER_SERVER_WORKER_MINIJAIL)
293 minijail_destroy(jail->minijail);
294 #endif
295
296 if (jail->sigchld_fd >= 0)
297 close(jail->sigchld_fd);
298
299 free(jail);
300 }
301
302 int
render_worker_jail_get_sigchld_fd(const struct render_worker_jail * jail)303 render_worker_jail_get_sigchld_fd(const struct render_worker_jail *jail)
304 {
305 return jail->sigchld_fd;
306 }
307
308 static bool
render_worker_jail_drain_sigchld_fd(struct render_worker_jail * jail)309 render_worker_jail_drain_sigchld_fd(struct render_worker_jail *jail)
310 {
311 if (jail->sigchld_fd < 0)
312 return true;
313
314 do {
315 struct signalfd_siginfo siginfos[8];
316 const ssize_t r = read(jail->sigchld_fd, siginfos, sizeof(siginfos));
317 if (r == sizeof(siginfos))
318 continue;
319 if (r > 0 || (r < 0 && errno == EAGAIN))
320 break;
321
322 render_log("failed to read signalfd");
323 return false;
324 } while (true);
325
326 return true;
327 }
328
329 bool
render_worker_jail_reap_workers(struct render_worker_jail * jail)330 render_worker_jail_reap_workers(struct render_worker_jail *jail)
331 {
332 if (!render_worker_jail_drain_sigchld_fd(jail))
333 return false;
334
335 do {
336 struct render_worker *worker =
337 render_worker_jail_reap_any_worker(jail, false /* block */);
338 if (!worker)
339 break;
340
341 assert(worker->reaped);
342 if (worker->destroyed)
343 render_worker_jail_remove_worker(jail, worker);
344 } while (true);
345
346 return true;
347 }
348
349 void
render_worker_jail_detach_workers(struct render_worker_jail * jail)350 render_worker_jail_detach_workers(struct render_worker_jail *jail)
351 {
352 /* free workers without killing nor reaping */
353 list_for_each_entry_safe (struct render_worker, worker, &jail->workers, head)
354 render_worker_jail_remove_worker(jail, worker);
355 }
356
357 struct render_worker *
render_worker_create(struct render_worker_jail * jail,int (* thread_func)(void * thread_data),void * thread_data,size_t thread_data_size)358 render_worker_create(struct render_worker_jail *jail,
359 int (*thread_func)(void *thread_data),
360 void *thread_data,
361 size_t thread_data_size)
362 {
363 if (jail->worker_count >= jail->max_worker_count) {
364 render_log("too many workers");
365 return NULL;
366 }
367
368 struct render_worker *worker = calloc(1, sizeof(*worker) + thread_data_size);
369 if (!worker)
370 return NULL;
371
372 memcpy(worker->thread_data, thread_data, thread_data_size);
373
374 bool ok;
375 #if defined(ENABLE_RENDER_SERVER_WORKER_PROCESS)
376 worker->pid = fork();
377 ok = worker->pid >= 0;
378 (void)thread_func;
379 #elif defined(ENABLE_RENDER_SERVER_WORKER_THREAD)
380 ok = thrd_create(&worker->thread, thread_func, worker->thread_data) == thrd_success;
381 #elif defined(ENABLE_RENDER_SERVER_WORKER_MINIJAIL)
382 worker->pid = fork_minijail(jail->minijail);
383 ok = worker->pid >= 0;
384 (void)thread_func;
385 #endif
386 if (!ok) {
387 free(worker);
388 return NULL;
389 }
390
391 render_worker_jail_add_worker(jail, worker);
392
393 return worker;
394 }
395
396 void
render_worker_destroy(struct render_worker_jail * jail,struct render_worker * worker)397 render_worker_destroy(struct render_worker_jail *jail, struct render_worker *worker)
398 {
399 assert(render_worker_is_record(worker));
400
401 #ifdef ENABLE_RENDER_SERVER_WORKER_THREAD
402 /* we trust the thread to clean up and exit in finite time */
403 thrd_join(worker->thread, NULL);
404 worker->reaped = true;
405 #else
406 /* kill to make sure the worker exits in finite time */
407 if (!worker->reaped)
408 kill(worker->pid, SIGKILL);
409 #endif
410
411 worker->destroyed = true;
412
413 if (worker->reaped)
414 render_worker_jail_remove_worker(jail, worker);
415 }
416
417 bool
render_worker_is_record(const struct render_worker * worker)418 render_worker_is_record(const struct render_worker *worker)
419 {
420 /* return false if called from the worker itself */
421 #ifdef ENABLE_RENDER_SERVER_WORKER_THREAD
422 return !thrd_equal(worker->thread, thrd_current());
423 #else
424 return worker->pid > 0;
425 #endif
426 }
427