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