/* * Copyright (c) 2016-2024 OARC, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. Neither the name of the copyright holder nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "daemon.h" #include "log.h" #include "memzero.h" void drop_privileges(void) { struct rlimit rss; struct passwd pwd; struct passwd* result = 0; size_t pwdBufSize; char* pwdBuf; unsigned int s; uid_t oldUID = getuid(); uid_t oldGID = getgid(); uid_t dropUID; gid_t dropGID; const char* user; struct group* grp = 0; /* * Security: getting UID and GUID for nobody */ pwdBufSize = sysconf(_SC_GETPW_R_SIZE_MAX); if (pwdBufSize == -1) pwdBufSize = 16384; pwdBuf = calloc(pwdBufSize, sizeof(char)); if (pwdBuf == NULL) { fprintf(stderr, "unable to allocate buffer for pwdBuf\n"); exit(1); } user = options.user ? options.user : DROPTOUSER; if (options.group) { if (!(grp = getgrnam(options.group))) { if (errno) { fprintf(stderr, "Unable to get group %s: %s\n", options.group, strerror(errno)); } else { fprintf(stderr, "Group %s not found, existing.\n", options.group); } exit(1); } } s = getpwnam_r(user, &pwd, pwdBuf, pwdBufSize, &result); if (result == NULL) { if (s == 0) { fprintf(stderr, "User %s not found, exiting.\n", user); exit(1); } else { fprintf(stderr, "issue with getpwnnam_r call, exiting.\n"); exit(1); } } dropUID = pwd.pw_uid; dropGID = grp ? grp->gr_gid : pwd.pw_gid; dnscap_memzero(pwdBuf, pwdBufSize); free(pwdBuf); /* * Security section: setting memory limit and dropping privileges to nobody */ getrlimit(RLIMIT_DATA, &rss); if (mem_limit_set) { rss.rlim_cur = mem_limit; rss.rlim_max = mem_limit; if (setrlimit(RLIMIT_DATA, &rss) == -1) { fprintf(stderr, "Unable to set the memory limit, exiting\n"); exit(1); } } #if HAVE_SETRESGID if (setresgid(dropGID, dropGID, dropGID) < 0) { fprintf(stderr, "Unable to drop GID to %s: %s\n", options.group ? options.group : user, strerror(errno)); exit(1); } #elif HAVE_SETREGID if (setregid(dropGID, dropGID) < 0) { fprintf(stderr, "Unable to drop GID to %s: %s\n", options.group ? options.group : user, strerror(errno)); exit(1); } #elif HAVE_SETEGID if (setegid(dropGID) < 0) { fprintf(stderr, "Unable to drop GID to %s: %s\n", options.group ? options.group : user, strerror(errno)); exit(1); } #endif #if HAVE_INITGROUPS if (initgroups(pwd.pw_name, dropGID) < 0) { fprintf(stderr, "Unable to init supplemental groups for %s: %s\n", user, strerror(errno)); exit(1); } #elif HAVE_SETGROUPS if (setgroups(0, NULL) < 0) { fprintf(stderr, "Unable to drop supplemental groups: %s\n", strerror(errno)); exit(1); } #endif #if HAVE_SETRESUID if (setresuid(dropUID, dropUID, dropUID) < 0) { fprintf(stderr, "Unable to drop UID to %s: %s\n", user, strerror(errno)); exit(1); } #elif HAVE_SETREUID if (setreuid(dropUID, dropUID) < 0) { fprintf(stderr, "Unable to drop UID to %s: %s\n", user, strerror(errno)); exit(1); } #elif HAVE_SETEUID if (seteuid(dropUID) < 0) { fprintf(stderr, "Unable to drop UID to %s: %s\n", user, strerror(errno)); exit(1); } #endif /* * Testing if privileges are dropped */ if (oldGID != getgid() && (setgid(oldGID) == 1 && setegid(oldGID) != 1)) { fprintf(stderr, "Able to restore back to root, exiting.\n"); fprintf(stderr, "currentUID:%u currentGID:%u\n", getuid(), getgid()); exit(1); } if ((oldUID != getuid() && getuid() == 0) && (setuid(oldUID) != 1 && seteuid(oldUID) != 1)) { fprintf(stderr, "Able to restore back to root, exiting.\n"); fprintf(stderr, "currentUID:%u currentGID:%u\n", getgid(), getgid()); exit(1); } #ifdef USE_SECCOMP if (use_seccomp == FALSE) { return; } #if 0 /* * Setting SCMP_ACT_TRAP means the process will get * a SIGSYS signal when a bad syscall is executed * This is for debugging and should be monitored. */ scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_TRAP); #endif /* * SCMP_ACT_KILL tells the kernel to kill the process * when a syscall we did not filter on is called. * This should be uncommented in production. */ scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL); if (ctx == NULL) { fprintf(stderr, "Unable to create seccomp-bpf context\n"); exit(1); } int r = 0; r |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt), 0); r |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(uname), 0); r |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap), 0); r |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(munmap), 0); r |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0); r |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0); r |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0); r |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(close), 0); r |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fstat), 0); r |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(lseek), 0); r |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(select), 0); r |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(stat), 0); if (r != 0) { fprintf(stderr, "Unable to apply seccomp-bpf filter\n"); seccomp_release(ctx); exit(1); } r = seccomp_load(ctx); if (r < 0) { seccomp_release(ctx); fprintf(stderr, "Unable to load seccomp-bpf filter\n"); exit(1); } #endif } void write_pid_file(void) { FILE* fp; int fd, flags; struct flock lock; if (!options.pid_file) return; if ((fd = open(options.pid_file, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR)) == -1) { fprintf(stderr, "unable to open PID file %s: %s", options.pid_file, strerror(errno)); exit(1); } if ((flags = fcntl(fd, F_GETFD)) == -1) { fprintf(stderr, "unable to get PID file flags: %s", strerror(errno)); exit(1); } flags |= FD_CLOEXEC; if (fcntl(fd, F_SETFD, flags) == 1) { fprintf(stderr, "unable to set PID file flags: %s", strerror(errno)); exit(1); } lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0; if (fcntl(fd, F_SETLK, &lock) == -1) { if (errno == EACCES || errno == EAGAIN) { fprintf(stderr, "PID file locked by other process"); exit(1); } fprintf(stderr, "unable to lock PID file: %s", strerror(errno)); exit(1); } if (ftruncate(fd, 0) == -1) { fprintf(stderr, "unable to truncate PID file: %s", strerror(errno)); exit(1); } fp = fdopen(fd, "w"); if (!fp || fprintf(fp, "%d\n", getpid()) < 1 || fflush(fp)) { fprintf(stderr, "unable to write to PID file: %s", strerror(errno)); exit(1); } } void daemonize(void) { pid_t pid; #ifdef TIOCNOTTY int i; #endif if ((pid = fork()) < 0) { logerr("fork failed: %s", strerror(errno)); exit(1); } else if (pid > 0) exit(0); write_pid_file(); openlog("dnscap", 0, LOG_DAEMON); if (setsid() < 0) { logerr("setsid failed: %s", strerror(errno)); exit(1); } #ifdef TIOCNOTTY if ((i = open("/dev/tty", O_RDWR)) >= 0) { ioctl(i, TIOCNOTTY, NULL); close(i); } #endif logerr("Backgrounded as pid %u", getpid()); }