diff --git a/cmdline.c b/cmdline.c index aedbdab..aed4d1f 100644 --- a/cmdline.c +++ b/cmdline.c @@ -36,6 +36,8 @@ #include #include #include +#include +#include #include #include @@ -63,6 +65,7 @@ struct custom_option custom_opts[] = { "\tr: Launch a single process on the console with clone/execve, keep doing it forever [MODE_STANDALONE_RERUN]" }, { { "config", required_argument, NULL, 'C' }, "Configuration file in the config.proto ProtoBuf format (see configs/ directory for examples)" }, { { "exec_file", required_argument, NULL, 'x' }, "File to exec (default: argv[0])" }, + { { "execute_fd", no_argument, NULL, 0x0607 }, "Use execveat() to execute a file-descriptor instead of executing the binary path. In such case argv[0]/exec_file denotes a file path before mount namespacing" }, { { "chroot", required_argument, NULL, 'c' }, "Directory containing / of the jail (default: none)" }, { { "rw", no_argument, NULL, 0x601 }, "Mount chroot dir (/) R/W (default: R/O)" }, { { "user", required_argument, NULL, 'u' }, "Username/uid of processess inside the jail (default: your current uid). You can also use inside_ns_uid:outside_ns_uid:count convention here. Can be specified multiple times" }, @@ -312,10 +315,12 @@ bool cmdlineParse(int argc, char* argv[], struct nsjconf_t* nsjconf) { (*nsjconf) = (const struct nsjconf_t){ .exec_file = NULL, + .use_execveat = false, + .exec_fd = -1, + .argv = NULL, .hostname = "NSJAIL", .cwd = "/", .chroot = NULL, - .argv = NULL, .port = 0, .bindhost = "::", .log_fd = STDERR_FILENO, @@ -556,7 +561,7 @@ bool cmdlineParse(int argc, char* argv[], struct nsjconf_t* nsjconf) case 0x0508: nsjconf->max_cpus = strtoul(optarg, NULL, 0); break; - case 0x509: { + case 0x0509: { struct ints_t* f = utilMalloc(sizeof(struct ints_t)); f->val = capsNameToVal(optarg); if (f->val == -1) { @@ -581,6 +586,9 @@ bool cmdlineParse(int argc, char* argv[], struct nsjconf_t* nsjconf) case 0x0606: nsjconf->is_proc_rw = true; break; + case 0x0607: + nsjconf->use_execveat = true; + break; case 'E': { struct charptr_t* p = utilMalloc(sizeof(struct charptr_t)); p->val = optarg; @@ -799,5 +807,18 @@ bool cmdlineParse(int argc, char* argv[], struct nsjconf_t* nsjconf) nsjconf->exec_file = nsjconf->argv[0]; } + if (nsjconf->use_execveat) { +#if !defined(__NR_execveat) + LOG_E("Your nsjail is compiled without support for the execveat() syscall, yet you " + "specified --execute_fd flag"); + return false; +#endif /* !defined(__NR_execveat) */ + if ((nsjconf->exec_fd = open(nsjconf->exec_file, O_RDONLY | O_PATH | O_CLOEXEC)) + == -1) { + PLOG_W("Couldn't open '%s' file", nsjconf->exec_file); + return false; + } + } + return true; } diff --git a/config.cc b/config.cc index 95f4270..91fa959 100644 --- a/config.cc +++ b/config.cc @@ -296,6 +296,7 @@ static bool configParseInternal(struct nsjconf_t* nsjconf, const nsjail::NsJailC argv.push_back(nullptr); nsjconf->exec_file = DUP_IF_SET(njc.exec_bin(), path); nsjconf->argv = argv.data(); + nsjconf->use_execveat = njc.exec_bin().exec_fd(); } return true; diff --git a/config.proto b/config.proto index 9bd04bc..3c727f3 100644 --- a/config.proto +++ b/config.proto @@ -68,6 +68,8 @@ message Exe repeated string arg = 2; /* Override argv[0] */ optional string arg0 = 3; + /* Should execveat() be used to execute a file-descriptor instead? */ + optional bool exec_fd = 4 [ default = false ]; } message NsJailConfig { diff --git a/configs/busybox-with-execveat.cfg b/configs/busybox-with-execveat.cfg new file mode 100644 index 0000000..af2402f --- /dev/null +++ b/configs/busybox-with-execveat.cfg @@ -0,0 +1,48 @@ +name: "busybox-with-execveat" +description: "An example/demo policy which allows to execute /bin/busybox in an empty (only /proc) " +description: "mount namespace which doesn't even include busybox itself." + +mode: ONCE +hostname: "BUSYBOX" +cwd: "/" + +time_limit: 100 + +keep_env: false +envar: "TERM=linux" +envar: "PS1=$ " + +skip_setsid: true + +clone_newcgroup: true + +uidmap { + inside_id: "999999" + outside_id: "" + count: 1 +} + +gidmap { + inside_id: "999999" + outside_id: "" + count: 1 +} + +mount_proc: false + +mount { + dst: "/proc" + fstype: "proc" + rw: false +} + +seccomp_string: "POLICY example { " +seccomp_string: " ERRNO(0) { ptrace } " +seccomp_string: "} " +seccomp_string: "USE example DEFAULT ALLOW" + +exec_bin { + path: "/bin/busybox" + arg: "sh" + exec_fd: true +} diff --git a/nsjail.h b/nsjail.h index d5f894f..b094acf 100644 --- a/nsjail.h +++ b/nsjail.h @@ -105,9 +105,12 @@ enum llevel_t { struct nsjconf_t { const char* exec_file; + bool use_execveat; + int exec_fd; + const char** argv; const char* hostname; const char* cwd; - const char** argv; + const char* chroot; int port; const char* bindhost; int log_fd; @@ -135,7 +138,6 @@ struct nsjconf_t { bool clone_newuts; bool clone_newcgroup; enum ns_mode_t mode; - const char* chroot; bool is_root_rw; bool is_silent; bool skip_setsid; diff --git a/subproc.c b/subproc.c index 61c2726..5d2cd48 100644 --- a/subproc.c +++ b/subproc.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -170,7 +171,17 @@ static int subprocNewProc(struct nsjconf_t* nsjconf, int fd_in, int fd_out, int if (sandboxApply(nsjconf) == false) { exit(0xff); } - execv(nsjconf->exec_file, (char* const*)&nsjconf->argv[0]); + + if (nsjconf->use_execveat) { +#if defined(__NR_execveat) + syscall(__NR_execveat, (uintptr_t)nsjconf->exec_fd, "", + (char* const*)&nsjconf->argv[0], environ, (uintptr_t)AT_EMPTY_PATH); +#else /* defined(__NR_execveat) */ + LOG_F("Your system doesn't support execveat() syscall"); +#endif /* defined(__NR_execveat) */ + } else { + execv(nsjconf->exec_file, (char* const*)&nsjconf->argv[0]); + } PLOG_E("execve('%s') failed", nsjconf->exec_file);