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