| /* |
| |
| nsjail - config parsing |
| ----------------------------------------- |
| |
| Copyright 2017 Google Inc. All Rights Reserved. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| |
| #include "config.h" |
| |
| #include <fcntl.h> |
| #include <google/protobuf/io/zero_copy_stream_impl.h> |
| #include <google/protobuf/text_format.h> |
| #include <stdio.h> |
| #include <sys/mount.h> |
| #include <sys/personality.h> |
| #include <sys/resource.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| #include <fstream> |
| #include <string> |
| #include <vector> |
| |
| #include "caps.h" |
| #include "cmdline.h" |
| #include "config.pb.h" |
| #include "logs.h" |
| #include "macros.h" |
| #include "mnt.h" |
| #include "user.h" |
| #include "util.h" |
| |
| namespace config { |
| |
| static uint64_t configRLimit( |
| int res, const nsjail::RLimit& rl, const uint64_t val, unsigned long mul = 1UL) { |
| if (rl == nsjail::RLimit::VALUE) { |
| return (val * mul); |
| } |
| if (rl == nsjail::RLimit::SOFT) { |
| return cmdline::parseRLimit(res, "soft", mul); |
| } |
| if (rl == nsjail::RLimit::HARD) { |
| return cmdline::parseRLimit(res, "hard", mul); |
| } |
| if (rl == nsjail::RLimit::INF) { |
| return RLIM64_INFINITY; |
| } |
| LOG_F("Unknown rlimit value type for rlimit:%d", res); |
| abort(); |
| } |
| |
| static bool configParseInternal(nsjconf_t* nsjconf, const nsjail::NsJailConfig& njc) { |
| switch (njc.mode()) { |
| case nsjail::Mode::LISTEN: |
| nsjconf->mode = MODE_LISTEN_TCP; |
| break; |
| case nsjail::Mode::ONCE: |
| nsjconf->mode = MODE_STANDALONE_ONCE; |
| break; |
| case nsjail::Mode::RERUN: |
| nsjconf->mode = MODE_STANDALONE_RERUN; |
| break; |
| case nsjail::Mode::EXECVE: |
| nsjconf->mode = MODE_STANDALONE_EXECVE; |
| break; |
| default: |
| LOG_E("Unknown running mode: %d", njc.mode()); |
| return false; |
| } |
| nsjconf->hostname = njc.hostname(); |
| nsjconf->cwd = njc.cwd(); |
| nsjconf->port = njc.port(); |
| nsjconf->bindhost = njc.bindhost(); |
| nsjconf->max_conns = njc.max_conns(); |
| nsjconf->max_conns_per_ip = njc.max_conns_per_ip(); |
| nsjconf->tlimit = njc.time_limit(); |
| nsjconf->max_cpus = njc.max_cpus(); |
| nsjconf->daemonize = njc.daemon(); |
| |
| if (njc.has_log_fd()) { |
| logs::logFile("", njc.log_fd()); |
| } |
| if (njc.has_log_file()) { |
| logs::logFile(njc.log_file(), STDERR_FILENO); |
| } |
| |
| if (njc.has_log_level()) { |
| switch (njc.log_level()) { |
| case nsjail::LogLevel::DEBUG: |
| logs::logLevel(logs::DEBUG); |
| break; |
| case nsjail::LogLevel::INFO: |
| logs::logLevel(logs::INFO); |
| break; |
| case nsjail::LogLevel::WARNING: |
| logs::logLevel(logs::WARNING); |
| break; |
| case nsjail::LogLevel::ERROR: |
| logs::logLevel(logs::ERROR); |
| break; |
| case nsjail::LogLevel::FATAL: |
| logs::logLevel(logs::FATAL); |
| break; |
| default: |
| LOG_E("Unknown log_level: %d", njc.log_level()); |
| return false; |
| } |
| } |
| |
| nsjconf->keep_env = njc.keep_env(); |
| for (ssize_t i = 0; i < njc.envar_size(); i++) { |
| cmdline::addEnv(nsjconf, njc.envar(i)); |
| } |
| |
| nsjconf->keep_caps = njc.keep_caps(); |
| for (ssize_t i = 0; i < njc.cap_size(); i++) { |
| int cap = caps::nameToVal(njc.cap(i).c_str()); |
| if (cap == -1) { |
| return false; |
| } |
| nsjconf->caps.push_back(cap); |
| } |
| |
| nsjconf->is_silent = njc.silent(); |
| nsjconf->skip_setsid = njc.skip_setsid(); |
| |
| for (ssize_t i = 0; i < njc.pass_fd_size(); i++) { |
| nsjconf->openfds.push_back(njc.pass_fd(i)); |
| } |
| |
| nsjconf->stderr_to_null = njc.stderr_to_null(); |
| nsjconf->disable_no_new_privs = njc.disable_no_new_privs(); |
| |
| nsjconf->rl_as = |
| configRLimit(RLIMIT_AS, njc.rlimit_as_type(), njc.rlimit_as(), 1024UL * 1024UL); |
| nsjconf->rl_core = |
| configRLimit(RLIMIT_CORE, njc.rlimit_core_type(), njc.rlimit_core(), 1024UL * 1024UL); |
| nsjconf->rl_cpu = configRLimit(RLIMIT_CPU, njc.rlimit_cpu_type(), njc.rlimit_cpu()); |
| nsjconf->rl_fsize = configRLimit( |
| RLIMIT_FSIZE, njc.rlimit_fsize_type(), njc.rlimit_fsize(), 1024UL * 1024UL); |
| nsjconf->rl_nofile = |
| configRLimit(RLIMIT_NOFILE, njc.rlimit_nofile_type(), njc.rlimit_nofile()); |
| nsjconf->rl_nproc = configRLimit(RLIMIT_NPROC, njc.rlimit_nproc_type(), njc.rlimit_nproc()); |
| nsjconf->rl_stack = configRLimit( |
| RLIMIT_STACK, njc.rlimit_stack_type(), njc.rlimit_stack(), 1024UL * 1024UL); |
| nsjconf->rl_mlock = |
| configRLimit(RLIMIT_MEMLOCK, njc.rlimit_memlock_type(), njc.rlimit_memlock(), 1024UL); |
| nsjconf->rl_rtpr = |
| configRLimit(RLIMIT_RTPRIO, njc.rlimit_rtprio_type(), njc.rlimit_rtprio()); |
| nsjconf->rl_msgq = |
| configRLimit(RLIMIT_MSGQUEUE, njc.rlimit_msgqueue_type(), njc.rlimit_msgqueue()); |
| |
| nsjconf->disable_rl = njc.disable_rl(); |
| |
| if (njc.persona_addr_compat_layout()) { |
| nsjconf->personality |= ADDR_COMPAT_LAYOUT; |
| } |
| if (njc.persona_mmap_page_zero()) { |
| nsjconf->personality |= MMAP_PAGE_ZERO; |
| } |
| if (njc.persona_read_implies_exec()) { |
| nsjconf->personality |= READ_IMPLIES_EXEC; |
| } |
| if (njc.persona_addr_limit_3gb()) { |
| nsjconf->personality |= ADDR_LIMIT_3GB; |
| } |
| if (njc.persona_addr_no_randomize()) { |
| nsjconf->personality |= ADDR_NO_RANDOMIZE; |
| } |
| |
| nsjconf->clone_newnet = njc.clone_newnet(); |
| nsjconf->clone_newuser = njc.clone_newuser(); |
| nsjconf->clone_newns = njc.clone_newns(); |
| nsjconf->clone_newpid = njc.clone_newpid(); |
| nsjconf->clone_newipc = njc.clone_newipc(); |
| nsjconf->clone_newuts = njc.clone_newuts(); |
| nsjconf->clone_newcgroup = njc.clone_newcgroup(); |
| nsjconf->clone_newtime = njc.clone_newtime(); |
| |
| nsjconf->no_pivotroot = njc.no_pivotroot(); |
| |
| for (ssize_t i = 0; i < njc.uidmap_size(); i++) { |
| if (!user::parseId(nsjconf, njc.uidmap(i).inside_id(), njc.uidmap(i).outside_id(), |
| njc.uidmap(i).count(), false /* is_gid */, njc.uidmap(i).use_newidmap())) { |
| return false; |
| } |
| } |
| for (ssize_t i = 0; i < njc.gidmap_size(); i++) { |
| if (!user::parseId(nsjconf, njc.gidmap(i).inside_id(), njc.gidmap(i).outside_id(), |
| njc.gidmap(i).count(), true /* is_gid */, njc.gidmap(i).use_newidmap())) { |
| return false; |
| } |
| } |
| |
| if (!njc.mount_proc()) { |
| nsjconf->proc_path.clear(); |
| } |
| for (ssize_t i = 0; i < njc.mount_size(); i++) { |
| std::string src = njc.mount(i).src(); |
| std::string src_env = njc.mount(i).prefix_src_env(); |
| std::string dst = njc.mount(i).dst(); |
| std::string dst_env = njc.mount(i).prefix_dst_env(); |
| std::string fstype = njc.mount(i).fstype(); |
| std::string options = njc.mount(i).options(); |
| |
| uintptr_t flags = (!njc.mount(i).rw()) ? MS_RDONLY : 0; |
| flags |= njc.mount(i).is_bind() ? (MS_BIND | MS_REC | MS_PRIVATE) : 0; |
| flags |= njc.mount(i).nosuid() ? MS_NOSUID : 0; |
| flags |= njc.mount(i).nodev() ? MS_NODEV : 0; |
| flags |= njc.mount(i).noexec() ? MS_NOEXEC : 0; |
| bool is_mandatory = njc.mount(i).mandatory(); |
| bool is_symlink = njc.mount(i).is_symlink(); |
| std::string src_content = njc.mount(i).src_content(); |
| |
| mnt::isDir_t is_dir = mnt::NS_DIR_MAYBE; |
| if (njc.mount(i).has_is_dir()) { |
| is_dir = njc.mount(i).is_dir() ? mnt::NS_DIR_YES : mnt::NS_DIR_NO; |
| } |
| |
| if (!mnt::addMountPtTail(nsjconf, src, dst, fstype, options, flags, is_dir, |
| is_mandatory, src_env, dst_env, src_content, is_symlink)) { |
| LOG_E("Couldn't add mountpoint for src:'%s' dst:'%s'", src.c_str(), |
| dst.c_str()); |
| return false; |
| } |
| } |
| |
| if (njc.has_seccomp_policy_file()) { |
| nsjconf->kafel_file_path = njc.seccomp_policy_file(); |
| } |
| for (ssize_t i = 0; i < njc.seccomp_string().size(); i++) { |
| nsjconf->kafel_string += njc.seccomp_string(i); |
| nsjconf->kafel_string += '\n'; |
| } |
| nsjconf->seccomp_log = njc.seccomp_log(); |
| nsjconf->nice_level = njc.nice_level(); |
| |
| nsjconf->cgroup_mem_max = njc.cgroup_mem_max(); |
| nsjconf->cgroup_mem_memsw_max = njc.cgroup_mem_memsw_max(); |
| nsjconf->cgroup_mem_swap_max = njc.cgroup_mem_swap_max(); |
| nsjconf->cgroup_mem_mount = njc.cgroup_mem_mount(); |
| nsjconf->cgroup_mem_parent = njc.cgroup_mem_parent(); |
| nsjconf->cgroup_pids_max = njc.cgroup_pids_max(); |
| nsjconf->cgroup_pids_mount = njc.cgroup_pids_mount(); |
| nsjconf->cgroup_pids_parent = njc.cgroup_pids_parent(); |
| nsjconf->cgroup_net_cls_classid = njc.cgroup_net_cls_classid(); |
| nsjconf->cgroup_net_cls_mount = njc.cgroup_net_cls_mount(); |
| nsjconf->cgroup_net_cls_parent = njc.cgroup_net_cls_parent(); |
| nsjconf->cgroup_cpu_ms_per_sec = njc.cgroup_cpu_ms_per_sec(); |
| nsjconf->cgroup_cpu_mount = njc.cgroup_cpu_mount(); |
| nsjconf->cgroup_cpu_parent = njc.cgroup_cpu_parent(); |
| nsjconf->cgroupv2_mount = njc.cgroupv2_mount(); |
| nsjconf->use_cgroupv2 = njc.use_cgroupv2(); |
| |
| nsjconf->iface_lo = !(njc.iface_no_lo()); |
| for (ssize_t i = 0; i < njc.iface_own().size(); i++) { |
| nsjconf->ifaces.push_back(njc.iface_own(i)); |
| } |
| if (njc.has_macvlan_iface()) { |
| nsjconf->iface_vs = njc.macvlan_iface(); |
| } |
| nsjconf->iface_vs_ip = njc.macvlan_vs_ip(); |
| nsjconf->iface_vs_nm = njc.macvlan_vs_nm(); |
| nsjconf->iface_vs_gw = njc.macvlan_vs_gw(); |
| nsjconf->iface_vs_ma = njc.macvlan_vs_ma(); |
| nsjconf->iface_vs_mo = njc.macvlan_vs_mo(); |
| |
| nsjconf->disable_tsc = njc.disable_tsc(); |
| |
| if (njc.has_exec_bin()) { |
| if (njc.exec_bin().has_path()) { |
| nsjconf->exec_file = njc.exec_bin().path(); |
| nsjconf->argv.push_back(njc.exec_bin().path()); |
| } |
| for (ssize_t i = 0; i < njc.exec_bin().arg().size(); i++) { |
| nsjconf->argv.push_back(njc.exec_bin().arg(i)); |
| } |
| if (njc.exec_bin().has_arg0()) { |
| nsjconf->argv[0] = njc.exec_bin().arg0(); |
| } |
| nsjconf->use_execveat = njc.exec_bin().exec_fd(); |
| } |
| |
| return true; |
| } |
| |
| static void LogHandler( |
| google::protobuf::LogLevel level, const char* filename, int line, const std::string& message) { |
| LOG_W("config.cc: '%s'", message.c_str()); |
| } |
| |
| bool parseFile(nsjconf_t* nsjconf, const char* file) { |
| LOG_D("Parsing configuration from '%s'", file); |
| |
| int fd = TEMP_FAILURE_RETRY(open(file, O_RDONLY | O_CLOEXEC)); |
| if (fd == -1) { |
| PLOG_W("Couldn't open config file '%s'", file); |
| return false; |
| } |
| |
| SetLogHandler(LogHandler); |
| google::protobuf::io::FileInputStream input(fd); |
| input.SetCloseOnDelete(true); |
| |
| /* Use static so we can get c_str() pointers, and copy them into the nsjconf struct */ |
| static nsjail::NsJailConfig nsc; |
| |
| auto parser = google::protobuf::TextFormat::Parser(); |
| if (!parser.Parse(&input, &nsc)) { |
| LOG_W("Couldn't parse file '%s' from Text into ProtoBuf", file); |
| return false; |
| } |
| if (!configParseInternal(nsjconf, nsc)) { |
| LOG_W("Couldn't parse the ProtoBuf from '%s'", file); |
| return false; |
| } |
| |
| LOG_D("Parsed config from '%s':\n'%s'", file, nsc.DebugString().c_str()); |
| return true; |
| } |
| |
| } // namespace config |