1 /*
2 * Copyright 2018-2020, Björn Ståhl
3 * License: 3-Clause BSD, see COPYING file in arcan source repository.
4 * Reference: https://arcan-fe.com
5 * Description: platform_open implementation that takes the option of
6 * privilege separation into account.
7 */
8 #include <stdbool.h>
9 #include <stdlib.h>
10 #include <stdio.h>
11 #include <stdint.h>
12 #include <stdatomic.h>
13 #include <inttypes.h>
14 #include <ctype.h>
15 #include <grp.h>
16 #include <sys/param.h>
17 #include <sys/ioctl.h>
18 #include <sys/uio.h>
19 #include <sys/types.h>
20 #include <sys/time.h>
21 #include <sys/wait.h>
22 #include <sys/socket.h>
23 #include <sys/stat.h>
24 #include <sys/resource.h>
25 #include <err.h>
26 #include <errno.h>
27 #include <fcntl.h>
28 #include <signal.h>
29 #include <poll.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34 #include "platform.h"
35
36 static uint64_t watchdog_anr_sent;
37
38 #if defined(__OpenBSD__) || defined(__FreeBSD__)
39 #include <sys/event.h>
40 /* wsdisplay_usl_io? */
41 #if defined(__FreeBSD__)
42 #include <termios.h>
43 #include <sys/consio.h>
44 #include <sys/kbio.h>
45 struct termios termios_tty;
46 #endif
47 #endif
48
49 #ifdef __LINUX
50 #include <linux/rtnetlink.h>
51 #include <linux/netlink.h>
52 #include <sys/inotify.h>
53 #include <sys/fsuid.h>
54 #include <linux/kd.h>
55 #include <linux/input.h>
56 #include <linux/vt.h>
57 #include <linux/major.h>
58 #endif
59
60 #include <assert.h>
61 #include <xf86drm.h>
62
63 #include "platform.h"
64
65 #ifndef KDSKBMUTE
66 #define KDSKBMUTE 0x4851
67 #endif
68
69 static int child_conn = -1;
70
71 enum command {
72 NO_OP = 0,
73 OPEN_DEVICE = 'o',
74 RELEASE_DEVICE = 'r',
75 OPEN_FAILED = '#',
76 NEW_INPUT_DEVICE = 'i',
77 DISPLAY_CONNECTOR_STATE = 'd',
78 SYSTEM_STATE_RELEASE = '1',
79 SYSTEM_STATE_ACQUIRE = '2',
80 SYSTEM_STATE_TERMINATE = '3',
81 };
82
83 struct packet {
84 enum command cmd_ch;
85 int arg;
86 char path[MAXPATHLEN];
87 };
88
89 enum device_mode {
90 /* Points to a single device, nothing fancy here */
91 MODE_DEFAULT = 0,
92
93 /* Interpret the path as a prefix to whatever device the user wants to
94 * open (assuming the device has a nice and wholesome name */
95 MODE_PREFIX = 1,
96
97 /* This is an experimental-not-really-in-use mode where the device never
98 * gets closed and we just lock/release the drmMaster if needed. */
99 MODE_DRM = 2,
100
101 /* For tty devices, we track those that are opened and restore their state
102 * when shutting down so that we don't risk leaving a broken tty. */
103 MODE_TTY = 4,
104 };
105
106 struct whitelist {
107 const char* const name;
108 int fd;
109 enum device_mode mode;
110 };
111
112 /*
113 * Something of a hard choice here, the default setup for /dev/input do not
114 * enforce exclusive access, meaning that other users with permissions on the
115 * device can potentially access them. This includes another instance of arcan,
116 * which is actually what we want in cases where we have multiple instances
117 * sharing the same input. On the other hand, you can then spawn an arcan
118 * instance with an appl that dumps the input events and there you go.
119 */
120 struct whitelist whitelist[] = {
121 #ifdef __LINUX
122 {"/dev/input/", -1, MODE_PREFIX},
123 {"/dev/dri/card0", -1, MODE_DRM},
124 {"/dev/dri/card1", -1, MODE_DRM},
125 {"/dev/dri/card2", -1, MODE_DRM},
126 {"/dev/dri/card3", -1, MODE_DRM},
127 {"/dev/dri/", -1, MODE_PREFIX},
128 {"/sys/class/backlight/", -1, MODE_PREFIX},
129 {"/sys/class/tty/", -1, MODE_PREFIX},
130 /* we need better patterns to deal with this one but it
131 * is needed for backlight controls as backlight resolves here */
132 {"/sys/devices/", -1, MODE_PREFIX},
133 {"/dev/tty", -1, MODE_PREFIX | MODE_TTY},
134 #elif defined(__OpenBSD__) || defined (__NetBSD__)
135 {"/dev/wsmouse", -1, MODE_DEFAULT},
136 {"/dev/wsmouse0", -1, MODE_DEFAULT},
137 {"/dev/wsmouse1", -1, MODE_DEFAULT},
138 {"/dev/wsmouse2", -1, MODE_DEFAULT},
139 {"/dev/wsmouse3", -1, MODE_DEFAULT},
140 {"/dev/uhid0", -1, MODE_DEFAULT},
141 {"/dev/uhid1", -1, MODE_DEFAULT},
142 {"/dev/uhid2", -1, MODE_DEFAULT},
143 {"/dev/uhid3", -1, MODE_DEFAULT},
144 {"/dev/tty00", -1, MODE_DEFAULT},
145 {"/dev/tty01", -1, MODE_DEFAULT},
146 {"/dev/tty02", -1, MODE_DEFAULT},
147 {"/dev/tty03", -1, MODE_DEFAULT},
148 {"/dev/tty04", -1, MODE_DEFAULT},
149 {"/dev/ttya", -1, MODE_DEFAULT},
150 {"/dev/ttyb", -1, MODE_DEFAULT},
151 {"/dev/ttyc", -1, MODE_DEFAULT},
152 {"/dev/ttyd", -1, MODE_DEFAULT},
153 {"/dev/wskbd", -1, MODE_DEFAULT},
154 {"/dev/wskbd0", -1, MODE_DEFAULT},
155 {"/dev/wskbd1", -1, MODE_DEFAULT},
156 {"/dev/wskbd2", -1, MODE_DEFAULT},
157 {"/dev/wskbd3", -1, MODE_DEFAULT},
158 {"/dev/ttyC0", -1, MODE_DEFAULT},
159 {"/dev/ttyC1", -1, MODE_DEFAULT},
160 {"/dev/ttyC2", -1, MODE_DEFAULT},
161 {"/dev/ttyC3", -1, MODE_DEFAULT},
162 {"/dev/ttyC4", -1, MODE_DEFAULT},
163 {"/dev/ttyC5", -1, MODE_DEFAULT},
164 {"/dev/ttyC6", -1, MODE_DEFAULT},
165 {"/dev/ttyC7", -1, MODE_DEFAULT},
166 {"/dev/ttyD0", -1, MODE_DEFAULT},
167 {"/dev/ttyE0", -1, MODE_DEFAULT},
168 {"/dev/ttyF0", -1, MODE_DEFAULT},
169 {"/dev/ttyG0", -1, MODE_DEFAULT},
170 {"/dev/ttyH0", -1, MODE_DEFAULT},
171 {"/dev/ttyI0", -1, MODE_DEFAULT},
172 {"/dev/ttyJ0", -1, MODE_DEFAULT},
173 {"/dev/pci", -1, MODE_DEFAULT},
174 {"/dev/dri/card0", -1, MODE_DRM},
175 {"/dev/dri/card1", -1, MODE_DRM},
176 {"/dev/dri/card2", -1, MODE_DRM},
177 {"/dev/dri/card3", -1, MODE_DRM},
178 {"/dev/dri/renderD128", -1, MODE_DEFAULT},
179 {"/dev/dri/renderD129", -1, MODE_DEFAULT},
180 {"/dev/dri/renderD130", -1, MODE_DEFAULT},
181 {"/dev/dri/renderD131", -1, MODE_DEFAULT},
182 {"/dev/amdmsr", -1, MODE_DEFAULT}
183 #elif defined(__FreeBSD__) || defined(__DragonFly__)
184 {"/dev/input/", -1, MODE_PREFIX},
185 {"/dev/sysmouse", -1, MODE_DEFAULT},
186 {"/dev/dri/", -1, MODE_PREFIX}
187 #else
188 fatal_posix_psep_open_but_no_ostable
189 #endif
190 };
191
192 /*
193 * All of the following (set/release tty, signal handlers etc) is to manage the
194 * meta-modeset that is done on tty devices in addition to the normal modeset
195 * to get control of graphics.
196 *
197 * The sequence is:
198 * a. privsep-child requests a device that is flagged as TTY, the first such
199 * request saves the current state of the device. We don't do the probing
200 * for a tty here as it is somewhat OS dependent and the client config
201 * might specify a different tty device to be used. These are needed when
202 * jumping in from something else than the normal CLI TTY, so a pty from
203 * a shell or script.
204 *
205 * b. after the state is saved, we disable local keyboard input (or all input
206 * done in the UI will potentially be mirrored inside the TTY) and register
207 * three signal handlers. TERM, USR1 and USR2. All these just forward the
208 * type of the signal to the child.
209 *
210 * c. when the user presses [some combination] that should indicate a tty-sw,
211 * it 'reopens' the tty device with an argument of the indicated number to
212 * switch to. The VT_ACTIVATE ioctl is used on the tty, telling the kernel
213 * that we want to switch.
214 *
215 * d. the kernel sends a signal to release, which we forward to the client.
216 * the client, when in a safe state(!) will read this, release as much
217 * resources as possible and send a release on the tty device. the client
218 * goes into a sleep-loop, waiting for a signal that it can resume.
219 *
220 * e. the release gets mapped to a corresponding VT_RELDISP which should
221 * prompt the kernel to allow the next TTY to take over.
222 *
223 * f. the acquire signal gets forwarded, the client wakes from its loop,
224 * re-opens its terminal device and rebuilds its GPU state, while we send
225 * and ACKACQ to the tty that we have resumed control over the TTY.
226 */
227 static struct {
228 bool active;
229 unsigned long kbmode;
230 int mode;
231 int leds;
232 int ind;
233 } got_tty;
234
235 /*
236 * Next time client reaches video poll state, it will find these, switch to
237 * the relevant state and mark a release of the related tty device
238 */
sigusr_acq(int sign,siginfo_t * info,void * ctx)239 static void sigusr_acq(int sign, siginfo_t* info, void* ctx)
240 {
241 if (-1 == write(child_conn,
242 &(struct packet){.cmd_ch = SYSTEM_STATE_ACQUIRE}, sizeof(struct packet))){}
243 }
244
sigusr_rel(int sign,siginfo_t * info,void * ctx)245 static void sigusr_rel(int sign, siginfo_t* info, void* ctx)
246 {
247 if (-1 == write(child_conn,
248 &(struct packet){.cmd_ch = SYSTEM_STATE_RELEASE}, sizeof(struct packet))){}
249 }
250
sigusr_term(int sign)251 static void sigusr_term(int sign)
252 {
253 if (-1 == write(child_conn,
254 &(struct packet){.cmd_ch = SYSTEM_STATE_TERMINATE}, sizeof(struct packet))){}
255 }
256
set_tty(int i,bool graphics)257 static void set_tty(int i, bool graphics)
258 {
259 #ifdef __LINUX
260 /* This will (hopefully) make the kernel try and initiate the switch
261 * related signals, taking care of release / acquire sequence */
262 int dfd = whitelist[got_tty.ind].fd;
263 if (-1 == dfd)
264 return;
265
266 if (graphics){
267 ioctl(dfd, KDSETMODE, KD_GRAPHICS);
268 return;
269 }
270
271 if (i >= 0){
272 ioctl(dfd, VT_ACTIVATE, i);
273 return;
274 }
275
276 /* already setup, client just reopened the device for some reason */
277 if (got_tty.active){
278 ioctl(dfd, VT_ACTIVATE, VT_ACKACQ);
279 return;
280 }
281 got_tty.active = true;
282
283 /* one subtle note here, the LED controller for the keyboard LEDs assume that
284 * we can access that property via the evdev path, since even though we do have
285 * access to the tty fd in the child process, we don't have permissions to ioctl */
286 ioctl(dfd, KDGETMODE, &got_tty.mode);
287 ioctl(dfd, KDGETLED, &got_tty.leds);
288 ioctl(dfd, KDGKBMODE, &got_tty.kbmode);
289 ioctl(dfd, KDSETLED, 0);
290 ioctl(dfd, KDSKBMUTE, 1);
291 ioctl(dfd, KDSKBMODE, K_OFF);
292 #endif
293
294 /* register signal handlers that forward the desired action to the client,
295 * and set the tty to try and signal acquire/release when a VT switch is
296 * supposed to occur */
297
298 sigaction(SIGTERM, &(struct sigaction){
299 .sa_handler = sigusr_term
300 }, NULL
301 );
302
303 sigaction(SIGUSR1, &(struct sigaction){
304 .sa_sigaction = sigusr_acq,
305 .sa_flags = SA_SIGINFO
306 }, NULL
307 );
308
309 sigaction(SIGUSR2, &(struct sigaction){
310 .sa_sigaction = sigusr_rel,
311 .sa_flags = SA_SIGINFO
312 }, NULL
313 );
314
315 #ifdef __LINUX
316 ioctl(dfd, VT_SETMODE, &(struct vt_mode){
317 .mode = VT_PROCESS,
318 .acqsig = SIGUSR1,
319 .relsig = SIGUSR2
320 });
321 #endif
322
323 /* this is treated special and is actually not triggered as part of the
324 * whitelist (though there might be reason to add it) as we get the tty
325 * from STDIN directly */
326 #ifdef __FreeBSD__
327 tcgetattr(STDIN_FILENO, &termios_tty);
328 ioctl(i, VT_SETMODE, &(struct vt_mode){
329 .mode = VT_PROCESS,
330 .acqsig = SIGUSR1,
331 .relsig = SIGUSR2
332 });
333 #endif
334 }
335
release_device(int i,bool shutdown)336 static void release_device(int i, bool shutdown)
337 {
338 if (whitelist[i].fd == -1)
339 return;
340
341 if (whitelist[i].mode & MODE_DRM){
342 #ifndef __OpenBSD__
343 drmDropMaster(whitelist[i].fd);
344 #endif
345
346 /* if we have a saved KMS mode, we could try and restore */
347 if (!shutdown)
348 return;
349 }
350
351 /* will always be released on shutdown, so use that to restore */
352 if (whitelist[i].mode & MODE_TTY){
353 #ifdef __LINUX
354 if (shutdown){
355 ioctl(whitelist[i].fd, KDSKBMUTE, 0);
356 ioctl(whitelist[i].fd, KDSETMODE, KD_TEXT);
357 ioctl(whitelist[i].fd, KDSKBMODE,
358 got_tty.kbmode == K_OFF ? K_XLATE : got_tty.kbmode);
359 ioctl(whitelist[i].fd, KDSETLED, got_tty.leds);
360 close(whitelist[i].fd);
361 whitelist[i].fd = -1;
362 }
363 else{
364 ioctl(whitelist[i].fd, VT_RELDISP, 1);
365 }
366 #endif
367 }
368 }
369
release_devices()370 static void release_devices()
371 {
372 for (size_t i = 0; i < COUNT_OF(whitelist); i++)
373 release_device(i, true);
374
375 /* For FreeBSD, we use the STDIN_FILENO and assume it is the tty */
376 #ifdef __FreeBSD__
377 tcsetattr(STDIN_FILENO, TCSAFLUSH, &termios_tty);
378 ioctl(STDIN_FILENO, KDSETMODE, KD_TEXT);
379 ioctl(STDIN_FILENO, KDSKBMODE, K_XLATE);
380 #endif
381 }
382
access_device(const char * path,int arg,bool release,bool * keep)383 static int access_device(const char* path, int arg, bool release, bool* keep)
384 {
385 struct stat devst;
386 *keep = false;
387
388 /* special case 1, substitute TTY for the active tty device (if known) */
389 if (strcmp(path, "TTY") == 0){
390 if (!got_tty.active)
391 return -1;
392 path = whitelist[got_tty.ind].name;
393 if (release && arg >= 0){
394 set_tty(arg, false);
395 }
396 }
397
398 /* special case 3 - activate TTY (GRAPHICS switch) deferred to the first frame
399 * so that any error output is actually visible after init but before first
400 * composition, return this as an 'invalid file'. */
401 if (strcmp(path, "TTYGRAPHICS") == 0){
402 if (got_tty.active)
403 set_tty(-1, true);
404
405 return -1;
406 }
407
408 /* safeguard check against a whitelist, this would require an attack path that
409 * goes from code exec inside arcan which is mostly a game over for the user,
410 * but no need to make it easier to escalate further */
411 for (size_t ind = 0; ind < COUNT_OF(whitelist); ind++){
412 if (whitelist[ind].mode & MODE_PREFIX){
413 if (0 != strncmp(
414 whitelist[ind].name, path, strlen(whitelist[ind].name)))
415 continue;
416
417 /* dumb traversal safeguard, we only accept printable and no . */
418 size_t len = strlen(path);
419 for (size_t i = 0; i < len; i++)
420 if (!(isprint(path[i])) || path[i] == '.')
421 return -1;
422
423 }
424 else if (strcmp(whitelist[ind].name, path) != 0)
425 continue;
426
427 /* only allow character devices, with the exception of linux sysfs paths */
428 if (stat(path, &devst) < 0 ||
429 (strncmp(path, "/sys", 4) != 0 && !(devst.st_mode & S_IFCHR)))
430 return -1;
431
432 /* already "open" (drm devices and ttys) */
433 if (whitelist[ind].fd != -1){
434 if (release){
435 release_device(ind, false);
436 return -1;
437 }
438 *keep = true;
439 if (whitelist[ind].mode & MODE_DRM){
440 if (arcan_watchdog_ping)
441 atomic_store(arcan_watchdog_ping, arcan_timemillis());
442
443 #ifndef __OpenBSD__
444 drmSetMaster(whitelist[ind].fd);
445 #endif
446 }
447 return whitelist[ind].fd;
448 }
449
450 /* recipient will set real flags, including cloexec etc. */
451 int fd = open(path, O_RDWR);
452 if (-1 == fd){
453 fd = open(path, O_RDONLY);
454 if (-1 == fd){
455 return -1;
456 }
457 }
458
459 if (whitelist[ind].fd != -1)
460 close(whitelist[ind].fd);
461
462 if (whitelist[ind].mode & MODE_TTY){
463 whitelist[ind].fd = fd;
464 got_tty.ind = ind;
465 set_tty(arg, false);
466 *keep = true;
467 return fd;
468 }
469
470 if (whitelist[ind].mode & MODE_DRM){
471 #ifndef __OpenBSD__
472 drmSetMaster(fd);
473 #endif
474 whitelist[ind].fd = fd;
475 *keep = true;
476 return fd;
477 }
478
479 return fd;
480 }
481
482 /* path is not valid */
483 return -1;
484 }
485
data_in(pid_t child)486 static int data_in(pid_t child)
487 {
488 struct packet cmd;
489
490 if (read(child_conn, &cmd, sizeof(cmd)) != sizeof(cmd))
491 return -1;
492
493 if (!(cmd.cmd_ch == OPEN_DEVICE || cmd.cmd_ch == RELEASE_DEVICE))
494 return -1;
495
496 /* need to keep so we can release on VT sw */
497 bool keep;
498 bool release = cmd.cmd_ch == RELEASE_DEVICE;
499
500 int fd = access_device(cmd.path, cmd.arg, release, &keep);
501
502 /* release device won't return a valid file descriptor, otherwise
503 * it is an error code that should be forwarded */
504
505 if (!release && -1 == fd){
506 cmd.cmd_ch = OPEN_FAILED;
507 write(child_conn, &cmd, sizeof(cmd));
508 }
509 else if (!release){
510 write(child_conn, &cmd, sizeof(cmd));
511 arcan_pushhandle(fd, child_conn);
512
513 if (!keep){
514 close(fd);
515 return -1;
516 }
517 }
518
519 return fd;
520 }
521
522 #ifdef __LINUX
check_netlink(pid_t child,int netlink)523 static void check_netlink(pid_t child, int netlink)
524 {
525 char buf[8192];
526 char cred[CMSG_SPACE(sizeof(struct ucred))];
527
528 struct iovec iov = {
529 .iov_base = buf,
530 .iov_len = sizeof(buf)
531 };
532
533 struct msghdr msg = {
534 .msg_iov = &iov,
535 .msg_iovlen = 1,
536 .msg_control = cred,
537 .msg_controllen = sizeof(cred),
538 };
539
540 /* someone could possibly(?) spoof this message, though the only thing it would
541 * trigger is the rate limited rescan on the client side, which isn't even a
542 * DoS, just make sure to not use this for anything else */
543 ssize_t buflen = recvmsg(netlink, &msg, 0);
544 if (buflen < 0 || (msg.msg_flags & MSG_TRUNC))
545 return;
546
547 /*
548 * uncomment for quick log / debugging
549 static FILE* netlink_log;
550 if (!netlink_log)
551 netlink_log = fopen("netlink.log", "w+");
552 fprintf(netlink_log, "%s\n", buf);
553 */
554
555 /* buf should now contain @/ and changed and drm */
556 if (!strstr(buf, "change@"))
557 return;
558
559 if (!strstr(buf, "drm/card"))
560 return;
561
562 /* don't need to notify on backlight changes */
563 if (strstr(buf, "backlight"))
564 return;
565
566 /* now we can finally write the message */
567 struct packet pkg = {
568 .cmd_ch = DISPLAY_CONNECTOR_STATE
569 };
570
571 write(child_conn, &pkg, sizeof(pkg));
572 }
573 #else
check_netlink(pid_t child,int netlink)574 static void check_netlink(pid_t child, int netlink)
575 {
576 }
577 #endif
578
check_child(pid_t child,bool die)579 static void check_child(pid_t child, bool die)
580 {
581 int st;
582
583 /* child dead? */
584 if (waitpid(child, &st, WNOHANG) > 0){
585 if (WIFEXITED(st) || WIFSIGNALED(st)){
586 die = true;
587 }
588 }
589 /* or we want the child to soft-die? */
590 else if (die){
591 kill(child, SIGTERM);
592 release_devices();
593 _exit(WEXITSTATUS(st));
594 }
595
596 /* first send a watchdog signal, SIGUSR1, child will check if it comes from
597 * ppid() or not - and use that as an 'ANR' (if it comes from the lua context,
598 * second time around, it's killing time */
599 uint64_t ts = arcan_watchdog_ping ? atomic_load(arcan_watchdog_ping) : 0;
600 if (ts && arcan_timemillis() - ts > WATCHDOG_ANR_TIMEOUT_MS){
601
602 /* only trigger if there is a drm device open */
603 bool found = false;
604 for (size_t i = 0; i < COUNT_OF(whitelist); i++){
605 if (whitelist[i].fd != -1 && (whitelist[i].mode & MODE_DRM)){
606 found = true;
607 break;
608 }
609 }
610 if (!found)
611 return;
612
613 /* the other option here would be to longjmp back into main, we ignore that as
614 * that would cause more complexity with pledge/unveil, so shutdown and assume
615 * an outer service manager would relaunch us if possible - we want explicit
616 * relaunch in order to not act as a fork() oracle for ASLR break */
617 if (watchdog_anr_sent){
618 /* if (arcan_timemillis() - watchdog_anr_sent > WATCHDOG_ANR_TIMEOUT_MS){
619 kill(child, SIGTERM);
620 release_devices();
621 _exit(EXIT_FAILURE);
622 }*/
623 }
624 else {
625 kill(child, SIGUSR1);
626 watchdog_anr_sent = arcan_timemillis();
627 }
628 }
629 else
630 watchdog_anr_sent = 0;
631 }
632
633 #if defined(__OpenBSD__) || defined(__FreeBSD__)
parent_loop(pid_t child,int netlink)634 static void parent_loop(pid_t child, int netlink)
635 {
636 static bool init_kq;
637 static int kq;
638 static struct kevent ev[3];
639 static int kused;
640
641 if (!init_kq){
642 init_kq = true;
643 kq = kqueue();
644 EV_SET(&ev[0], child_conn, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0);
645 #ifdef __OpenBSD__
646 EV_SET(&ev[1], child, EVFILT_PROC, EV_ADD | EV_ENABLE, 0, 0, 0);
647 #else
648 EV_SET(&ev[1], child, EVFILT_PROC,
649 EV_ADD | EV_ENABLE | EV_ONESHOT, NOTE_EXIT, 0, 0);
650 #endif
651 kused = 2;
652 }
653
654 struct kevent changed[kused];
655 memset(changed, '\0', sizeof(struct kevent) * kused);
656 ssize_t nret;
657 if ((nret = kevent(kq, ev, kused, changed, kused, NULL)) < 0){
658 return;
659 }
660
661 for (size_t i = 0; i < nret; i++){
662 int st;
663 if (changed[i].flags & EV_ERROR){
664 check_child(child, true);
665 }
666 if (changed[i].ident == child){
667 check_child(child, false);
668 }
669 else if (changed[i].ident == child_conn){
670 int fd = data_in(child);
671 if (-1 != fd){
672 #ifdef __OpenBSD__
673 kused = 3;
674 EV_SET(&ev[2], fd, EVFILT_DEVICE, EV_ADD | EV_ENABLE | EV_CLEAR, NOTE_CHANGE, 0, 0);
675 #endif
676 }
677 }
678 else {
679 struct packet pkg = {
680 .cmd_ch = DISPLAY_CONNECTOR_STATE
681 };
682 write(child_conn, &pkg, sizeof(pkg));
683 }
684 }
685 }
686
687 #else
parent_loop(pid_t child,int netlink)688 static void parent_loop(pid_t child, int netlink)
689 {
690 /*
691 * Should really refactor / move both the TTY management and the inotify
692 * monitoring over here as well to simplify the evdev.c implementation
693 * but more ground work first.
694 */
695
696 /* here is a decent place to actually track the DRM devices that are open
697 * and restore their scanout status so that we will not risk leaving a broken
698 * tty. */
699 int st;
700 check_child(child, false);
701
702 struct pollfd pfd[2] = {
703 {
704 .fd = child_conn,
705 .events = POLLIN | POLLERR | POLLHUP | POLLNVAL
706 },
707 {
708 .fd = netlink,
709 .events = POLLIN | POLLERR | POLLHUP | POLLNVAL
710 }
711 };
712
713 int rv = poll(pfd, netlink == -1 ? 1 : 2, 1000);
714 if (-1 == rv && (errno != EAGAIN && errno != EINTR))
715 check_child(child, true); /* don't go stronger than this */
716
717 if (rv == 0)
718 return;
719
720 if (pfd[0].revents & ~POLLIN)
721 check_child(child, true);
722
723 if (pfd[0].revents & POLLIN)
724 data_in(child);
725
726 /* could add other commands here as well, but what we concern ourselves with
727 * at the moment is only GPU changed events, these match the pattern:
728 * changed@ ... drm/card */
729 if (-1 == netlink || !(pfd[1].revents & POLLIN))
730 return;
731
732 check_netlink(child, netlink);
733 }
734 #endif
735
drop_privileges()736 static bool drop_privileges()
737 {
738 /* in case of suid, drop to user now */
739 uid_t uid = getuid();
740 uid_t euid = geteuid();
741 gid_t gid = getgid();
742
743 if (uid == euid)
744 return true;
745
746 /* no weird suid, drmMaster needs so this would be pointless */
747 if (euid != 0)
748 return false;
749
750 setsid();
751 /* ugly tradeof here, setting the supplementary groups to only the gid
752 * would subtly break certain sudo configurations in terminals spawned
753 * from the normal privileged process, the best?! thing we can do is
754 * likely to filter out the egid out of the groups list and replace
755 * with just the gid -- and yes setgroups is a linux/BSD extension */
756 int ngroups = getgroups(0, NULL);
757 gid_t groups[ngroups];
758 gid_t egid = getegid();
759 if (getgroups(ngroups, groups)){
760 for (size_t i = 0; i < ngroups; i++){
761 if (groups[i] == egid)
762 groups[i] = gid;
763 }
764 setgroups(ngroups, groups);
765 }
766
767 #ifdef __LINUX
768 /* more diligence would take the CAPABILITIES crapola into account as well */
769 if (
770 setegid(gid) == -1 ||
771 setgid(gid) == -1 ||
772 setfsgid(gid) == -1 ||
773 setfsuid(uid) == -1 ||
774 seteuid(uid) == -1 ||
775 setuid(uid) == -1
776 )
777 return false;
778
779 #else /* BSDs */
780 if (
781 setegid(gid) == -1 ||
782 setgid(gid) == -1 ||
783 seteuid(uid) == -1 ||
784 setuid(uid) == -1
785 )
786 return false;
787 #endif
788
789 if (geteuid() != uid || getegid() != gid)
790 return false;
791
792 return true;
793 }
794
795 /*
796 * PARENT SIDE FUNCTIONS, we split even if there is no root- state (i.e. a
797 * system with user permissions on devices) in order to have the same interface
798 * code for hotplug.
799 *
800 * Note that this is slightly hairy due to the fact that if we are root, we
801 * can't be allowed to use the get_config class of functions tas they can
802 * trivially be used to escalate. This affects evdev when it comes to the
803 * 'scandir', which needs to be encoded into the device_list as well then.
804 *
805 * This is primarily a concern when building very custom systems where you
806 * can presumably control uid/gid allocation better anyhow.
807 */
808 static int psock = -1;
platform_device_init()809 void platform_device_init()
810 {
811
812 /*
813 * Signs of these environment variables indicate that we are in a context where
814 * other display servers exist, drop out immediately so that we don't risk
815 * interfering with their execution.
816 */
817 if (getenv("ARCAN_CONNPATH") || getenv("DISPLAY") || getenv("WAYLAND_DISPLAY")){
818 drop_privileges();
819 return;
820 }
821
822 int sockets[2];
823 if (socketpair(AF_LOCAL, SOCK_STREAM, PF_UNSPEC, sockets) == -1)
824 _exit(EXIT_FAILURE);
825
826 /*
827 * The 'low-priv' side of arcan does this as well, but since we want to be
828 * able to restore should that crash, we need to do something about that
829 * here and before fork so we don't race.
830 */
831 #ifdef __FreeBSD__
832 set_tty(STDIN_FILENO, true);
833 #endif
834
835 pid_t pid = fork();
836 if (pid < 0)
837 _exit(EXIT_FAILURE);
838
839 if (pid == 0){
840 /* last thing before dropping privileges, set high priority */
841 arcan_process_title("arcan renderer");
842 setpriority(PRIO_PROCESS, 0, -19);
843
844 close(sockets[1]);
845
846 if (!drop_privileges()){
847 _exit(EXIT_FAILURE);
848 }
849
850 /* HARDENING NOTE:
851 * make sure this socket doesn't get forwarded (would provide device access),
852 * overall this is a fun CTFy attack angle - poke descriptor table to unset
853 * cloexec (this is still at a predictable descriptor value given the startup
854 * chain) and from an exec()ed setting, leverage more comfortably - randomize
855 * dup is needed here */
856 psock = sockets[0];
857 int fl = fcntl(psock, F_GETFD);
858 if (-1 != fl)
859 fcntl(psock, F_SETFD, fl | FD_CLOEXEC);
860
861 /* privsep child can have STDOUT/STDERR, but prevent it from cascading */
862 int flags = fcntl(STDOUT_FILENO, F_GETFD);
863 if (-1 != flags)
864 fcntl(STDOUT_FILENO, F_SETFD, flags | FD_CLOEXEC);
865 flags = fcntl(STDERR_FILENO, F_GETFD);
866 if (-1 != flags)
867 fcntl(STDERR_FILENO, F_SETFD, flags | FD_CLOEXEC);
868 return;
869 }
870
871 #ifdef __LINUX
872 /* bind netlink for display event detection */
873 struct sockaddr_nl sa = {
874 .nl_family = AF_NETLINK,
875 .nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR
876 };
877 int netlink = socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT);
878 if (netlink >= 0){
879 if (bind(netlink, (struct sockaddr*)&sa, sizeof(sa))){
880 close(netlink);
881 netlink = -1;
882 }
883 }
884
885 close(sockets[0]);
886
887 #else
888 int netlink = -1;
889 #endif
890
891 #ifdef __OpenBSD__
892 if (-1 == pledge("stdio drm sendfd proc rpath wpath", NULL)){
893 fprintf(stderr, "couldn't pledge\n");
894 _exit(EXIT_FAILURE);
895 }
896 #endif
897
898 int sigset[] = {
899 SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, SIGFPE,
900 SIGPIPE, SIGALRM, SIGTERM, SIGUSR1, SIGUSR2, SIGCHLD,
901 SIGCONT, SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU};
902 for (size_t i = 0; i < COUNT_OF(sigset); i++)
903 sigaction(sigset[i], &(struct sigaction){.sa_handler = SIG_IGN}, NULL);
904 child_conn = sockets[1];
905
906 arcan_process_title("arcan device control");
907
908 while(true){
909 parent_loop(pid, netlink);
910 }
911 }
912
913 /*
914 * CLIENT SIDE FUNCTIONS
915 */
916 struct packet pkg_queue[1];
917
platform_device_release(const char * const name,int ind)918 void platform_device_release(const char* const name, int ind)
919 {
920 struct packet pkg = {
921 .cmd_ch = RELEASE_DEVICE,
922 .arg = ind
923 };
924
925 snprintf(pkg.path, sizeof(pkg.path), "%s", name);
926 write(psock, &pkg, sizeof(pkg));
927 }
928
platform_device_open(const char * const name,int flags)929 int platform_device_open(const char* const name, int flags)
930 {
931 struct packet pkg = {
932 .cmd_ch = OPEN_DEVICE,
933 .arg = -1
934 };
935
936 snprintf(pkg.path, sizeof(pkg.path), "%s", name);
937 if (-1 == write(psock, &pkg, sizeof(pkg)))
938 return -1;
939
940 while (sizeof(struct packet) == read(psock, &pkg, sizeof(struct packet))){
941 /* there might be other command events that we need to respect as there can be
942 * both explicit open requests and implicit open requests depending on what
943 * kind of device manager that is running on the other side, so we need to
944 * queue here */
945 if (pkg.cmd_ch == OPEN_FAILED)
946 return -1;
947
948 else if (pkg.cmd_ch == OPEN_DEVICE){
949 int fd = arcan_fetchhandle(psock, true);
950 if (-1 == fd)
951 return -1;
952 fcntl(fd, F_SETFD, FD_CLOEXEC);
953 fcntl(fd, F_SETFL, flags);
954 return fd;
955 }
956
957 else if (pkg.cmd_ch == DISPLAY_CONNECTOR_STATE)
958 pkg_queue[0] = pkg;
959
960 assert(pkg.cmd_ch != NEW_INPUT_DEVICE);
961 }
962
963 /*
964 * When we move the inotify- behavior from evdev additional care needs to
965 * be taken here to handle the input device discovery part as the devices
966 * received needs to be queued until the engine is in a state to handle them.
967 * Then we can simply look at the NETLINK socket for the inputn entries
968 * appearing (or use the inotify + folder approach, compile-time option).
969 */
970 return 0;
971 }
972
platform_device_pollfd()973 int platform_device_pollfd()
974 {
975 return psock;
976 }
977
platform_device_poll(char ** identifier)978 int platform_device_poll(char** identifier)
979 {
980 if (pkg_queue[0].cmd_ch == DISPLAY_CONNECTOR_STATE){
981 pkg_queue[0] = (struct packet){};
982 return 2;
983 }
984
985 struct pollfd pfd = {.fd = psock,
986 .events = POLLIN | POLLERR | POLLHUP | POLLNVAL};
987
988 if (poll(&pfd, 1, 0) <= 0)
989 return 0;
990
991 if ((pfd.revents & ~POLLIN)){
992 return -1;
993 }
994
995 /* translate from the visible command format to the internal one */
996 struct packet pkg;
997 while (sizeof(struct packet) == read(psock, &pkg, sizeof(struct packet))){
998 switch(pkg.cmd_ch){
999 case NEW_INPUT_DEVICE:
1000 /* not properly handled right now as we have other hotplug mechanisms in place
1001 * via inotify in the evdev layer, the problem is that we need to cache new
1002 * input names until we get a call where there's an identifier provided */
1003 break;
1004 case DISPLAY_CONNECTOR_STATE:
1005 return 2;
1006 break;
1007 case SYSTEM_STATE_RELEASE:
1008 return 3;
1009 break;
1010 case SYSTEM_STATE_ACQUIRE:
1011 return 4;
1012 break;
1013 case SYSTEM_STATE_TERMINATE:
1014 return 5;
1015 break;
1016 default:
1017 return 0;
1018 break;
1019 }
1020 }
1021
1022 return 0;
1023 }
1024