xref: /dragonfly/contrib/dhcpcd/src/privsep-root.c (revision 6a3cbbc2)
1 /* SPDX-License-Identifier: BSD-2-Clause */
2 /*
3  * Privilege Separation for dhcpcd, privileged actioneer
4  * Copyright (c) 2006-2020 Roy Marples <roy@marples.name>
5  * All rights reserved
6 
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/ioctl.h>
30 #include <sys/socket.h>
31 #include <sys/stat.h>
32 #include <sys/time.h>
33 #include <sys/types.h>
34 #include <sys/wait.h>
35 
36 #include <assert.h>
37 #include <errno.h>
38 #include <fcntl.h>
39 #include <pwd.h>
40 #include <signal.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <unistd.h>
44 
45 #include "common.h"
46 #include "dhcpcd.h"
47 #include "eloop.h"
48 #include "if.h"
49 #include "logerr.h"
50 #include "privsep.h"
51 #include "script.h"
52 
53 __CTASSERT(sizeof(ioctl_request_t) <= sizeof(unsigned long));
54 
55 struct psr_error
56 {
57 	ssize_t psr_result;
58 	int psr_errno;
59 	char psr_pad[sizeof(ssize_t) - sizeof(int)];
60 };
61 
62 struct psr_ctx {
63 	struct dhcpcd_ctx *psr_ctx;
64 	struct psr_error psr_error;
65 };
66 
67 static void
68 ps_root_readerrorsig(__unused int sig, void *arg)
69 {
70 	struct dhcpcd_ctx *ctx = arg;
71 
72 	eloop_exit(ctx->ps_eloop, EXIT_FAILURE);
73 }
74 
75 static void
76 ps_root_readerrorcb(void *arg)
77 {
78 	struct psr_ctx *psr_ctx = arg;
79 	struct dhcpcd_ctx *ctx = psr_ctx->psr_ctx;
80 	struct psr_error *psr_error = &psr_ctx->psr_error;
81 	ssize_t len;
82 	int exit_code = EXIT_FAILURE;
83 
84 	len = read(ctx->ps_root_fd, psr_error, sizeof(*psr_error));
85 	if (len == 0 || len == -1) {
86 		logerr(__func__);
87 		psr_error->psr_result = -1;
88 		psr_error->psr_errno = errno;
89 	} else if ((size_t)len < sizeof(*psr_error)) {
90 		logerrx("%s: psr_error truncated", __func__);
91 		psr_error->psr_result = -1;
92 		psr_error->psr_errno = EINVAL;
93 	} else
94 		exit_code = EXIT_SUCCESS;
95 
96 	eloop_exit(ctx->ps_eloop, exit_code);
97 }
98 
99 ssize_t
100 ps_root_readerror(struct dhcpcd_ctx *ctx)
101 {
102 	struct psr_ctx psr_ctx = { .psr_ctx = ctx };
103 
104 	if (eloop_event_add(ctx->ps_eloop, ctx->ps_root_fd,
105 	    ps_root_readerrorcb, &psr_ctx) == -1)
106 	{
107 		logerr(__func__);
108 		return -1;
109 	}
110 
111 	eloop_start(ctx->ps_eloop, &ctx->sigset);
112 
113 	errno = psr_ctx.psr_error.psr_errno;
114 	return psr_ctx.psr_error.psr_result;
115 }
116 
117 static ssize_t
118 ps_root_writeerror(struct dhcpcd_ctx *ctx, ssize_t result)
119 {
120 	struct psr_error psr = {
121 		.psr_result = result,
122 		.psr_errno = errno,
123 	};
124 
125 #ifdef PRIVSEP_DEBUG
126 	logdebugx("%s: result %zd errno %d", __func__, result, errno);
127 #endif
128 
129 	return write(ctx->ps_root_fd, &psr, sizeof(psr));
130 }
131 
132 static ssize_t
133 ps_root_doioctl(unsigned long req, void *data, size_t len)
134 {
135 	int s, err;
136 
137 	s = socket(PF_INET, SOCK_DGRAM, 0);
138 	if (s != -1)
139 #ifdef IOCTL_REQUEST_TYPE
140 	{
141 		ioctl_request_t reqt;
142 
143 		memcpy(&reqt, &req, sizeof(reqt));
144 		err = ioctl(s, reqt, data, len);
145 	}
146 #else
147 		err = ioctl(s, req, data, len);
148 #endif
149 	else
150 		err = -1;
151 	if (s != -1)
152 		close(s);
153 	return err;
154 }
155 
156 static ssize_t
157 ps_root_run_script(struct dhcpcd_ctx *ctx, const void *data, size_t len)
158 {
159 	const char *envbuf = data;
160 	char * const argv[] = { UNCONST(data), NULL };
161 	pid_t pid;
162 	int status;
163 
164 #ifdef PRIVSEP_DEBUG
165 	logdebugx("%s: IN %zu", __func__, len);
166 #endif
167 
168 	if (len == 0)
169 		return 0;
170 
171 	/* Script is the first one, find the environment buffer. */
172 	while (*envbuf != '\0') {
173 		if (len == 0)
174 			return EINVAL;
175 		envbuf++;
176 		len--;
177 	}
178 
179 	if (len != 0) {
180 		envbuf++;
181 		len--;
182 	}
183 
184 #ifdef PRIVSEP_DEBUG
185 	logdebugx("%s: run script: %s", __func__, argv[0]);
186 #endif
187 
188 	if (script_buftoenv(ctx, UNCONST(envbuf), len) == NULL)
189 		return -1;
190 
191 	pid = script_exec(argv, ctx->script_env);
192 	if (pid == -1)
193 		return -1;
194 	/* Wait for the script to finish */
195 	while (waitpid(pid, &status, 0) == -1) {
196 		if (errno != EINTR) {
197 			logerr(__func__);
198 			status = 0;
199 			break;
200 		}
201 	}
202 	return status;
203 }
204 
205 #if defined(__linux__) && !defined(st_mtimespec)
206 #define	st_atimespec  st_atim
207 #define	st_mtimespec  st_mtim
208 #endif
209 ssize_t
210 ps_root_docopychroot(struct dhcpcd_ctx *ctx, const char *file)
211 {
212 
213 	char path[PATH_MAX], buf[BUFSIZ], *slash;
214 	struct stat from_sb, to_sb;
215 	int from_fd, to_fd;
216 	ssize_t rcount, wcount, total;
217 #if defined(BSD) || defined(__linux__)
218 	struct timespec ts[2];
219 #else
220 	struct timeval tv[2];
221 #endif
222 
223 	if (snprintf(path, sizeof(path), "%s/%s",
224 	    ctx->ps_user->pw_dir, file) == -1)
225 		return -1;
226 	if (stat(file, &from_sb) == -1)
227 		return -1;
228 	if (stat(path, &to_sb) == 0) {
229 #if defined(BSD) || defined(__linux__)
230 		if (from_sb.st_mtimespec.tv_sec == to_sb.st_mtimespec.tv_sec &&
231 		    from_sb.st_mtimespec.tv_nsec == to_sb.st_mtimespec.tv_nsec)
232 			return 0;
233 #else
234 		if (from_sb.st_mtime == to_sb.st_mtime)
235 			return 0;
236 #endif
237 	} else {
238 		/* Ensure directory exists */
239 		slash = strrchr(path, '/');
240 		if (slash != NULL) {
241 			*slash = '\0';
242 			ps_mkdir(path);
243 			*slash = '/';
244 		}
245 	}
246 
247 	if (unlink(path) == -1 && errno != ENOENT)
248 		return -1;
249 	if ((from_fd = open(file, O_RDONLY, 0)) == -1)
250 		return -1;
251 	if ((to_fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, 0555)) == -1)
252 		return -1;
253 
254 	total = 0;
255 	while ((rcount = read(from_fd, buf, sizeof(buf))) > 0) {
256 		wcount = write(to_fd, buf, (size_t)rcount);
257 		if (wcount != rcount) {
258 			total = -1;
259 			break;
260 		}
261 		total += wcount;
262 	}
263 
264 #if defined(BSD) || defined(__linux__)
265 	ts[0] = from_sb.st_atimespec;
266 	ts[1] = from_sb.st_mtimespec;
267 	if (futimens(to_fd, ts) == -1)
268 		total = -1;
269 #else
270 	tv[0].tv_sec = from_sb.st_atime;
271 	tv[0].tv_usec = 0;
272 	tv[1].tv_sec = from_sb.st_mtime;
273 	tv[1].tv_usec = 0;
274 	if (futimes(to_fd, tv) == -1)
275 		total = -1;
276 #endif
277 	close(from_fd);
278 	close(to_fd);
279 
280 	return total;
281 }
282 
283 static ssize_t
284 ps_root_dofileop(struct dhcpcd_ctx *ctx, void *data, size_t len, uint8_t op)
285 {
286 	char *path = data;
287 	size_t plen;
288 
289 	if (len < sizeof(plen)) {
290 		errno = EINVAL;
291 		return -1;
292 	}
293 
294 	memcpy(&plen, path, sizeof(plen));
295 	path += sizeof(plen);
296 	if (sizeof(plen) + plen > len) {
297 		errno = EINVAL;
298 		return -1;
299 	}
300 
301 	switch(op) {
302 	case PS_COPY:
303 		return ps_root_docopychroot(ctx, path);
304 	case PS_UNLINK:
305 		return (ssize_t)unlink(path);
306 	default:
307 		errno = ENOTSUP;
308 		return -1;
309 	}
310 }
311 
312 static ssize_t
313 ps_root_recvmsgcb(void *arg, struct ps_msghdr *psm, struct msghdr *msg)
314 {
315 	struct dhcpcd_ctx *ctx = arg;
316 	uint8_t cmd;
317 	struct ps_process *psp;
318 	struct iovec *iov = msg->msg_iov;
319 	void *data = iov->iov_base;
320 	size_t len = iov->iov_len;
321 	ssize_t err;
322 
323 	cmd = (uint8_t)(psm->ps_cmd & ~(PS_START | PS_STOP | PS_DELETE));
324 	psp = ps_findprocess(ctx, &psm->ps_id);
325 
326 #ifdef PRIVSEP_DEBUG
327 	logerrx("%s: IN cmd %x, psp %p", __func__, psm->ps_cmd, psp);
328 #endif
329 
330 	if ((!(psm->ps_cmd & PS_START) || cmd == PS_BPF_ARP_ADDR) &&
331 	    psp != NULL)
332 	{
333 		if (psm->ps_cmd & PS_STOP) {
334 			int ret = ps_dostop(ctx, &psp->psp_pid, &psp->psp_fd);
335 
336 			ps_freeprocess(psp);
337 			return ret;
338 		}
339 		return ps_sendpsmmsg(ctx, psp->psp_fd, psm, msg);
340 	}
341 
342 	if (psm->ps_cmd & (PS_STOP | PS_DELETE) && psp == NULL)
343 		return 0;
344 
345 	/* All these should just be PS_START */
346 	switch (cmd) {
347 #ifdef INET
348 #ifdef ARP
349 	case PS_BPF_ARP:	/* FALLTHROUGH */
350 #endif
351 	case PS_BPF_BOOTP:
352 		return ps_bpf_cmd(ctx, psm, msg);
353 #endif
354 #ifdef INET
355 	case PS_BOOTP:
356 		return ps_inet_cmd(ctx, psm, msg);
357 #endif
358 #ifdef INET6
359 #ifdef DHCP6
360 	case PS_DHCP6:	/* FALLTHROUGH */
361 #endif
362 	case PS_ND:
363 		return ps_inet_cmd(ctx, psm, msg);
364 #endif
365 	default:
366 		break;
367 	}
368 
369 	assert(msg->msg_iovlen == 1);
370 
371 	/* Reset errno */
372 	errno = 0;
373 
374 	switch (psm->ps_cmd) {
375 	case PS_IOCTL:
376 		err = ps_root_doioctl(psm->ps_flags, data, len);
377 		break;
378 	case PS_SCRIPT:
379 		err = ps_root_run_script(ctx, data, len);
380 		break;
381 	case PS_COPY:	/* FALLTHROUGH */
382 	case PS_UNLINK:
383 		err = ps_root_dofileop(ctx, data, len, psm->ps_cmd);
384 		break;
385 	default:
386 		err = ps_root_os(psm, msg);
387 		break;
388 	}
389 
390 	return ps_root_writeerror(ctx, err);
391 }
392 
393 /* Receive from state engine, do an action. */
394 static void
395 ps_root_recvmsg(void *arg)
396 {
397 	struct dhcpcd_ctx *ctx = arg;
398 
399 	if (ps_recvpsmsg(ctx, ctx->ps_root_fd, ps_root_recvmsgcb, ctx) == -1 &&
400 	    errno != ECONNRESET)
401 		logerr(__func__);
402 }
403 
404 static int
405 ps_root_startcb(void *arg)
406 {
407 	struct dhcpcd_ctx *ctx = arg;
408 
409 	setproctitle("[privileged actioneer]");
410 	ctx->ps_root_pid = getpid();
411 	return 0;
412 }
413 
414 static void
415 ps_root_signalcb(int sig, void *arg)
416 {
417 	struct dhcpcd_ctx *ctx = arg;
418 
419 	/* Ignore SIGINT, respect PS_STOP command or SIGTERM. */
420 	if (sig == SIGINT)
421 		return;
422 
423 	logerrx("process %d unexpectedly terminating on signal %d",
424 	    getpid(), sig);
425 	if (ctx->ps_root_pid == getpid()) {
426 		shutdown(ctx->ps_root_fd, SHUT_RDWR);
427 		shutdown(ctx->ps_data_fd, SHUT_RDWR);
428 	}
429 	eloop_exit(ctx->eloop, sig == SIGTERM ? EXIT_SUCCESS : EXIT_FAILURE);
430 }
431 
432 static ssize_t
433 ps_root_dispatchcb(void *arg, struct ps_msghdr *psm, struct msghdr *msg)
434 {
435 	struct dhcpcd_ctx *ctx = arg;
436 	ssize_t err;
437 
438 #ifdef INET
439 	err = ps_bpf_dispatch(ctx, psm, msg);
440 	if (err == -1 && errno == ENOTSUP)
441 #endif
442 		err = ps_inet_dispatch(ctx, psm, msg);
443 	return err;
444 }
445 
446 static void
447 ps_root_dispatch(void *arg)
448 {
449 	struct dhcpcd_ctx *ctx = arg;
450 
451 	if (ps_recvpsmsg(ctx, ctx->ps_data_fd, ps_root_dispatchcb, ctx) == -1)
452 		logerr(__func__);
453 }
454 
455 pid_t
456 ps_root_start(struct dhcpcd_ctx *ctx)
457 {
458 	int fd[2];
459 	pid_t pid;
460 
461 #define	SOCK_CXNB	SOCK_CLOEXEC | SOCK_NONBLOCK
462 	if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CXNB, 0, fd) == -1)
463 		return -1;
464 
465 	pid = ps_dostart(ctx, &ctx->ps_root_pid, &ctx->ps_root_fd,
466 	    ps_root_recvmsg, NULL, ctx,
467 	    ps_root_startcb, ps_root_signalcb, 0);
468 
469 	if (pid == 0) {
470 		ctx->ps_data_fd = fd[1];
471 		close(fd[0]);
472 		return 0;
473 	} else if (pid == -1)
474 		return -1;
475 
476 	ctx->ps_data_fd = fd[0];
477 	close(fd[1]);
478 	if (eloop_event_add(ctx->eloop, ctx->ps_data_fd,
479 	    ps_root_dispatch, ctx) == -1)
480 		logerr(__func__);
481 
482 	if ((ctx->ps_eloop = eloop_new()) == NULL) {
483 		logerr(__func__);
484 		return -1;
485 	}
486 
487 	if (eloop_signal_set_cb(ctx->ps_eloop,
488 	    dhcpcd_signals, dhcpcd_signals_len,
489 	    ps_root_readerrorsig, ctx) == -1)
490 	{
491 		logerr(__func__);
492 		return -1;
493 	}
494 	return pid;
495 }
496 
497 int
498 ps_root_stop(struct dhcpcd_ctx *ctx)
499 {
500 
501 	return ps_dostop(ctx, &ctx->ps_root_pid, &ctx->ps_root_fd);
502 }
503 
504 ssize_t
505 ps_root_script(const struct interface *ifp, const void *data, size_t len)
506 {
507 	char buf[PS_BUFLEN], *p = buf;
508 	size_t blen = PS_BUFLEN, slen = strlen(ifp->options->script) + 1;
509 
510 #ifdef PRIVSEP_DEBUG
511 	logdebugx("%s: sending script: %zu %s len %zu",
512 	    __func__, slen, ifp->options->script, len);
513 #endif
514 
515 	if (slen > blen) {
516 		errno = ENOBUFS;
517 		return -1;
518 	}
519 	memcpy(p, ifp->options->script, slen);
520 	p += slen;
521 	blen -= slen;
522 
523 	if (len > blen) {
524 		errno = ENOBUFS;
525 		return -1;
526 	}
527 	memcpy(p, data, len);
528 
529 #ifdef PRIVSEP_DEBUG
530 	logdebugx("%s: sending script data: %zu", __func__, slen + len);
531 #endif
532 
533 	if (ps_sendcmd(ifp->ctx, ifp->ctx->ps_root_fd, PS_SCRIPT, 0,
534 	    buf, slen + len) == -1)
535 		return -1;
536 
537 	return ps_root_readerror(ifp->ctx);
538 }
539 
540 ssize_t
541 ps_root_ioctl(struct dhcpcd_ctx *ctx, ioctl_request_t req, void *data,
542     size_t len)
543 {
544 #ifdef IOCTL_REQUEST_TYPE
545 	unsigned long ulreq = 0;
546 
547 	memcpy(&ulreq, &req, sizeof(req));
548 	if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IOCTL, ulreq, data, len) == -1)
549 		return -1;
550 #else
551 	if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IOCTL, req, data, len) == -1)
552 		return -1;
553 #endif
554 	return ps_root_readerror(ctx);
555 }
556 
557 static ssize_t
558 ps_root_fileop(struct dhcpcd_ctx *ctx, const char *path, uint8_t op)
559 {
560 	char buf[PATH_MAX], *p = buf;
561 	size_t plen = strlen(path) + 1;
562 	size_t len = sizeof(plen) + plen;
563 
564 	if (len > sizeof(buf)) {
565 		errno = ENOBUFS;
566 		return -1;
567 	}
568 
569 	memcpy(p, &plen, sizeof(plen));
570 	p += sizeof(plen);
571 	memcpy(p, path, plen);
572 
573 	if (ps_sendcmd(ctx, ctx->ps_root_fd, op, 0, buf, len) == -1)
574 		return -1;
575 	return ps_root_readerror(ctx);
576 }
577 
578 
579 ssize_t
580 ps_root_copychroot(struct dhcpcd_ctx *ctx, const char *path)
581 {
582 
583 	return ps_root_fileop(ctx, path, PS_COPY);
584 }
585 
586 ssize_t
587 ps_root_unlink(struct dhcpcd_ctx *ctx, const char *path)
588 {
589 
590 	return ps_root_fileop(ctx, path, PS_UNLINK);
591 }
592