1/*	$OpenBSD: privsep.c,v 1.35 2021/07/12 15:09:19 beck Exp $	*/
2
3/*
4 * Copyright (c) 2003 Can Erkin Acar
5 * Copyright (c) 2003 Anil Madhavapeddy <anil@recoil.org>
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19#include <sys/types.h>
20#include <sys/time.h>
21#include <sys/socket.h>
22#include <sys/ioctl.h>
23
24#include <net/if.h>
25#include <net/bpf.h>
26
27#include <err.h>
28#include <errno.h>
29#include <fcntl.h>
30#include <limits.h>
31#include <pcap.h>
32#include <pcap-int.h>
33#include <pwd.h>
34#include <signal.h>
35#include <stdio.h>
36#include <stdlib.h>
37#include <string.h>
38#include <syslog.h>
39#include <unistd.h>
40#include <netdb.h>
41#include <resolv.h>
42#include "pflogd.h"
43
44enum cmd_types {
45	PRIV_INIT_PCAP,		/* init pcap fdpass bpf */
46	PRIV_SET_SNAPLEN,	/* set the snaplength */
47	PRIV_OPEN_LOG,		/* open logfile for appending */
48};
49
50static int priv_fd = -1;
51static volatile pid_t child_pid = -1;
52
53static void sig_pass_to_chld(int);
54static int  may_read(int, void *, size_t);
55static void must_read(int, void *, size_t);
56static void must_write(int, void *, size_t);
57static int  set_snaplen(int snap);
58
59extern char *filename;
60extern char *interface;
61extern char errbuf[PCAP_ERRBUF_SIZE];
62extern pcap_t *hpcap;
63
64/* based on syslogd privsep */
65void
66priv_init(int Pflag, int argc, char *argv[])
67{
68	int i, fd = -1, bpfd = -1, nargc, socks[2], cmd;
69	int snaplen, ret, olderrno;
70	struct passwd *pw;
71	char **nargv;
72	unsigned int buflen;
73
74	pw = getpwnam("_pflogd");
75	if (pw == NULL)
76		errx(1, "unknown user _pflogd");
77	endpwent();
78
79	if (Pflag) {
80		gid_t gidset[1];
81
82		/* Child - drop privileges and return */
83		if (chroot(pw->pw_dir) != 0)
84			err(1, "unable to chroot");
85		if (chdir("/") != 0)
86			err(1, "unable to chdir");
87
88		gidset[0] = pw->pw_gid;
89		if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1)
90			err(1, "setresgid() failed");
91		if (setgroups(1, gidset) == -1)
92			err(1, "setgroups() failed");
93		if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
94			err(1, "setresuid() failed");
95		priv_fd = 3;
96		return;
97	}
98
99	/* Create sockets */
100	if (socketpair(AF_LOCAL, SOCK_STREAM, PF_UNSPEC, socks) == -1)
101		err(1, "socketpair() failed");
102
103	child_pid = fork();
104	if (child_pid == -1)
105		err(1, "fork() failed");
106
107	if (!child_pid) {
108		close(socks[0]);
109		if (dup2(socks[1], 3) == -1)
110			err(1, "dup2 unpriv sock failed");
111		close(socks[1]);
112		if ((nargv = reallocarray(NULL, argc + 2,
113		    sizeof(char *))) == NULL)
114			err(1, "alloc unpriv argv failed");
115		nargc = 0;
116		nargv[nargc++] = argv[0];
117		nargv[nargc++] = "-P";
118		for (i = 1; i < argc; i++)
119			nargv[nargc++] = argv[i];
120		nargv[nargc] = NULL;
121		execvp(nargv[0], nargv);
122		err(1, "exec unpriv '%s' failed", nargv[0]);
123	}
124	close(socks[1]);
125
126	/* Father */
127	/* Pass ALRM/TERM/HUP/INT/QUIT through to child */
128	signal(SIGALRM, sig_pass_to_chld);
129	signal(SIGTERM, sig_pass_to_chld);
130	signal(SIGHUP,  sig_pass_to_chld);
131	signal(SIGINT,  sig_pass_to_chld);
132	signal(SIGQUIT, sig_pass_to_chld);
133
134	setproctitle("[priv]");
135
136	if (unveil(_PATH_RESCONF, "r") == -1)
137		err(1, "unveil %s", _PATH_RESCONF);
138	if (unveil(_PATH_HOSTS, "r") == -1)
139		err(1, "unveil %s", _PATH_HOSTS);
140	if (unveil(_PATH_SERVICES, "r") == -1)
141		err(1, "unveil %s", _PATH_SERVICES);
142	if (unveil("/dev/bpf", "r") == -1)
143		err(1, "unveil /dev/bpf");
144	if (unveil(filename, "rwc") == -1)
145		err(1, "unveil %s", filename);
146	if (unveil(NULL, NULL) == -1)
147		err(1, "unveil");
148
149#if 0
150	/* This needs to do bpf ioctl */
151BROKEN	if (pledge("stdio rpath wpath cpath sendfd proc bpf", NULL) == -1)
152		err(1, "pledge");
153#endif
154
155	while (1) {
156		if (may_read(socks[0], &cmd, sizeof(int)))
157			break;
158		switch (cmd) {
159		case PRIV_INIT_PCAP:
160			logmsg(LOG_DEBUG,
161			    "[priv]: msg PRIV_INIT_PCAP received");
162			/* initialize pcap */
163			if (hpcap != NULL || init_pcap()) {
164				logmsg(LOG_ERR, "[priv]: Exiting, init failed");
165				_exit(1);
166			}
167			buflen = hpcap->bufsize; /* BIOCGBLEN for unpriv proc */
168			must_write(socks[0], &buflen, sizeof(unsigned int));
169			fd = pcap_fileno(hpcap);
170			send_fd(socks[0], fd);
171			if (fd < 0) {
172				logmsg(LOG_ERR, "[priv]: Exiting, init failed");
173				_exit(1);
174			}
175			break;
176
177		case PRIV_SET_SNAPLEN:
178			logmsg(LOG_DEBUG,
179			    "[priv]: msg PRIV_SET_SNAPLENGTH received");
180			must_read(socks[0], &snaplen, sizeof(int));
181
182			ret = set_snaplen(snaplen);
183			if (ret) {
184				logmsg(LOG_NOTICE,
185				   "[priv]: set_snaplen failed for snaplen %d",
186				   snaplen);
187			}
188
189			must_write(socks[0], &ret, sizeof(int));
190			break;
191
192		case PRIV_OPEN_LOG:
193			logmsg(LOG_DEBUG,
194			    "[priv]: msg PRIV_OPEN_LOG received");
195			/* create or append logs but do not follow symlinks */
196			if (bpfd != -1) {
197				close(bpfd);
198				bpfd = -1;
199			}
200			bpfd = open(filename,
201			    O_RDWR|O_CREAT|O_APPEND|O_NONBLOCK|O_NOFOLLOW,
202			    0600);
203			olderrno = errno;
204			send_fd(socks[0], bpfd);
205			if (bpfd == -1)
206				logmsg(LOG_NOTICE,
207				    "[priv]: failed to open %s: %s",
208				    filename, strerror(olderrno));
209			break;
210
211		default:
212			logmsg(LOG_ERR, "[priv]: unknown command %d", cmd);
213			_exit(1);
214			/* NOTREACHED */
215		}
216	}
217
218	exit(1);
219}
220
221/* this is called from parent */
222static int
223set_snaplen(int snap)
224{
225	if (hpcap == NULL)
226		return (1);
227
228	hpcap->snapshot = snap;
229	set_pcap_filter();
230
231	return 0;
232}
233
234/* receive bpf fd from privileged process using fdpass and init pcap */
235int
236priv_init_pcap(int snaplen)
237{
238	int cmd, fd;
239	unsigned int buflen;
240
241	if (priv_fd < 0)
242		errx(1, "%s: called from privileged portion", __func__);
243
244	cmd = PRIV_INIT_PCAP;
245
246	must_write(priv_fd, &cmd, sizeof(int));
247	must_read(priv_fd, &buflen, sizeof(unsigned int));
248	fd = receive_fd(priv_fd);
249	if (fd < 0)
250		return (-1);
251
252	/* XXX temporary until pcap_open_live_fd API */
253	hpcap = pcap_create(interface, errbuf);
254	if (hpcap == NULL)
255		return (-1);
256
257	/* XXX copies from pcap_open_live/pcap_activate */
258	hpcap->fd = fd;
259	pcap_set_snaplen(hpcap, snaplen);
260	pcap_set_promisc(hpcap, 1);
261	pcap_set_timeout(hpcap, PCAP_TO_MS);
262	hpcap->oldstyle = 1;
263	hpcap->linktype = DLT_PFLOG;
264	hpcap->bufsize = buflen; /* XXX bpf BIOCGBLEN */
265	hpcap->buffer = malloc(hpcap->bufsize);
266	if (hpcap->buffer == NULL)
267		return (-1);
268	hpcap->activated = 1;
269
270	return (0);
271}
272
273/*
274 * send the snaplength to privileged process
275 */
276int
277priv_set_snaplen(int snaplen)
278{
279	int cmd, ret;
280
281	if (priv_fd < 0)
282		errx(1, "%s: called from privileged portion", __func__);
283
284	cmd = PRIV_SET_SNAPLEN;
285
286	must_write(priv_fd, &cmd, sizeof(int));
287	must_write(priv_fd, &snaplen, sizeof(int));
288
289	must_read(priv_fd, &ret, sizeof(int));
290
291	/* also set hpcap->snapshot in child */
292	if (ret == 0)
293		hpcap->snapshot = snaplen;
294
295	return (ret);
296}
297
298/* Open log-file */
299int
300priv_open_log(void)
301{
302	int cmd, fd;
303
304	if (priv_fd < 0)
305		errx(1, "%s: called from privileged portion", __func__);
306
307	cmd = PRIV_OPEN_LOG;
308	must_write(priv_fd, &cmd, sizeof(int));
309	fd = receive_fd(priv_fd);
310
311	return (fd);
312}
313
314/* If priv parent gets a TERM or HUP, pass it through to child instead */
315static void
316sig_pass_to_chld(int sig)
317{
318	int oerrno = errno;
319
320	if (child_pid != -1)
321		kill(child_pid, sig);
322	errno = oerrno;
323}
324
325/* Read all data or return 1 for error.  */
326static int
327may_read(int fd, void *buf, size_t n)
328{
329	char *s = buf;
330	ssize_t res, pos = 0;
331
332	while (n > pos) {
333		res = read(fd, s + pos, n - pos);
334		switch (res) {
335		case -1:
336			if (errno == EINTR || errno == EAGAIN)
337				continue;
338		case 0:
339			return (1);
340		default:
341			pos += res;
342		}
343	}
344	return (0);
345}
346
347/* Read data with the assertion that it all must come through, or
348 * else abort the process.  Based on atomicio() from openssh. */
349static void
350must_read(int fd, void *buf, size_t n)
351{
352	char *s = buf;
353	ssize_t res, pos = 0;
354
355	while (n > pos) {
356		res = read(fd, s + pos, n - pos);
357		switch (res) {
358		case -1:
359			if (errno == EINTR || errno == EAGAIN)
360				continue;
361		case 0:
362			_exit(0);
363		default:
364			pos += res;
365		}
366	}
367}
368
369/* Write data with the assertion that it all has to be written, or
370 * else abort the process.  Based on atomicio() from openssh. */
371static void
372must_write(int fd, void *buf, size_t n)
373{
374	char *s = buf;
375	ssize_t res, pos = 0;
376
377	while (n > pos) {
378		res = write(fd, s + pos, n - pos);
379		switch (res) {
380		case -1:
381			if (errno == EINTR || errno == EAGAIN)
382				continue;
383		case 0:
384			_exit(0);
385		default:
386			pos += res;
387		}
388	}
389}
390