/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* * Code to lock-down the environment of the processes we run, to avoid * exotic or un-necessary system calls to be used to break containment. */ #include "config.h" #include #include #include #include #if DISABLE_SECCOMP == 0 #include #endif #include #include #include #include #include #include #include #include #include #include #include #ifndef SYS_SECCOMP # define SYS_SECCOMP 1 #endif #if defined(__x86_64__) # define AUDIT_ARCH_NR AUDIT_ARCH_X86_64 # define REG_SYSCALL REG_RAX #else # error "Platform does not support seccomp filtering yet - unsafe." #endif #if DISABLE_SECCOMP == 0 extern "C" { static void handleSysSignal(int /* signal */, siginfo_t *info, void *context) { ucontext_t *uctx = reinterpret_cast(context); Log::signalLogPrefix(); Log::signalLog("SIGSYS trapped with code: "); Log::signalLogNumber(info->si_code); Log::signalLog(" and context "); Log::signalLogNumber(reinterpret_cast(context)); Log::signalLog("\n"); if (info->si_code != SYS_SECCOMP || !uctx) return; unsigned int syscall = uctx->uc_mcontext.gregs[REG_SYSCALL]; Log::signalLogPrefix(); Log::signalLog(" seccomp trapped signal, un-authorized sys-call: "); Log::signalLogNumber(syscall); Log::signalLog("\n"); SigUtil::dumpBacktrace(); _exit(1); } } // extern "C" #endif namespace Seccomp { bool lockdown(Type type) { (void)type; // so far just the kit. #if DISABLE_SECCOMP == 0 #define ACCEPT_SYSCALL(name) \ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_##name, 0, 1), \ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW) #define KILL_SYSCALL(name) \ BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_##name, 0, 1), \ BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_TRAP) struct sock_filter filterCode[] = { // Check our architecture is correct. BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, arch)), BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, AUDIT_ARCH_NR, 1, 0), BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL), // Load sycall number BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, nr)), // ------------------------------------------------------------ // --- First white-list the syscalls we frequently use. --- // ------------------------------------------------------------ ACCEPT_SYSCALL(recvfrom), ACCEPT_SYSCALL(write), ACCEPT_SYSCALL(futex), // glibc's 'poll' has to answer for this lot: ACCEPT_SYSCALL(epoll_wait), ACCEPT_SYSCALL(epoll_ctl), ACCEPT_SYSCALL(epoll_create), ACCEPT_SYSCALL(close), ACCEPT_SYSCALL(nanosleep), // ------------------------------------------------------------ // --- Now block everything that we don't like the look of. --- // ------------------------------------------------------------ // FIXME: should we bother blocking calls that have early // permission checks we don't meet ? #if 0 // cf. eg. /usr/include/asm/unistd_64.h ... KILL_SYSCALL(ioctl), KILL_SYSCALL(mincore), KILL_SYSCALL(shmget), KILL_SYSCALL(shmat), KILL_SYSCALL(shmctl), #endif KILL_SYSCALL(getitimer), KILL_SYSCALL(setitimer), KILL_SYSCALL(sendfile), KILL_SYSCALL(shutdown), KILL_SYSCALL(listen), // server sockets KILL_SYSCALL(accept), // server sockets #if 0 KILL_SYSCALL(wait4), #endif KILL_SYSCALL(kill), // ! KILL_SYSCALL(shmctl), KILL_SYSCALL(ptrace), // tracing KILL_SYSCALL(capset), KILL_SYSCALL(uselib), KILL_SYSCALL(personality), // ! KILL_SYSCALL(vhangup), KILL_SYSCALL(modify_ldt), // ! KILL_SYSCALL(pivot_root), // ! KILL_SYSCALL(chroot), KILL_SYSCALL(acct), // ! KILL_SYSCALL(sync), // I/O perf. KILL_SYSCALL(mount), KILL_SYSCALL(umount2), KILL_SYSCALL(swapon), KILL_SYSCALL(swapoff), KILL_SYSCALL(reboot), // ! KILL_SYSCALL(sethostname), KILL_SYSCALL(setdomainname), KILL_SYSCALL(tkill), KILL_SYSCALL(mbind), // vm bits KILL_SYSCALL(set_mempolicy), // vm bits KILL_SYSCALL(get_mempolicy), // vm bits KILL_SYSCALL(kexec_load), KILL_SYSCALL(add_key), // kernel keyring KILL_SYSCALL(request_key), // kernel keyring KILL_SYSCALL(keyctl), // kernel keyring KILL_SYSCALL(inotify_init), KILL_SYSCALL(inotify_add_watch), KILL_SYSCALL(inotify_rm_watch), KILL_SYSCALL(unshare), KILL_SYSCALL(splice), KILL_SYSCALL(tee), KILL_SYSCALL(vmsplice), // vm bits KILL_SYSCALL(move_pages), // vm bits KILL_SYSCALL(accept4), // server sockets KILL_SYSCALL(inotify_init1), KILL_SYSCALL(perf_event_open), // profiling KILL_SYSCALL(fanotify_init), KILL_SYSCALL(fanotify_mark), #ifdef __NR_seccomp KILL_SYSCALL(seccomp), // no further fiddling #endif #ifdef __NR_bpf KILL_SYSCALL(bpf), // no further fiddling #endif // allow the rest. BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW) }; struct sock_fprog filter = { sizeof(filterCode)/sizeof(filterCode[0]), // length filterCode }; if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { LOG_ERR("Cannot turn off acquisition of new privileges for us & children"); return false; } if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &filter)) { LOG_ERR("Failed to install seccomp syscall filter"); return false; } // Trap, log, and exit on failure struct sigaction action; sigemptyset(&action.sa_mask); action.sa_flags = SA_SIGINFO; action.sa_handler = reinterpret_cast<__sighandler_t>(handleSysSignal); sigaction(SIGSYS, &action, nullptr); LOG_TRC("Install seccomp filter successfully."); return true; #else // DISABLE_SECCOMP == 0 LOG_WRN("Warning: this binary was compiled with disabled seccomp-bpf."); return true; #endif // DISABLE_SECCOMP == 0 } } // namespace Seccomp namespace Rlimit { bool handleSetrlimitCommand(const std::vector& tokens) { if (tokens.size() == 3 && tokens[0] == "setconfig") { if (tokens[1] == "limit_virt_mem_mb") { rlim_t lim = std::stoi(tokens[2]) * 1024 * 1024; if (lim <= 0) lim = RLIM_INFINITY; rlimit rlim = { lim, lim }; if (setrlimit(RLIMIT_AS, &rlim) != 0) LOG_SYS("Failed to set RLIMIT_AS to " << lim << " bytes."); if (getrlimit(RLIMIT_AS, &rlim) == 0) LOG_INF("RLIMIT_AS is " << rlim.rlim_max << " bytes after setting it to " << lim << " bytes."); else LOG_SYS("Failed to get RLIMIT_AS."); return true; } else if (tokens[1] == "limit_stack_mem_kb") { rlim_t lim = std::stoi(tokens[2]) * 1024; if (lim <= 0) lim = RLIM_INFINITY; rlimit rlim = { lim, lim }; if (setrlimit(RLIMIT_STACK, &rlim) != 0) LOG_SYS("Failed to set RLIMIT_STACK to " << lim << " bytes."); if (getrlimit(RLIMIT_STACK, &rlim) == 0) LOG_INF("RLIMIT_STACK is " << rlim.rlim_max << " bytes after setting it to " << lim << " bytes."); else LOG_SYS("Failed to get RLIMIT_STACK."); return true; } else if (tokens[1] == "limit_file_size_mb") { rlim_t lim = std::stoi(tokens[2]) * 1024 * 1024; if (lim <= 0) lim = RLIM_INFINITY; /* FIXME Currently the RLIMIT_FSIZE handling is non-ideal, and can * lead to crashes of the kit processes due to not handling signal * 25 gracefully. Let's disable for now before there's a more * concrete plan. rlimit rlim = { lim, lim }; if (setrlimit(RLIMIT_FSIZE, &rlim) != 0) LOG_SYS("Failed to set RLIMIT_FSIZE to " << lim << " bytes."); if (getrlimit(RLIMIT_FSIZE, &rlim) == 0) LOG_INF("RLIMIT_FSIZE is " << rlim.rlim_max << " bytes after setting it to " << lim << " bytes."); else LOG_SYS("Failed to get RLIMIT_FSIZE."); */ LOG_SYS("Ignored setting RLIMIT_FSIZE to " << lim << " bytes."); return true; } else if (tokens[1] == "limit_num_open_files") { rlim_t lim = std::stoi(tokens[2]); if (lim <= 0) lim = RLIM_INFINITY; /* FIXME Currently the RLIMIT_ is non-ideal, and can lead to * problems. Let's disable for now before there's a more * concrete plan. rlimit rlim = { lim, lim }; if (setrlimit(RLIMIT_NOFILE, &rlim) != 0) LOG_SYS("Failed to set RLIMIT_NOFILE to " << lim << " files."); if (getrlimit(RLIMIT_NOFILE, &rlim) == 0) LOG_INF("RLIMIT_NOFILE is " << rlim.rlim_max << " files after setting it to " << lim << " files."); else LOG_SYS("Failed to get RLIMIT_NOFILE."); */ LOG_SYS("Ignored setting RLIMIT_NOFILE to " << lim << " files."); return true; } } return false; } } // namespace Rlimit /* vim:set shiftwidth=4 softtabstop=4 expandtab: */