xref: /aosp_15_r20/external/virglrenderer/server/render_worker.c (revision bbecb9d118dfdb95f99bd754f8fa9be01f189df3)
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