/* * utils.c * potd is licensed under the BSD license: * * Copyright (c) 2018 Toni Uhlig * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * - 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. * * - The names of its contributors may not 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 OWNER 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. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_EXECINFO #include #endif #ifdef HAVE_VALGRIND #include #endif #include "utils.h" #include "compat.h" #ifdef HAVE_SECCOMP #include "pseccomp.h" #endif #include "log.h" #include "options.h" #define _POSIX_PATH_MAX 256 #define MAX_STACK_FRAMES 64 char *arg0 = NULL; static int null_fd = -1; static const char cgmem[] = "/sys/fs/cgroup/memory/potd"; static const char cgcpu[] = "/sys/fs/cgroup/cpu/potd"; static const char cgpid[] = "/sys/fs/cgroup/pids/potd"; static const char cgdef[] = "/sys/fs/cgroup/potd"; static const char *_cgmem = NULL; static const char *_cgcpu = NULL; static const char *_cgpid = NULL; #ifdef HAVE_EXECINFO static void *stack_traces[MAX_STACK_FRAMES]; #endif #ifdef HAVE_EXECINFO static void print_stack_trace(void); #endif static char * sig_to_str(int signo, char *buf, size_t siz); static void sighandler_child(int signo); static void sighandler_master(int signo); static int cgroups_write_file(const char *cdir, const char *csub, const char *value, size_t siz); static inline void bin2hex_char(unsigned char c, char hexc[5]); #ifdef HAVE_EXECINFO static void print_stack_trace(void) { int i, trace_size = 0; char **call_stack; trace_size = backtrace(stack_traces, MAX_STACK_FRAMES); call_stack = backtrace_symbols(stack_traces, trace_size); if (log_fmt) E("Backtrace returned %d addresses", trace_size); else fprintf(stderr, "Backtrace returned %d addresses", trace_size); for (i = 0; i < trace_size; ++i) { if (log_fmt) E(" %s", call_stack[i]); else fprintf(stderr, " %s", call_stack[i]); } if (call_stack) free(call_stack); } #endif int set_fd_nonblock(int fd) { int flags; flags = fcntl(fd, F_GETFL, 0); if (flags < 0) return 1; flags |= O_NONBLOCK; if (fcntl(fd, F_SETFL, flags) == -1) return 1; return 0; } static char * sig_to_str(int signo, char *buf, size_t siz) { switch (signo) { case SIGCHLD: strncpy(buf, "SIGCHLD", siz-1); break; case SIGPIPE: strncpy(buf, "SIGPIPE", siz-1); break; case SIGABRT: strncpy(buf, "SIGABRT", siz-1); break; case SIGSEGV: strncpy(buf, "SIGSEGV", siz-1); break; case SIGTERM: strncpy(buf, "SIGTERM", siz-1); break; case SIGINT: strncpy(buf, "SIGINT", siz-1); break; case SIGHUP: strncpy(buf, "SIGHUP", siz-1); break; default: strncpy(buf, "UNKNOWN", siz-1); break; } buf[siz - 1] = 0; return buf; } static void sighandler_child(int signo) { static int got_exitsig = 0; char buf[16] = {0}; if (got_exitsig) return; W("Got signal[%d]: %s", signo, sig_to_str(signo, &buf[0], sizeof buf)); switch (signo) { case SIGABRT: got_exitsig = 1; exit(EXIT_FAILURE); case SIGHUP: if (getppid() == 1) { N("Master process %d died, exiting", getpgrp()); got_exitsig = 1; exit(EXIT_SUCCESS); } break; case SIGSEGV: #ifdef HAVE_CONFIG_H E("Segmentation fault .. please report to <%s>", PACKAGE_BUGREPORT); #else E("%s", "Segmentation fault .."); #endif #ifdef HAVE_EXECINFO print_stack_trace(); #endif got_exitsig = 1; exit(EXIT_FAILURE); } } int set_child_sighandler(void) { if (prctl(PR_SET_PDEATHSIG, SIGHUP) != 0) return 1; assert( signal(SIGCHLD, SIG_IGN) != SIG_ERR ); assert( signal(SIGPIPE, SIG_IGN) != SIG_ERR ); assert( signal(SIGABRT, sighandler_child) != SIG_ERR ); assert( signal(SIGSEGV, sighandler_child) != SIG_ERR ); return signal(SIGHUP, sighandler_child) == SIG_ERR; } static void sighandler_master(int signo) { static int exiting = 0; char buf[16] = {0}; W("Got signal[%d]: %s", signo, sig_to_str(signo, &buf[0], sizeof buf)); switch (signo) { case SIGSEGV: case SIGINT: case SIGTERM: case SIGABRT: if (exiting) break; exiting = 1; exit(EXIT_FAILURE); } } int set_master_sighandler(void) { int s = 0; s |= signal(SIGSEGV, sighandler_master) == SIG_ERR; s |= signal(SIGINT, sighandler_master) == SIG_ERR; s |= signal(SIGTERM, sighandler_master) == SIG_ERR; s |= signal(SIGABRT, sighandler_master) == SIG_ERR; return s; } void set_procname(const char *new_arg0) { assert(arg0); memset(arg0, 0, _POSIX_PATH_MAX); strncpy(arg0, new_arg0, _POSIX_PATH_MAX); } pid_t daemonize(int stay_foreground) { int status = -1; pid_t pid; /* Fork off the parent process */ pid = fork(); /* An error occurred */ if (pid < 0) { E_STRERR("%s", "fork"); return pid; } /* Success: Let the parent terminate */ if (pid > 0) { if (!stay_foreground) exit(EXIT_SUCCESS); waitpid(-1, &status, 0); exit(EXIT_SUCCESS); } /* On success: The child process becomes session leader */ if (!stay_foreground && setsid() < 0) { E_STRERR("%s", "setsid"); exit(EXIT_FAILURE); } /* Fork off for the second time*/ if (!stay_foreground) { pid = fork(); /* An error occurred */ if (pid < 0) exit(EXIT_FAILURE); /* Success: Let the parent terminate */ if (pid > 0) exit(EXIT_SUCCESS); } if (!stay_foreground && setpgrp()) { E_STRERR("%s", "setpgrp"); exit(EXIT_FAILURE); } /* Set new file permissions */ umask(0); if (!stay_foreground) { /* Change the working directory to the root directory */ /* or another appropriated directory */ if (chdir("/")) return -1; /* Close all open file descriptors */ assert( close_fds_except(-1) == 0 ); assert( redirect_devnull_to(0, 1, 2, -1) == 0 ); } else { assert( close_fds_except(0, 1, 2, -1) == 0 ); } if (log_open()) return -1; return pid; } int close_fds_except(int fds, ...) { int fd; long max_fd; size_t i, except_count, found; va_list ap; max_fd = sysconf(_SC_OPEN_MAX) - 1; if (max_fd <= 0) max_fd = 1024; va_start(ap, fds); { int *all_fds = (int *) malloc((max_fd+1) * sizeof(*all_fds)); assert(all_fds); memset(all_fds, -1, max_fd * sizeof(*all_fds)); except_count = 0; while ( (fd = va_arg(ap, int)) >= 0 ) { all_fds[except_count++] = fd; } all_fds[except_count++] = fds; for (fd = max_fd; fd >= 0; --fd) { found = 0; for (i = 0; i < except_count && fds >= 0; ++i) { if (fd == all_fds[i]) found++; } if (!found) { close(fd); } } free(all_fds); } va_end(ap); return 0; } int redirect_devnull_to(int fds, ...) { int fd, rc = 0; va_list ap; if (null_fd < 0) null_fd = open("/dev/null", O_RDWR); if (null_fd < 0) return -1; if (fds < -1) return -1; va_start(ap, fds); { while ( (fd = va_arg(ap, int)) >= 0 ) { if ( dup2(null_fd, fd) < 0 ) rc++; } } va_end(ap); return rc; } int change_user_group(const char *user, const char *group) { struct passwd pwd; struct group grp; gid_t gid; if (group) D2("Change user to '%s' and group '%s'", user, group); else D2("Change user to '%s' and its main group", user); if (potd_getpwnam(user, &pwd)) { E_STRERR("Get uid from user '%s'", user); return 1; } if (!group) { gid = pwd.pw_gid; } else { if (potd_getgrnam(group, &grp)) { E_STRERR("Get gid from group '%s'", group); return 1; } gid = grp.gr_gid; } if (setresgid(gid, gid, gid)) return 1; if (setresuid(pwd.pw_uid, pwd.pw_uid, pwd.pw_uid)) return 1; return 0; } int change_default_user_group(void) { return change_user_group(getopt_str(OPT_CHUSER), (getopt_used(OPT_CHGROUP) ? getopt_str(OPT_CHGROUP) : NULL)); } int safe_chroot(const char *newroot) { int s; s = chdir(newroot); if (s) { E_STRERR("Change directory to '%s'", newroot); return 1; } /* Flawfinder: ignore */ s = chroot("."); if (s) return 1; s = chdir("/"); if (s) { E_STRERR("Change directory inside new root to '%s'", "/"); return 1; } return 0; } void mkdir_attr(const char *fname, mode_t mode, uid_t uid, gid_t gid) { assert(fname); mode &= 07777; if (mkdir(fname, mode) == -1 || chmod(fname, mode) == -1 || chown(fname, uid, gid)) { FATAL("%s: create directory '%s'", __func__, fname); } } int is_dir(const char *fname) { int rv; struct stat s; char tmp[PATH_MAX]; assert(fname); if (*fname == '\0') return 0; // if fname doesn't end in '/', add one if (fname[strlen(fname) - 1] == '/') { rv = stat(fname, &s); } else { snprintf(tmp, sizeof tmp, "%s/", fname); /* Flawfinder: ignore */ rv = stat(tmp, &s); } if (rv == -1) return 0; if (S_ISDIR(s.st_mode)) return 1; return 0; } int is_link(const char *fname) { struct stat s; assert(fname); if (*fname == '\0') return 0; if (lstat(fname, &s) == 0) { if (S_ISLNK(s.st_mode)) return 1; } return 0; } int path_is_mountpoint(const char *path) { struct stat current, parent; size_t plen = strnlen(path, PATH_MAX); char parent_path[plen + 4]; char *dirc, *dname; if (stat(path, ¤t)) goto error; if (S_ISREG(current.st_mode)) { dirc = strdup(path); assert(dirc); dname = dirname(dirc); if (stat(dname, &parent)) { free(dirc); goto error; } free(dirc); } else { strncpy(parent_path, path, plen); parent_path[plen] = '/'; parent_path[plen+1] = '.'; parent_path[plen+2] = '.'; parent_path[plen+3] = 0; if (stat(parent_path, &parent)) goto error; } return current.st_dev != parent.st_dev; error: W_STRERR("Mountpoint check for '%s'", path); return -1; } void chk_chroot(void) { struct stat s; if (stat("/", &s) == 0) { if (s.st_ino != 2) return; } W2("%s", "Can not mount filesystem as slave/private"); } void mount_root(void) { int s; s = mount("none", "/", "none", MS_SLAVE|MS_REC, NULL); if (s) s = mount("none", "/", "none", MS_PRIVATE|MS_REC, NULL); if (s) chk_chroot(); } int mount_dev(const char *mount_path) { int s; s = mount("tmpfs", mount_path, "tmpfs", MS_NOSUID|MS_STRICTATIME| MS_NOEXEC|MS_REC, "size=4k,mode=755,gid=0"); if (s) { E_STRERR("Mount devtmpfs filesystem to %s", mount_path); return 1; } return 0; } int mount_pts(const char *mount_path) { int s; s = mount("devpts", mount_path, "devpts", MS_MGC_VAL, "newinstance,gid=5,mode=620,ptmxmode=0666"); if (s) { E_STRERR("Mount devpts filesystem to %s", mount_path); return 1; } return 0; } int setup_network_namespace(const char *name) { int fd; char netns_path[PATH_MAX]; int made_netns_run_dir_mount = 0; snprintf(netns_path, sizeof netns_path, "%s/%s", getopt_str(OPT_NETNS_RUN_DIR), name); /* Flawfinder: ignore */ D2("Network Namespace path '%s'", netns_path); if (mkdir(getopt_str(OPT_NETNS_RUN_DIR), S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)) { if (errno != EEXIST) { E_STRERR("Create netns directory '%s'", getopt_str(OPT_NETNS_RUN_DIR)); return 1; } } while (mount("", getopt_str(OPT_NETNS_RUN_DIR), "none", MS_SHARED|MS_REC, NULL)) { /* Fail unless we need to make the mount point */ if (errno != EINVAL || made_netns_run_dir_mount) { E_STRERR("Mount netns directory '%s' as shared", getopt_str(OPT_NETNS_RUN_DIR)); return 1; } /* Upgrade NETNS_RUN_DIR to a mount point */ if (mount(getopt_str(OPT_NETNS_RUN_DIR), getopt_str(OPT_NETNS_RUN_DIR), "none", MS_BIND | MS_REC, NULL)) { E_STRERR("Bind mount netns directory '%s'", getopt_str(OPT_NETNS_RUN_DIR)); return 1; } made_netns_run_dir_mount = 1; } /* Create the filesystem state */ fd = open(netns_path, O_RDONLY|O_CREAT|O_EXCL, 0); if (fd < 0 && errno != EEXIST) { E_STRERR("Create namespace file '%s'", netns_path); return 1; } if (fd >= 0) close(fd); if (unshare(CLONE_NEWNET) < 0) { E_STRERR("Create network namespace '%s'", name); goto error; } /* Bind the netns last so I can watch for it */ if (mount("/proc/self/ns/net", netns_path, "none", MS_BIND, NULL) < 0) { E_STRERR("Bind /proc/self/ns/net to '%s'", netns_path); goto error; } return 0; error: /* cleanup netns? */ return 1; } int switch_network_namespace(const char *name) { char net_path[PATH_MAX]; int netns; snprintf(net_path, sizeof(net_path), "%s/%s", getopt_str(OPT_NETNS_RUN_DIR), name); /* Flawfinder: ignore */ netns = open(net_path, O_RDONLY | O_CLOEXEC); if (netns < 0) { W_STRERR("Open network namespace '%s'", name); goto error; } if (setns(netns, CLONE_NEWNET) < 0) { E_STRERR("Setting the network namespace '%s'", name); close(netns); goto error; } close(netns); return 0; error: #ifdef HAVE_VALGRIND /* * Older valgrind versions dont support the setns syscall * or the open might fail with ENOENT. */ if (RUNNING_ON_VALGRIND) { W2("%s", "Running on valgrind, using unshare instead of setns and " "ignoring errors before .."); return unshare(CLONE_NEWNET) != 0; } #endif return 1; } int create_device_file_checked(const char *mount_path, const char *device_file, mode_t mode, int add_mode, dev_t dev) { int s; mode_t defmode = S_IRUSR|S_IWUSR| S_IRGRP|S_IWGRP| S_IROTH; size_t plen = strnlen(mount_path, PATH_MAX); size_t dlen = strnlen(device_file, PATH_MAX); struct stat devbuf; char devpath[plen+dlen+2]; snprintf(devpath, plen+dlen+2, "%s/%s", mount_path, device_file); /* Flawfinder: ignore */ s = stat(devpath, &devbuf); if (s && errno != EEXIST && errno != ENOENT) { return 1; } if (errno == EEXIST) { if (unlink(devpath)) return 1; } D2("Create device file: %s", devpath); if (!add_mode) defmode = 0; s = mknod(devpath, defmode|mode, dev); if (s) { E_STRERR("Device creation '%s'", devpath); return 1; } return 0; } int create_device_files(const char *mount_path) { int s = 0; s |= create_device_file_checked(mount_path, "ptmx", S_IFCHR, 1, makedev(5,2)); s |= create_device_file_checked(mount_path, "tty", S_IFCHR, 1, makedev(5,0)); return s; } static int cgroups_write_file(const char *cdir, const char *csub, const char *value, size_t siz) { int fd, s = 0; char buf[BUFSIZ] = {0}; assert(cdir && csub && value); D2("Write '%s' to '%s/%s'", value, cdir, csub); if (snprintf(buf, sizeof buf, "%s/%s", cdir, csub) > 0) /* Flawfinder: ignore */ { if ((fd = open(buf, O_WRONLY)) < 0 || write(fd, value, siz) <= 0) { W_STRERR("Write '%s' to '%s/%s'", value, cdir, csub); s = 1; } if (fd >= 0) close(fd); } return s; } int cgroups_set(void) { int s = 0, fail = 0; const char maxmem[] = "memory.limit_in_bytes"; const char maxmem_soft[] = "memory.soft_limit_in_bytes"; const char kmem[] = "memory.kmem.limit_in_bytes"; const char kmem_tcp[] = "memory.kmem.tcp.limit_in_bytes"; const char maxmem_limit[] = "8388608"; /* 8*1024*1024 = 8MB */ const char maxmem_soft_limit[] = "7340032"; /* 7*1024*1024 = 8MB */ const char cpu_shares[] = "cpu.shares"; const char cpu_shares_limit[] = "32"; const char cfs_period[] = "cpu.cfs_period_us"; const char cfs_period_limit[] = "50000"; const char cfs_quota[] = "cpu.cfs_quota_us"; const char cfs_quota_limit[] = "10000"; const char pid_max[] = "pids.max"; const char pid_max_limit[] = "10"; const char rt_period[] = "cpu.rt_period_us"; const char *rt_period_limit = cfs_period_limit; const char rt_runtime[] = "cpu.rt_runtime_us"; const char *rt_runtime_limit = cfs_quota_limit; const char ccpus[] = "cpuset.cpus"; const char cmems[] = "cpuset.mems"; if (remove(cgmem) && errno != ENOENT) return 1; errno = 0; s |= mkdir(cgmem, S_IRUSR|S_IWUSR|S_IXUSR | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH); if (errno) fail++; if (remove(cgcpu) && errno != ENOENT) return 1; errno = 0; s |= mkdir(cgcpu, S_IRUSR|S_IWUSR|S_IXUSR | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH); if (errno) fail++; if (remove(cgpid) && errno != ENOENT) return 1; errno = 0; s |= mkdir(cgpid, S_IRUSR|S_IWUSR|S_IXUSR | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH); if (errno) fail++; if (fail == 3) { if (remove(cgdef) && errno != ENOENT) return 1; s = mkdir(cgdef, S_IRUSR|S_IWUSR|S_IXUSR | S_IRGRP|S_IXGRP | S_IROTH|S_IXOTH); if (s) return 1; s |= cgroups_write_file(cgdef, ccpus, "0", 1); s |= cgroups_write_file(cgdef, cmems, "0", 1); _cgmem = cgdef; _cgcpu = cgdef; _cgpid = cgdef; } else { _cgmem = cgmem; _cgcpu = cgcpu; _cgpid = cgpid; } s |= cgroups_write_file(_cgmem, maxmem, maxmem_limit, sizeof maxmem_limit); s |= cgroups_write_file(_cgmem, maxmem_soft, maxmem_soft_limit, sizeof maxmem_limit); s |= cgroups_write_file(_cgmem, kmem_tcp, maxmem_limit, sizeof maxmem_limit); s |= cgroups_write_file(_cgmem, kmem, maxmem_limit, sizeof maxmem_limit); s |= cgroups_write_file(_cgcpu, cpu_shares, cpu_shares_limit, sizeof cpu_shares_limit); errno = 0; cgroups_write_file(_cgcpu, cfs_period, cfs_period_limit, sizeof cfs_period_limit); if (errno) { s |= cgroups_write_file(_cgcpu, rt_period, rt_period_limit, sizeof cfs_period_limit); } errno = 0; cgroups_write_file(_cgcpu, cfs_quota, cfs_quota_limit, sizeof cfs_quota_limit); if (errno) { s |= cgroups_write_file(_cgcpu, rt_runtime, rt_runtime_limit, sizeof cfs_quota_limit); } s |= cgroups_write_file(_cgpid, pid_max, pid_max_limit, sizeof pid_max_limit); return s; } int cgroups_activate(void) { pid_t p = getpid(); int s; char buf[32] = {0}; const char tasks[] = "tasks"; s = snprintf(buf, sizeof buf, "%d", p); /* Flawfinder: ignore */ if (s <= 0) return 1; s = cgroups_write_file(_cgmem, tasks, buf, s); s = snprintf(buf, sizeof buf, "%d", p); /* Flawfinder: ignore */ if (s <= 0) return 1; s = cgroups_write_file(_cgcpu, tasks, buf, s); s = snprintf(buf, sizeof buf, "%d", p); /* Flawfinder: ignore */ if (s <= 0) return 1; s = cgroups_write_file(_cgpid, tasks, buf, s); return s; } #if 0 int update_guid_map(pid_t pid, unsigned int map[3], int update_uidmap) { int s, fd; ssize_t written; const char path_pid[] = "/proc/%d/%s"; const char path_self[] = "/proc/self/%s"; char buf[64]; if (pid < 0) { /* Flawfinder: ignore */ s = snprintf(buf, sizeof buf, path_self, (update_uidmap ? "uid_map" : "gid_map")); } else { /* Flawfinder: ignore */ s = snprintf(buf, sizeof buf, path_pid, pid, (update_uidmap ? "uid_map" : "gid_map")); } if (s <= 0) return 1; fd = open(buf, O_WRONLY); if (fd < 0) return 1; s = snprintf(buf, sizeof buf, "%u %u %u\n", map[0], map[1], map[2]); /* Flawfinder: ignore */ written = write(fd, buf, s); if (written <= 0) return 1; return 0; } int update_setgroups_self(int allow) { int fd; ssize_t written; const char path_self[] = "/proc/self/setgroups"; const char str_allow[] = "allow"; const char str_deny[] = "deny"; fd = open(path_self, O_WRONLY); if (fd < 0) return 1; if (allow) { written = write(fd, str_allow, sizeof(str_allow) - 1); } else { written = write(fd, str_deny, sizeof(str_deny) - 1); } if (written <= 0) return 1; return 0; } #endif static inline void bin2hex_char(unsigned char c, char hexc[5]) { static const char hexalnum[] = "0123456789ABCDEF"; hexc[0] = '\\'; hexc[1] = 'x'; hexc[2] = hexalnum[ (c >> 4)%16 ]; hexc[3] = hexalnum[ (c & 0x0F)%16 ]; hexc[4] = 0; } void escape_ascii_string(const char ascii[], size_t siz, char **dest, size_t *newsiz) { char hexbyte[5]; const size_t binsiz = 4; size_t i, j, ns; assert(ascii && dest && newsiz); ns = 0; for (i = 0; i < siz; ++i) { if (isprint(ascii[i])) ns++; else ns += binsiz; } if (ns > *newsiz) { if (*dest) free(*dest); *dest = (char *) malloc(sizeof(char) * (ns+1)); assert(*dest); (*newsiz) = ns; } for (i = 0, j = 0; i < siz && j < ns; ++i) { if (isprint(ascii[i])) { (*dest)[j] = ascii[i]; j++; } else { bin2hex_char(ascii[i], hexbyte); snprintf((*dest)+j, binsiz+1, "%s", hexbyte); /* Flawfinder: ignore */ j += binsiz; } } (*dest)[ns] = 0; } size_t parse_hostport(const char *str, const char *result[2], size_t siz[2]) { size_t i; const char *hostend = strchr(str, ':'); const char *portend; const char sep[] = ": \t\n\0"; result[0] = NULL; result[1] = NULL; siz[0] = 0; siz[1] = 0; if (!hostend) return 0; hostend++; for (i = 0; i < SIZEOF(sep); ++i) { portend = strchr(hostend, sep[i]); if (portend) break; } if (!portend) return 0; result[0] = str; result[1] = hostend; siz[0] = hostend - str - 1; siz[1] = portend - hostend; return siz[0] + siz[1] + 1 + (*portend != 0 ? 1 : 0); } size_t parse_hostport_str(const char *str, char hbuf[NI_MAXHOST], char sbuf[NI_MAXSERV]) { const char *hostport[2]; size_t hostport_siz[2]; size_t siz; siz = parse_hostport(str, hostport, hostport_siz); if (!siz) return 0; if (snprintf(hbuf, NI_MAXHOST, "%.*s", (int) hostport_siz[0], hostport[0]) <= 0) /* Flawfinder: ignore */ { return 0; } if (snprintf(sbuf, NI_MAXSERV, "%.*s", (int) hostport_siz[1], hostport[1]) <= 0) { return 0; } return siz; } int selftest_minimal_requirements(void) { int s; char buf[32] = {0}; char test[64] = {0}; pid_t child_pid; #ifdef HAVE_SECCOMP pseccomp_ctx *psc = NULL; #endif N2("%s", "Selftest .."); /* do some basic runtime tests */ memset(&test[0], 'A', sizeof test); test[sizeof test - 1] = 0; s = snprintf(buf, sizeof buf, "%s", &test[0]); if (s != sizeof test - 1) goto error; if (buf[sizeof buf - 1] != 0) goto error; #ifdef HAVE_VALGRIND if (RUNNING_ON_VALGRIND) W2("%s", "You are using valgrind. This is *ONLY* for debug reasons and may " "affect your overall security! Be warned."); #endif s = open(getopt_str(OPT_ROFILE), O_WRONLY|O_CREAT|O_TRUNC, 0); if (s < 0 && errno != EEXIST) { E_STRERR("RO-file '%s' check", getopt_str(OPT_ROFILE)); goto error; } else if (s >= 0) { close(s); if (chmod(getopt_str(OPT_ROFILE), S_IRUSR|S_IWUSR)) goto error; } if (mkdir(getopt_str(OPT_RODIR), S_IRWXU) && errno != EEXIST) { E_STRERR("RO-directory '%s' check", getopt_str(OPT_RODIR)); goto error; } if (mkdir(getopt_str(OPT_ROOT), S_IRWXU) && errno != EEXIST) { E_STRERR("ROOT-directory '%s' check", getopt_str(OPT_ROOT)); goto error; } if (mkdir(getopt_str(OPT_NETNS_RUN_DIR), S_IRWXU) && errno != EEXIST) { E_STRERR("NETNS-directory '%s' check", getopt_str(OPT_NETNS_RUN_DIR)); goto error; } if (mkdir(getopt_str(OPT_SSH_RUN_DIR), S_IRWXU) && errno != EEXIST) { E_STRERR("SSH-directory '%s' check", getopt_str(OPT_SSH_RUN_DIR)); goto error; } /* * The following tests do neither work on travis-ci nor on gitlab. * FIXME: fork() broken on some docker containers? */ s = -1; child_pid = fork(); if (!child_pid) { if (change_default_user_group()) exit(EXIT_FAILURE); else exit(EXIT_SUCCESS); } else waitpid(child_pid, &s, 0); if (s) goto error; /* advanced sandbox tests */ if (getuid() == (uid_t) 0) { child_pid = fork(); switch (child_pid) { case -1: E_STRERR("%s", "Forking"); goto error; break; case 0: if (clearenv()) { E_STRERR("%s", "Clearing environment vairables"); exit(EXIT_FAILURE); } if (cgroups_set() || cgroups_activate()) { E_STRERR("%s", "Activating cgroups"); exit(EXIT_FAILURE); } if (unshare(CLONE_NEWUTS|CLONE_NEWPID|CLONE_NEWIPC|CLONE_NEWNS)) { E_STRERR("%s", "Unshare"); exit(EXIT_FAILURE); } mount_root(); #ifdef HAVE_SECCOMP pseccomp_init(&psc, (getopt_used(OPT_SECCOMP_MINIMAL) ? PS_MINIMUM : 0)); if (pseccomp_default_rules(psc)) { E_STRERR("%s", "Seccomp"); exit(EXIT_FAILURE); } pseccomp_free(&psc); #endif s = -1; child_pid = fork(); if (!child_pid) { if (safe_chroot(getopt_str(OPT_ROOT))) exit(EXIT_FAILURE); #ifdef HAVE_SECCOMP pseccomp_set_immutable(); pseccomp_init(&psc, (getopt_used(OPT_SECCOMP_MINIMAL) ? PS_MINIMUM : 0)); if (pseccomp_jail_rules(psc)) exit(EXIT_FAILURE); #endif exit(EXIT_SUCCESS); } else waitpid(child_pid, &s, 0); exit(s); default: waitpid(child_pid, &s, 0); if (s) goto error; } } N("%s", "Selftest success"); if (getopt_used(OPT_RUNTEST)) exit(EXIT_SUCCESS); return 0; error: E("%s", "Selftest failed"); if (getopt_used(OPT_RUNTEST)) exit(EXIT_FAILURE); return 1; }