1 |
|
|
/* $OpenBSD: privsep.c,v 1.67 2017/04/05 11:31:45 bluhm Exp $ */ |
2 |
|
|
|
3 |
|
|
/* |
4 |
|
|
* Copyright (c) 2003 Anil Madhavapeddy <anil@recoil.org> |
5 |
|
|
* Copyright (c) 2016 Alexander Bluhm <bluhm@openbsd.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 |
|
|
|
20 |
|
|
#include <sys/queue.h> |
21 |
|
|
#include <sys/stat.h> |
22 |
|
|
#include <sys/wait.h> |
23 |
|
|
|
24 |
|
|
#include <err.h> |
25 |
|
|
#include <errno.h> |
26 |
|
|
#include <fcntl.h> |
27 |
|
|
#include <limits.h> |
28 |
|
|
#include <netdb.h> |
29 |
|
|
#include <paths.h> |
30 |
|
|
#include <pwd.h> |
31 |
|
|
#include <signal.h> |
32 |
|
|
#include <stdio.h> |
33 |
|
|
#include <stdlib.h> |
34 |
|
|
#include <string.h> |
35 |
|
|
#include <unistd.h> |
36 |
|
|
#include <utmp.h> |
37 |
|
|
|
38 |
|
|
#include "log.h" |
39 |
|
|
#include "syslogd.h" |
40 |
|
|
|
41 |
|
|
/* |
42 |
|
|
* syslogd can only go forward in these states; each state should represent |
43 |
|
|
* less privilege. After STATE_INIT, the child is allowed to parse its |
44 |
|
|
* config file once, and communicate the information regarding what logfiles |
45 |
|
|
* it needs access to back to the parent. When that is done, it sends a |
46 |
|
|
* message to the priv parent revoking this access, moving to STATE_RUNNING. |
47 |
|
|
* In this state, any log-files not in the access list are rejected. |
48 |
|
|
* |
49 |
|
|
* This allows a HUP signal to the child to reopen its log files, and |
50 |
|
|
* the config file to be parsed if it hasn't been changed (this is still |
51 |
|
|
* useful to force resolution of remote syslog servers again). |
52 |
|
|
* If the config file has been modified, then the child dies, and |
53 |
|
|
* the priv parent restarts itself. |
54 |
|
|
*/ |
55 |
|
|
enum priv_state { |
56 |
|
|
STATE_INIT, /* just started up */ |
57 |
|
|
STATE_CONFIG, /* parsing config file for first time */ |
58 |
|
|
STATE_RUNNING, /* running and accepting network traffic */ |
59 |
|
|
STATE_QUIT /* shutting down */ |
60 |
|
|
}; |
61 |
|
|
|
62 |
|
|
enum cmd_types { |
63 |
|
|
PRIV_OPEN_TTY, /* open terminal or console device */ |
64 |
|
|
PRIV_OPEN_LOG, /* open logfile for appending */ |
65 |
|
|
PRIV_OPEN_PIPE, /* fork & exec child that gets logs on stdin */ |
66 |
|
|
PRIV_OPEN_UTMP, /* open utmp for reading only */ |
67 |
|
|
PRIV_OPEN_CONFIG, /* open config file for reading only */ |
68 |
|
|
PRIV_CONFIG_MODIFIED, /* check if config file has been modified */ |
69 |
|
|
PRIV_GETADDRINFO, /* resolve host/service names */ |
70 |
|
|
PRIV_GETNAMEINFO, /* resolve numeric address into hostname */ |
71 |
|
|
PRIV_DONE_CONFIG_PARSE /* signal that initial config parse is done */ |
72 |
|
|
}; |
73 |
|
|
|
74 |
|
|
static int priv_fd = -1; |
75 |
|
|
static volatile pid_t child_pid = -1; |
76 |
|
|
static volatile sig_atomic_t cur_state = STATE_INIT; |
77 |
|
|
|
78 |
|
|
/* Queue for the allowed logfiles */ |
79 |
|
|
struct logname { |
80 |
|
|
char path[PATH_MAX]; |
81 |
|
|
TAILQ_ENTRY(logname) next; |
82 |
|
|
}; |
83 |
|
|
static TAILQ_HEAD(, logname) lognames; |
84 |
|
|
|
85 |
|
|
static void check_log_name(char *, size_t); |
86 |
|
|
static int open_file(char *); |
87 |
|
|
static int open_pipe(char *); |
88 |
|
|
static void check_tty_name(char *, size_t); |
89 |
|
|
static void increase_state(int); |
90 |
|
|
static void sig_pass_to_chld(int); |
91 |
|
|
static void sig_got_chld(int); |
92 |
|
|
static void must_read(int, void *, size_t); |
93 |
|
|
static void must_write(int, void *, size_t); |
94 |
|
|
static int may_read(int, void *, size_t); |
95 |
|
|
|
96 |
|
|
void |
97 |
|
|
priv_init(int lockfd, int nullfd, int argc, char *argv[]) |
98 |
|
|
{ |
99 |
|
|
int i, socks[2]; |
100 |
|
|
struct passwd *pw; |
101 |
|
|
char *execpath, childnum[11], **privargv; |
102 |
|
|
|
103 |
|
|
/* Create sockets */ |
104 |
|
|
if (socketpair(AF_LOCAL, SOCK_STREAM, PF_UNSPEC, socks) == -1) |
105 |
|
|
err(1, "socketpair() failed"); |
106 |
|
|
|
107 |
|
|
pw = getpwnam("_syslogd"); |
108 |
|
|
if (pw == NULL) |
109 |
|
|
errx(1, "unknown user _syslogd"); |
110 |
|
|
|
111 |
|
|
child_pid = fork(); |
112 |
|
|
if (child_pid < 0) |
113 |
|
|
err(1, "fork() failed"); |
114 |
|
|
|
115 |
|
|
if (!child_pid) { |
116 |
|
|
/* Child - drop privileges and return */ |
117 |
|
|
if (chroot(pw->pw_dir) != 0) |
118 |
|
|
err(1, "chroot %s", pw->pw_dir); |
119 |
|
|
if (chdir("/") != 0) |
120 |
|
|
err(1, "chdir %s", pw->pw_dir); |
121 |
|
|
|
122 |
|
|
if (setgroups(1, &pw->pw_gid) == -1) |
123 |
|
|
err(1, "setgroups() failed"); |
124 |
|
|
if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1) |
125 |
|
|
err(1, "setresgid() failed"); |
126 |
|
|
if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1) |
127 |
|
|
err(1, "setresuid() failed"); |
128 |
|
|
close(socks[0]); |
129 |
|
|
priv_fd = socks[1]; |
130 |
|
|
return; |
131 |
|
|
} |
132 |
|
|
close(socks[1]); |
133 |
|
|
|
134 |
|
|
if (strchr(argv[0], '/') == NULL) |
135 |
|
|
execpath = argv[0]; |
136 |
|
|
else if ((execpath = realpath(argv[0], NULL)) == NULL) |
137 |
|
|
err(1, "realpath %s", argv[0]); |
138 |
|
|
if (chdir("/") != 0) |
139 |
|
|
err(1, "chdir /"); |
140 |
|
|
|
141 |
|
|
if (!Debug) { |
142 |
|
|
close(lockfd); |
143 |
|
|
dup2(nullfd, STDIN_FILENO); |
144 |
|
|
dup2(nullfd, STDOUT_FILENO); |
145 |
|
|
dup2(nullfd, STDERR_FILENO); |
146 |
|
|
} |
147 |
|
|
if (nullfd > 2) |
148 |
|
|
close(nullfd); |
149 |
|
|
|
150 |
|
|
if (dup3(socks[0], 3, 0) == -1) |
151 |
|
|
err(1, "dup3 priv sock failed"); |
152 |
|
|
if (closefrom(4) == -1) |
153 |
|
|
err(1, "closefrom 4 failed"); |
154 |
|
|
|
155 |
|
|
snprintf(childnum, sizeof(childnum), "%d", child_pid); |
156 |
|
|
if ((privargv = reallocarray(NULL, argc + 3, sizeof(char *))) == NULL) |
157 |
|
|
err(1, "alloc priv argv failed"); |
158 |
|
|
privargv[0] = execpath; |
159 |
|
|
for (i = 1; i < argc; i++) |
160 |
|
|
privargv[i] = argv[i]; |
161 |
|
|
privargv[i++] = "-P"; |
162 |
|
|
privargv[i++] = childnum; |
163 |
|
|
privargv[i++] = NULL; |
164 |
|
|
execvp(privargv[0], privargv); |
165 |
|
|
err(1, "exec priv '%s' failed", privargv[0]); |
166 |
|
|
} |
167 |
|
|
|
168 |
|
|
__dead void |
169 |
|
|
priv_exec(char *conf, int numeric, int child, int argc, char *argv[]) |
170 |
|
|
{ |
171 |
|
1748 |
int i, fd, sock, cmd, addr_len, result, restart; |
172 |
|
874 |
size_t path_len, protoname_len, hostname_len, servname_len; |
173 |
|
874 |
char path[PATH_MAX], protoname[5]; |
174 |
|
874 |
char hostname[NI_MAXHOST], servname[NI_MAXSERV]; |
175 |
|
874 |
struct sockaddr_storage addr; |
176 |
|
874 |
struct stat cf_info, cf_stat; |
177 |
|
874 |
struct addrinfo hints, *res0; |
178 |
|
874 |
struct sigaction sa; |
179 |
|
874 |
sigset_t sigmask; |
180 |
|
|
|
181 |
✗✓ |
1748 |
if (pledge("stdio rpath wpath flock cpath dns getpw sendfd id proc exec", |
182 |
|
874 |
NULL) == -1) |
183 |
|
|
err(1, "pledge priv"); |
184 |
|
|
|
185 |
✓✗✗✓
|
1748 |
if (argc <= 2 || strcmp("-P", argv[argc - 2]) != 0) |
186 |
|
|
errx(1, "exec without priv"); |
187 |
|
874 |
argv[argc -= 2] = NULL; |
188 |
|
|
|
189 |
|
|
sock = 3; |
190 |
|
874 |
closefrom(4); |
191 |
|
|
|
192 |
|
874 |
child_pid = child; |
193 |
|
|
|
194 |
|
874 |
memset(&sa, 0, sizeof(sa)); |
195 |
|
874 |
sigemptyset(&sa.sa_mask); |
196 |
|
874 |
sa.sa_flags = SA_RESTART; |
197 |
|
874 |
sa.sa_handler = SIG_DFL; |
198 |
✓✓ |
57684 |
for (i = 1; i < _NSIG; i++) |
199 |
|
27968 |
sigaction(i, &sa, NULL); |
200 |
|
|
|
201 |
|
|
/* Pass TERM/HUP/INT/QUIT through to child, and accept CHLD */ |
202 |
|
874 |
sa.sa_handler = sig_pass_to_chld; |
203 |
|
874 |
sigaction(SIGTERM, &sa, NULL); |
204 |
|
874 |
sigaction(SIGHUP, &sa, NULL); |
205 |
|
874 |
sigaction(SIGINT, &sa, NULL); |
206 |
|
874 |
sigaction(SIGQUIT, &sa, NULL); |
207 |
|
874 |
sa.sa_handler = sig_got_chld; |
208 |
|
874 |
sa.sa_flags |= SA_NOCLDSTOP; |
209 |
|
874 |
sigaction(SIGCHLD, &sa, NULL); |
210 |
|
|
|
211 |
|
874 |
setproctitle("[priv]"); |
212 |
|
874 |
log_debug("[priv]: fork+exec done"); |
213 |
|
|
|
214 |
|
874 |
sigemptyset(&sigmask); |
215 |
✗✓ |
874 |
if (sigprocmask(SIG_SETMASK, &sigmask, NULL) == -1) |
216 |
|
|
err(1, "sigprocmask priv"); |
217 |
|
|
|
218 |
✗✓ |
874 |
if (stat(conf, &cf_info) < 0) |
219 |
|
|
err(1, "stat config file failed"); |
220 |
|
|
|
221 |
|
874 |
TAILQ_INIT(&lognames); |
222 |
|
874 |
increase_state(STATE_CONFIG); |
223 |
|
|
restart = 0; |
224 |
|
|
|
225 |
✓✓ |
48961 |
while (cur_state < STATE_QUIT) { |
226 |
✓✓ |
48065 |
if (may_read(sock, &cmd, sizeof(int))) |
227 |
|
|
break; |
228 |
✓✗✓✓ ✓✓✓✓ ✓✗ |
47213 |
switch (cmd) { |
229 |
|
|
case PRIV_OPEN_TTY: |
230 |
|
20124 |
log_debug("[priv]: msg PRIV_OPEN_TTY received"); |
231 |
|
|
/* Expecting: length, path */ |
232 |
|
20124 |
must_read(sock, &path_len, sizeof(size_t)); |
233 |
✗✓ |
20124 |
if (path_len == 0 || path_len > sizeof(path)) |
234 |
|
|
_exit(1); |
235 |
|
20124 |
must_read(sock, &path, path_len); |
236 |
|
20124 |
path[path_len - 1] = '\0'; |
237 |
|
20124 |
check_tty_name(path, sizeof(path)); |
238 |
|
20124 |
fd = open(path, O_WRONLY|O_NONBLOCK, 0); |
239 |
|
20124 |
send_fd(sock, fd); |
240 |
✗✓ |
20124 |
if (fd < 0) |
241 |
|
|
warnx("priv_open_tty failed"); |
242 |
|
|
else |
243 |
|
20124 |
close(fd); |
244 |
|
|
break; |
245 |
|
|
|
246 |
|
|
case PRIV_OPEN_LOG: |
247 |
|
|
case PRIV_OPEN_PIPE: |
248 |
|
2665 |
log_debug("[priv]: msg PRIV_OPEN_%s received", |
249 |
|
2665 |
cmd == PRIV_OPEN_PIPE ? "PIPE" : "LOG"); |
250 |
|
|
/* Expecting: length, path */ |
251 |
|
2665 |
must_read(sock, &path_len, sizeof(size_t)); |
252 |
✗✓ |
2665 |
if (path_len == 0 || path_len > sizeof(path)) |
253 |
|
|
_exit(1); |
254 |
|
2665 |
must_read(sock, &path, path_len); |
255 |
|
2665 |
path[path_len - 1] = '\0'; |
256 |
|
2665 |
check_log_name(path, sizeof(path)); |
257 |
|
|
|
258 |
✓✓ |
2665 |
if (cmd == PRIV_OPEN_LOG) |
259 |
|
1775 |
fd = open_file(path); |
260 |
✓✗ |
890 |
else if (cmd == PRIV_OPEN_PIPE) |
261 |
|
890 |
fd = open_pipe(path); |
262 |
|
|
else |
263 |
|
|
errx(1, "invalid cmd"); |
264 |
|
|
|
265 |
|
2665 |
send_fd(sock, fd); |
266 |
✓✓ |
2665 |
if (fd < 0) |
267 |
|
15 |
warnx("priv_open_log failed"); |
268 |
|
|
else |
269 |
|
2650 |
close(fd); |
270 |
|
|
break; |
271 |
|
|
|
272 |
|
|
case PRIV_OPEN_UTMP: |
273 |
|
18860 |
log_debug("[priv]: msg PRIV_OPEN_UTMP received"); |
274 |
|
18860 |
fd = open(_PATH_UTMP, O_RDONLY|O_NONBLOCK, 0); |
275 |
|
18860 |
send_fd(sock, fd); |
276 |
✗✓ |
18860 |
if (fd < 0) |
277 |
|
|
warnx("priv_open_utmp failed"); |
278 |
|
|
else |
279 |
|
18860 |
close(fd); |
280 |
|
|
break; |
281 |
|
|
|
282 |
|
|
case PRIV_OPEN_CONFIG: |
283 |
|
920 |
log_debug("[priv]: msg PRIV_OPEN_CONFIG received"); |
284 |
|
920 |
stat(conf, &cf_info); |
285 |
|
920 |
fd = open(conf, O_RDONLY|O_NONBLOCK, 0); |
286 |
|
920 |
send_fd(sock, fd); |
287 |
✗✓ |
920 |
if (fd < 0) |
288 |
|
|
warnx("priv_open_config failed"); |
289 |
|
|
else |
290 |
|
920 |
close(fd); |
291 |
|
|
break; |
292 |
|
|
|
293 |
|
|
case PRIV_CONFIG_MODIFIED: |
294 |
|
920 |
log_debug("[priv]: msg PRIV_CONFIG_MODIFIED received"); |
295 |
✓✗✗✓
|
1840 |
if (stat(conf, &cf_stat) < 0 || |
296 |
✓✗ |
1840 |
timespeccmp(&cf_info.st_mtimespec, |
297 |
✓✗✗✗
|
920 |
&cf_stat.st_mtimespec, <) || |
298 |
|
920 |
cf_info.st_size != cf_stat.st_size) { |
299 |
|
|
log_debug("config file modified: restarting"); |
300 |
|
|
restart = result = 1; |
301 |
|
|
must_write(sock, &result, sizeof(int)); |
302 |
|
|
} else { |
303 |
|
920 |
result = 0; |
304 |
|
920 |
must_write(sock, &result, sizeof(int)); |
305 |
|
|
} |
306 |
|
|
break; |
307 |
|
|
|
308 |
|
|
case PRIV_DONE_CONFIG_PARSE: |
309 |
|
874 |
log_debug("[priv]: msg PRIV_DONE_CONFIG_PARSE " |
310 |
|
|
"received"); |
311 |
|
874 |
increase_state(STATE_RUNNING); |
312 |
|
874 |
break; |
313 |
|
|
|
314 |
|
|
case PRIV_GETADDRINFO: |
315 |
|
895 |
log_debug("[priv]: msg PRIV_GETADDRINFO received"); |
316 |
|
|
/* Expecting: len, proto, len, host, len, serv */ |
317 |
|
895 |
must_read(sock, &protoname_len, sizeof(size_t)); |
318 |
✗✓ |
1790 |
if (protoname_len == 0 || |
319 |
|
895 |
protoname_len > sizeof(protoname)) |
320 |
|
|
_exit(1); |
321 |
|
895 |
must_read(sock, &protoname, protoname_len); |
322 |
|
895 |
protoname[protoname_len - 1] = '\0'; |
323 |
|
|
|
324 |
|
895 |
must_read(sock, &hostname_len, sizeof(size_t)); |
325 |
✗✓ |
1790 |
if (hostname_len == 0 || |
326 |
|
895 |
hostname_len > sizeof(hostname)) |
327 |
|
|
_exit(1); |
328 |
|
895 |
must_read(sock, &hostname, hostname_len); |
329 |
|
895 |
hostname[hostname_len - 1] = '\0'; |
330 |
|
|
|
331 |
|
895 |
must_read(sock, &servname_len, sizeof(size_t)); |
332 |
✗✓ |
1790 |
if (servname_len == 0 || |
333 |
|
895 |
servname_len > sizeof(servname)) |
334 |
|
|
_exit(1); |
335 |
|
895 |
must_read(sock, &servname, servname_len); |
336 |
|
895 |
servname[servname_len - 1] = '\0'; |
337 |
|
|
|
338 |
|
895 |
memset(&hints, 0, sizeof(hints)); |
339 |
✓✗✓ |
895 |
switch (strlen(protoname)) { |
340 |
|
|
case 3: |
341 |
|
|
hints.ai_family = AF_UNSPEC; |
342 |
|
|
break; |
343 |
|
|
case 4: |
344 |
✓✗✓ |
145 |
switch (protoname[3]) { |
345 |
|
|
case '4': |
346 |
|
|
hints.ai_family = AF_INET; |
347 |
|
55 |
break; |
348 |
|
|
case '6': |
349 |
|
|
hints.ai_family = AF_INET6; |
350 |
|
|
break; |
351 |
|
|
default: |
352 |
|
|
errx(1, "bad ip version %s", protoname); |
353 |
|
|
} |
354 |
|
|
break; |
355 |
|
|
default: |
356 |
|
|
errx(1, "bad protocol length %s", protoname); |
357 |
|
|
} |
358 |
✓✓ |
895 |
if (strncmp(protoname, "udp", 3) == 0) { |
359 |
|
|
hints.ai_socktype = SOCK_DGRAM; |
360 |
|
|
hints.ai_protocol = IPPROTO_UDP; |
361 |
✗✓ |
250 |
} else if (strncmp(protoname, "tcp", 3) == 0) { |
362 |
|
|
hints.ai_socktype = SOCK_STREAM; |
363 |
|
|
hints.ai_protocol = IPPROTO_TCP; |
364 |
|
|
} else { |
365 |
|
|
errx(1, "unknown protocol %s", protoname); |
366 |
|
|
} |
367 |
|
895 |
i = getaddrinfo(hostname, servname, &hints, &res0); |
368 |
✓✓ |
895 |
if (i != 0 || res0 == NULL) { |
369 |
|
10 |
addr_len = 0; |
370 |
|
10 |
must_write(sock, &addr_len, sizeof(int)); |
371 |
|
10 |
} else { |
372 |
|
|
/* Just send the first address */ |
373 |
|
885 |
i = res0->ai_addrlen; |
374 |
|
885 |
must_write(sock, &i, sizeof(int)); |
375 |
|
885 |
must_write(sock, res0->ai_addr, i); |
376 |
|
885 |
freeaddrinfo(res0); |
377 |
|
|
} |
378 |
|
|
break; |
379 |
|
|
|
380 |
|
|
case PRIV_GETNAMEINFO: |
381 |
|
1955 |
log_debug("[priv]: msg PRIV_GETNAMEINFO received"); |
382 |
✗✓ |
1955 |
if (numeric) |
383 |
|
|
errx(1, "rejected attempt to getnameinfo"); |
384 |
|
|
/* Expecting: length, sockaddr */ |
385 |
|
1955 |
must_read(sock, &addr_len, sizeof(int)); |
386 |
✓✗✗✓
|
3910 |
if (addr_len <= 0 || (size_t)addr_len > sizeof(addr)) |
387 |
|
|
_exit(1); |
388 |
|
1955 |
must_read(sock, &addr, addr_len); |
389 |
✗✓ |
5865 |
if (getnameinfo((struct sockaddr *)&addr, addr_len, |
390 |
|
1955 |
hostname, sizeof(hostname), NULL, 0, |
391 |
|
1955 |
NI_NOFQDN|NI_NAMEREQD|NI_DGRAM) != 0) { |
392 |
|
|
addr_len = 0; |
393 |
|
|
must_write(sock, &addr_len, sizeof(int)); |
394 |
|
|
} else { |
395 |
|
1955 |
addr_len = strlen(hostname) + 1; |
396 |
|
1955 |
must_write(sock, &addr_len, sizeof(int)); |
397 |
|
1955 |
must_write(sock, hostname, addr_len); |
398 |
|
|
} |
399 |
|
|
break; |
400 |
|
|
default: |
401 |
|
|
errx(1, "unknown command %d", cmd); |
402 |
|
|
break; |
403 |
|
|
} |
404 |
|
|
} |
405 |
|
|
|
406 |
|
874 |
close(sock); |
407 |
|
|
|
408 |
|
|
/* Unlink any domain sockets that have been opened */ |
409 |
✓✓ |
3706 |
for (i = 0; i < nunix; i++) |
410 |
|
979 |
(void)unlink(path_unix[i]); |
411 |
✓✓ |
874 |
if (path_ctlsock != NULL) |
412 |
|
50 |
(void)unlink(path_ctlsock); |
413 |
|
|
|
414 |
✗✓ |
874 |
if (restart) { |
415 |
|
|
int status; |
416 |
|
|
|
417 |
|
|
waitpid(child_pid, &status, 0); |
418 |
|
|
sigemptyset(&sigmask); |
419 |
|
|
sigaddset(&sigmask, SIGHUP); |
420 |
|
|
if (sigprocmask(SIG_SETMASK, &sigmask, NULL) == -1) |
421 |
|
|
err(1, "sigprocmask exec"); |
422 |
|
|
execvp(argv[0], argv); |
423 |
|
|
err(1, "exec restart '%s' failed", argv[0]); |
424 |
|
|
} |
425 |
|
|
unlink(_PATH_LOGPID); |
426 |
|
|
exit(0); |
427 |
|
|
} |
428 |
|
|
|
429 |
|
|
static int |
430 |
|
|
open_file(char *path) |
431 |
|
|
{ |
432 |
|
|
/* must not start with | */ |
433 |
✗✓ |
3550 |
if (path[0] == '|') |
434 |
|
|
return (-1); |
435 |
|
|
|
436 |
|
1775 |
return (open(path, O_WRONLY|O_APPEND|O_NONBLOCK, 0)); |
437 |
|
1775 |
} |
438 |
|
|
|
439 |
|
|
static int |
440 |
|
|
open_pipe(char *cmd) |
441 |
|
|
{ |
442 |
|
1780 |
char *argp[] = {"sh", "-c", NULL, NULL}; |
443 |
|
|
struct passwd *pw; |
444 |
|
890 |
int fd[2]; |
445 |
|
890 |
int bsize, flags; |
446 |
|
|
pid_t pid; |
447 |
|
|
|
448 |
|
|
/* skip over leading | and whitespace */ |
449 |
✗✓ |
890 |
if (cmd[0] != '|') |
450 |
|
|
return (-1); |
451 |
✓✗✗✓
|
2670 |
for (cmd++; *cmd && *cmd == ' '; cmd++) |
452 |
|
|
; /* nothing */ |
453 |
✗✓ |
890 |
if (!*cmd) |
454 |
|
|
return (-1); |
455 |
|
|
|
456 |
|
890 |
argp[2] = cmd; |
457 |
|
|
|
458 |
✗✓ |
890 |
if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, fd) == -1) { |
459 |
|
|
warnx("open_pipe"); |
460 |
|
|
return (-1); |
461 |
|
|
} |
462 |
|
|
|
463 |
|
|
/* make the fd on syslogd's side nonblocking */ |
464 |
✗✓ |
890 |
if ((flags = fcntl(fd[1], F_GETFL)) == -1) { |
465 |
|
|
warnx("fcntl"); |
466 |
|
|
return (-1); |
467 |
|
|
} |
468 |
|
890 |
flags |= O_NONBLOCK; |
469 |
✗✓ |
890 |
if ((flags = fcntl(fd[1], F_SETFL, flags)) == -1) { |
470 |
|
|
warnx("fcntl"); |
471 |
|
|
return (-1); |
472 |
|
|
} |
473 |
|
|
|
474 |
✗✓✗ |
890 |
switch (pid = fork()) { |
475 |
|
|
case -1: |
476 |
|
|
warnx("fork error"); |
477 |
|
|
return (-1); |
478 |
|
|
case 0: |
479 |
|
|
break; |
480 |
|
|
default: |
481 |
|
890 |
close(fd[0]); |
482 |
|
890 |
return (fd[1]); |
483 |
|
|
} |
484 |
|
|
|
485 |
|
|
close(fd[1]); |
486 |
|
|
|
487 |
|
|
/* grow receive buffer */ |
488 |
|
|
bsize = 65535; |
489 |
|
|
while (bsize > 0 && setsockopt(fd[0], SOL_SOCKET, SO_RCVBUF, |
490 |
|
|
&bsize, sizeof(bsize)) == -1) |
491 |
|
|
bsize /= 2; |
492 |
|
|
|
493 |
|
|
if ((pw = getpwnam("_syslogd")) == NULL) |
494 |
|
|
errx(1, "unknown user _syslogd"); |
495 |
|
|
if (setgroups(1, &pw->pw_gid) == -1 || |
496 |
|
|
setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1 || |
497 |
|
|
setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1) |
498 |
|
|
err(1, "failure dropping privs"); |
499 |
|
|
endpwent(); |
500 |
|
|
|
501 |
|
|
if (dup2(fd[0], STDIN_FILENO) == -1) |
502 |
|
|
err(1, "dup2 failed"); |
503 |
|
|
if (execv("/bin/sh", argp) == -1) |
504 |
|
|
err(1, "execv %s", cmd); |
505 |
|
|
/* NOTREACHED */ |
506 |
|
|
return (-1); |
507 |
|
890 |
} |
508 |
|
|
|
509 |
|
|
/* Check that the terminal device is ok, and if not, rewrite to /dev/null. |
510 |
|
|
* Either /dev/console or /dev/tty* are allowed. |
511 |
|
|
*/ |
512 |
|
|
static void |
513 |
|
|
check_tty_name(char *tty, size_t ttysize) |
514 |
|
|
{ |
515 |
|
|
const char ttypre[] = "/dev/tty"; |
516 |
|
|
char *p; |
517 |
|
|
|
518 |
|
|
/* Any path containing '..' is invalid. */ |
519 |
✓✗✓✓
|
689526 |
for (p = tty; p + 1 < tty + ttysize && *p; p++) |
520 |
✗✓✗✗
|
203010 |
if (*p == '.' && *(p + 1) == '.') |
521 |
|
|
goto bad_path; |
522 |
|
|
|
523 |
✓✓✓✗
|
39363 |
if (strcmp(_PATH_CONSOLE, tty) && strncmp(tty, ttypre, strlen(ttypre))) |
524 |
|
|
goto bad_path; |
525 |
|
20124 |
return; |
526 |
|
|
|
527 |
|
|
bad_path: |
528 |
|
|
warnx ("%s: invalid attempt to open %s: rewriting to /dev/null", |
529 |
|
|
"check_tty_name", tty); |
530 |
|
|
strlcpy(tty, "/dev/null", ttysize); |
531 |
|
20124 |
} |
532 |
|
|
|
533 |
|
|
/* If we are in the initial configuration state, accept a logname and add |
534 |
|
|
* it to the list of acceptable logfiles. Otherwise, check against this list |
535 |
|
|
* and rewrite to /dev/null if it's a bad path. |
536 |
|
|
*/ |
537 |
|
|
static void |
538 |
|
|
check_log_name(char *lognam, size_t logsize) |
539 |
|
|
{ |
540 |
|
|
struct logname *lg; |
541 |
|
|
char *p; |
542 |
|
|
|
543 |
|
|
/* Any path containing '..' is invalid. */ |
544 |
✓✗✓✓
|
362095 |
for (p = lognam; p + 1 < lognam + logsize && *p; p++) |
545 |
✓✓✓✗
|
121495 |
if (*p == '.' && *(p + 1) == '.') |
546 |
|
|
goto bad_path; |
547 |
|
|
|
548 |
✓✓✗ |
2665 |
switch (cur_state) { |
549 |
|
|
case STATE_CONFIG: |
550 |
|
2462 |
lg = malloc(sizeof(struct logname)); |
551 |
✗✓ |
2462 |
if (!lg) |
552 |
|
|
err(1, "check_log_name() malloc"); |
553 |
|
2462 |
strlcpy(lg->path, lognam, PATH_MAX); |
554 |
|
2462 |
TAILQ_INSERT_TAIL(&lognames, lg, next); |
555 |
|
|
break; |
556 |
|
|
case STATE_RUNNING: |
557 |
✓✗ |
2872 |
TAILQ_FOREACH(lg, &lognames, next) |
558 |
✓✓ |
1436 |
if (!strcmp(lg->path, lognam)) |
559 |
|
203 |
return; |
560 |
|
|
goto bad_path; |
561 |
|
|
break; |
562 |
|
|
default: |
563 |
|
|
/* Any other state should just refuse the request */ |
564 |
|
|
goto bad_path; |
565 |
|
|
break; |
566 |
|
|
} |
567 |
|
2462 |
return; |
568 |
|
|
|
569 |
|
|
bad_path: |
570 |
|
|
warnx("%s: invalid attempt to open %s: rewriting to /dev/null", |
571 |
|
|
"check_log_name", lognam); |
572 |
|
|
strlcpy(lognam, "/dev/null", logsize); |
573 |
|
2665 |
} |
574 |
|
|
|
575 |
|
|
/* Crank our state into less permissive modes */ |
576 |
|
|
static void |
577 |
|
|
increase_state(int state) |
578 |
|
|
{ |
579 |
✗✓ |
3496 |
if (state <= cur_state) |
580 |
|
|
errx(1, "attempt to decrease or match current state"); |
581 |
✗✓ |
1748 |
if (state < STATE_INIT || state > STATE_QUIT) |
582 |
|
|
errx(1, "attempt to switch to invalid state"); |
583 |
|
1748 |
cur_state = state; |
584 |
|
1748 |
} |
585 |
|
|
|
586 |
|
|
/* Open console or a terminal device for writing */ |
587 |
|
|
int |
588 |
|
|
priv_open_tty(const char *tty) |
589 |
|
|
{ |
590 |
|
|
char path[PATH_MAX]; |
591 |
|
|
int cmd, fd; |
592 |
|
|
size_t path_len; |
593 |
|
|
|
594 |
|
|
if (priv_fd < 0) |
595 |
|
|
errx(1, "%s: called from privileged portion", __func__); |
596 |
|
|
|
597 |
|
|
if (strlcpy(path, tty, sizeof path) >= sizeof(path)) |
598 |
|
|
return -1; |
599 |
|
|
path_len = strlen(path) + 1; |
600 |
|
|
|
601 |
|
|
cmd = PRIV_OPEN_TTY; |
602 |
|
|
must_write(priv_fd, &cmd, sizeof(int)); |
603 |
|
|
must_write(priv_fd, &path_len, sizeof(size_t)); |
604 |
|
|
must_write(priv_fd, path, path_len); |
605 |
|
|
fd = receive_fd(priv_fd); |
606 |
|
|
return fd; |
607 |
|
|
} |
608 |
|
|
|
609 |
|
|
/* Open log-file */ |
610 |
|
|
int |
611 |
|
|
priv_open_log(const char *lognam) |
612 |
|
|
{ |
613 |
|
|
char path[PATH_MAX]; |
614 |
|
|
int cmd, fd; |
615 |
|
|
size_t path_len; |
616 |
|
|
|
617 |
|
|
if (priv_fd < 0) |
618 |
|
|
errx(1, "%s: called from privileged child", __func__); |
619 |
|
|
|
620 |
|
|
if (strlcpy(path, lognam, sizeof path) >= sizeof(path)) |
621 |
|
|
return -1; |
622 |
|
|
path_len = strlen(path) + 1; |
623 |
|
|
|
624 |
|
|
if (lognam[0] == '|') |
625 |
|
|
cmd = PRIV_OPEN_PIPE; |
626 |
|
|
else |
627 |
|
|
cmd = PRIV_OPEN_LOG; |
628 |
|
|
must_write(priv_fd, &cmd, sizeof(int)); |
629 |
|
|
must_write(priv_fd, &path_len, sizeof(size_t)); |
630 |
|
|
must_write(priv_fd, path, path_len); |
631 |
|
|
fd = receive_fd(priv_fd); |
632 |
|
|
return fd; |
633 |
|
|
} |
634 |
|
|
|
635 |
|
|
/* Open utmp for reading */ |
636 |
|
|
FILE * |
637 |
|
|
priv_open_utmp(void) |
638 |
|
|
{ |
639 |
|
|
int cmd, fd; |
640 |
|
|
FILE *fp; |
641 |
|
|
|
642 |
|
|
if (priv_fd < 0) |
643 |
|
|
errx(1, "%s: called from privileged portion", __func__); |
644 |
|
|
|
645 |
|
|
cmd = PRIV_OPEN_UTMP; |
646 |
|
|
must_write(priv_fd, &cmd, sizeof(int)); |
647 |
|
|
fd = receive_fd(priv_fd); |
648 |
|
|
if (fd < 0) |
649 |
|
|
return NULL; |
650 |
|
|
|
651 |
|
|
fp = fdopen(fd, "r"); |
652 |
|
|
if (!fp) { |
653 |
|
|
warn("priv_open_utmp: fdopen() failed"); |
654 |
|
|
close(fd); |
655 |
|
|
return NULL; |
656 |
|
|
} |
657 |
|
|
|
658 |
|
|
return fp; |
659 |
|
|
} |
660 |
|
|
|
661 |
|
|
/* Open syslog config file for reading */ |
662 |
|
|
FILE * |
663 |
|
|
priv_open_config(void) |
664 |
|
|
{ |
665 |
|
|
int cmd, fd; |
666 |
|
|
FILE *fp; |
667 |
|
|
|
668 |
|
|
if (priv_fd < 0) |
669 |
|
|
errx(1, "%s: called from privileged portion", __func__); |
670 |
|
|
|
671 |
|
|
cmd = PRIV_OPEN_CONFIG; |
672 |
|
|
must_write(priv_fd, &cmd, sizeof(int)); |
673 |
|
|
fd = receive_fd(priv_fd); |
674 |
|
|
if (fd < 0) |
675 |
|
|
return NULL; |
676 |
|
|
|
677 |
|
|
fp = fdopen(fd, "r"); |
678 |
|
|
if (!fp) { |
679 |
|
|
warn("priv_open_config: fdopen() failed"); |
680 |
|
|
close(fd); |
681 |
|
|
return NULL; |
682 |
|
|
} |
683 |
|
|
|
684 |
|
|
return fp; |
685 |
|
|
} |
686 |
|
|
|
687 |
|
|
/* Ask if config file has been modified since last attempt to read it */ |
688 |
|
|
int |
689 |
|
|
priv_config_modified(void) |
690 |
|
|
{ |
691 |
|
|
int cmd, res; |
692 |
|
|
|
693 |
|
|
if (priv_fd < 0) |
694 |
|
|
errx(1, "%s: called from privileged portion", __func__); |
695 |
|
|
|
696 |
|
|
cmd = PRIV_CONFIG_MODIFIED; |
697 |
|
|
must_write(priv_fd, &cmd, sizeof(int)); |
698 |
|
|
|
699 |
|
|
/* Expect back integer signalling 1 for modification */ |
700 |
|
|
must_read(priv_fd, &res, sizeof(int)); |
701 |
|
|
return res; |
702 |
|
|
} |
703 |
|
|
|
704 |
|
|
/* Child can signal that its initial parsing is done, so that parent |
705 |
|
|
* can revoke further logfile permissions. This call only works once. */ |
706 |
|
|
void |
707 |
|
|
priv_config_parse_done(void) |
708 |
|
|
{ |
709 |
|
|
int cmd; |
710 |
|
|
|
711 |
|
|
if (priv_fd < 0) |
712 |
|
|
errx(1, "%s: called from privileged portion", __func__); |
713 |
|
|
|
714 |
|
|
cmd = PRIV_DONE_CONFIG_PARSE; |
715 |
|
|
must_write(priv_fd, &cmd, sizeof(int)); |
716 |
|
|
} |
717 |
|
|
|
718 |
|
|
/* Name/service to address translation. Response is placed into addr. |
719 |
|
|
* Return 0 for success or < 0 for error like getaddrinfo(3) */ |
720 |
|
|
int |
721 |
|
|
priv_getaddrinfo(char *proto, char *host, char *serv, struct sockaddr *addr, |
722 |
|
|
size_t addr_len) |
723 |
|
|
{ |
724 |
|
|
char protocpy[5], hostcpy[NI_MAXHOST], servcpy[NI_MAXSERV]; |
725 |
|
|
int cmd, ret_len; |
726 |
|
|
size_t protoname_len, hostname_len, servname_len; |
727 |
|
|
|
728 |
|
|
if (priv_fd < 0) |
729 |
|
|
errx(1, "%s: called from privileged portion", __func__); |
730 |
|
|
|
731 |
|
|
if (strlcpy(protocpy, proto, sizeof(protocpy)) >= sizeof(protocpy)) |
732 |
|
|
errx(1, "%s: overflow attempt in protoname", __func__); |
733 |
|
|
protoname_len = strlen(protocpy) + 1; |
734 |
|
|
if (strlcpy(hostcpy, host, sizeof(hostcpy)) >= sizeof(hostcpy)) |
735 |
|
|
errx(1, "%s: overflow attempt in hostname", __func__); |
736 |
|
|
hostname_len = strlen(hostcpy) + 1; |
737 |
|
|
if (strlcpy(servcpy, serv, sizeof(servcpy)) >= sizeof(servcpy)) |
738 |
|
|
errx(1, "%s: overflow attempt in servname", __func__); |
739 |
|
|
servname_len = strlen(servcpy) + 1; |
740 |
|
|
|
741 |
|
|
cmd = PRIV_GETADDRINFO; |
742 |
|
|
must_write(priv_fd, &cmd, sizeof(int)); |
743 |
|
|
must_write(priv_fd, &protoname_len, sizeof(size_t)); |
744 |
|
|
must_write(priv_fd, protocpy, protoname_len); |
745 |
|
|
must_write(priv_fd, &hostname_len, sizeof(size_t)); |
746 |
|
|
must_write(priv_fd, hostcpy, hostname_len); |
747 |
|
|
must_write(priv_fd, &servname_len, sizeof(size_t)); |
748 |
|
|
must_write(priv_fd, servcpy, servname_len); |
749 |
|
|
|
750 |
|
|
/* Expect back an integer size, and then a string of that length */ |
751 |
|
|
must_read(priv_fd, &ret_len, sizeof(int)); |
752 |
|
|
|
753 |
|
|
/* Check there was no error (indicated by a return of 0) */ |
754 |
|
|
if (!ret_len) |
755 |
|
|
return (-1); |
756 |
|
|
|
757 |
|
|
/* Make sure we aren't overflowing the passed in buffer */ |
758 |
|
|
if (ret_len < 0 || (size_t)ret_len > addr_len) |
759 |
|
|
errx(1, "%s: overflow attempt in return", __func__); |
760 |
|
|
|
761 |
|
|
/* Read the resolved address and make sure we got all of it */ |
762 |
|
|
memset(addr, '\0', addr_len); |
763 |
|
|
must_read(priv_fd, addr, ret_len); |
764 |
|
|
|
765 |
|
|
return (0); |
766 |
|
|
} |
767 |
|
|
|
768 |
|
|
/* Reverse address resolution; response is placed into host. |
769 |
|
|
* Return 0 for success or < 0 for error like getnameinfo(3) */ |
770 |
|
|
int |
771 |
|
|
priv_getnameinfo(struct sockaddr *sa, socklen_t salen, char *host, |
772 |
|
|
size_t hostlen) |
773 |
|
|
{ |
774 |
|
|
int cmd, ret_len; |
775 |
|
|
|
776 |
|
|
if (priv_fd < 0) |
777 |
|
|
errx(1, "%s called from privileged portion", __func__); |
778 |
|
|
|
779 |
|
|
cmd = PRIV_GETNAMEINFO; |
780 |
|
|
must_write(priv_fd, &cmd, sizeof(int)); |
781 |
|
|
must_write(priv_fd, &salen, sizeof(int)); |
782 |
|
|
must_write(priv_fd, sa, salen); |
783 |
|
|
|
784 |
|
|
/* Expect back an integer size, and then a string of that length */ |
785 |
|
|
must_read(priv_fd, &ret_len, sizeof(int)); |
786 |
|
|
|
787 |
|
|
/* Check there was no error (indicated by a return of 0) */ |
788 |
|
|
if (!ret_len) |
789 |
|
|
return (-1); |
790 |
|
|
|
791 |
|
|
/* Check we don't overflow the passed in buffer */ |
792 |
|
|
if (ret_len < 0 || (size_t)ret_len > hostlen) |
793 |
|
|
errx(1, "%s: overflow attempt in return", __func__); |
794 |
|
|
|
795 |
|
|
/* Read the resolved hostname */ |
796 |
|
|
must_read(priv_fd, host, ret_len); |
797 |
|
|
return (0); |
798 |
|
|
} |
799 |
|
|
|
800 |
|
|
/* Pass the signal through to child */ |
801 |
|
|
static void |
802 |
|
|
sig_pass_to_chld(int sig) |
803 |
|
|
{ |
804 |
|
1712 |
int save_errno = errno; |
805 |
|
|
|
806 |
✓✗ |
856 |
if (child_pid != -1) |
807 |
|
856 |
kill(child_pid, sig); |
808 |
|
856 |
errno = save_errno; |
809 |
|
856 |
} |
810 |
|
|
|
811 |
|
|
/* When child dies, move into the shutdown state */ |
812 |
|
|
/* ARGSUSED */ |
813 |
|
|
static void |
814 |
|
|
sig_got_chld(int sig) |
815 |
|
|
{ |
816 |
|
2250 |
int save_errno = errno; |
817 |
|
|
pid_t pid; |
818 |
|
|
|
819 |
|
1125 |
do { |
820 |
|
2270 |
pid = waitpid(WAIT_ANY, NULL, WNOHANG); |
821 |
✓✓✓✗
|
3144 |
if (pid == child_pid && cur_state < STATE_QUIT) |
822 |
|
874 |
cur_state = STATE_QUIT; |
823 |
✓✓✓✓ ✗✓ |
3647 |
} while (pid > 0 || (pid == -1 && errno == EINTR)); |
824 |
|
1125 |
errno = save_errno; |
825 |
|
1125 |
} |
826 |
|
|
|
827 |
|
|
/* Read all data or return 1 for error. */ |
828 |
|
|
static int |
829 |
|
|
may_read(int fd, void *buf, size_t n) |
830 |
|
|
{ |
831 |
|
|
char *s = buf; |
832 |
|
|
ssize_t res; |
833 |
|
|
size_t pos = 0; |
834 |
|
|
|
835 |
✓✓ |
238621 |
while (n > pos) { |
836 |
|
48065 |
res = read(fd, s + pos, n - pos); |
837 |
✗✓✓ |
48065 |
switch (res) { |
838 |
|
|
case -1: |
839 |
|
|
if (errno == EINTR || errno == EAGAIN) |
840 |
|
|
continue; |
841 |
|
|
case 0: |
842 |
|
852 |
return (1); |
843 |
|
|
default: |
844 |
|
47213 |
pos += res; |
845 |
|
|
} |
846 |
|
|
} |
847 |
|
47213 |
return (0); |
848 |
|
48065 |
} |
849 |
|
|
|
850 |
|
|
/* Read data with the assertion that it all must come through, or |
851 |
|
|
* else abort the process. Based on atomicio() from openssh. */ |
852 |
|
|
static void |
853 |
|
|
must_read(int fd, void *buf, size_t n) |
854 |
|
|
{ |
855 |
|
|
char *s = buf; |
856 |
|
|
ssize_t res; |
857 |
|
|
size_t pos = 0; |
858 |
|
|
|
859 |
✓✓ |
274290 |
while (n > pos) { |
860 |
|
54858 |
res = read(fd, s + pos, n - pos); |
861 |
✗✗✓ |
54858 |
switch (res) { |
862 |
|
|
case -1: |
863 |
|
|
if (errno == EINTR || errno == EAGAIN) |
864 |
|
|
continue; |
865 |
|
|
case 0: |
866 |
|
|
_exit(1); |
867 |
|
|
default: |
868 |
|
54858 |
pos += res; |
869 |
|
|
} |
870 |
|
|
} |
871 |
|
54858 |
} |
872 |
|
|
|
873 |
|
|
/* Write data with the assertion that it all has to be written, or |
874 |
|
|
* else abort the process. Based on atomicio() from openssh. */ |
875 |
|
|
static void |
876 |
|
|
must_write(int fd, void *buf, size_t n) |
877 |
|
|
{ |
878 |
|
|
char *s = buf; |
879 |
|
|
ssize_t res; |
880 |
|
|
size_t pos = 0; |
881 |
|
|
|
882 |
✓✓ |
33050 |
while (n > pos) { |
883 |
|
6610 |
res = write(fd, s + pos, n - pos); |
884 |
✗✗✓ |
6610 |
switch (res) { |
885 |
|
|
case -1: |
886 |
|
|
if (errno == EINTR || errno == EAGAIN) |
887 |
|
|
continue; |
888 |
|
|
case 0: |
889 |
|
|
_exit(1); |
890 |
|
|
default: |
891 |
|
6610 |
pos += res; |
892 |
|
|
} |
893 |
|
|
} |
894 |
|
6610 |
} |