blob: 5cae41319996582d4c8a1f6926cab3f64a23facb [file] [log] [blame]
// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Some portions Copyright (c) 2011 The Chromium Authors.
//
// Driver program for applying a minijail from the commandline to
// a process and its children (depending on the feature).
#include "minijail/minijail.h"
#include <errno.h>
#include <grp.h>
#include <linux/capability.h>
#include <pwd.h>
#include <stdio.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <unistd.h>
#include <iostream>
#include <new>
#include <string>
#include <vector>
#include <base/basictypes.h>
#include <base/command_line.h>
#include <base/logging.h>
#include <base/string_number_conversions.h>
#include <base/string_util.h>
namespace switches {
static const char kAddReadonlyMounts[] = "add-readonly-mounts";
static const char kDisableTracing[] = "disable-tracing";
static const char kEnforceSyscallsBenchmark[] = "enforce-syscall-benchmark";
static const char kEnforceSyscallsBySource[] = "enforce-syscall-by-source";
static const char kGid[] = "gid";
static const char kNamespaceVfs[] = "namespace-vfs";
static const char kNamespacePid[] = "namespace-pid";
static const char kSanitizeEnvironment[] = "sanitize-environment";
static const char kUid[] = "uid";
static const char kUseCapabilities[] = "use-capabilities";
static const char kHelp[] = "help";
static const char kHelpMessage[] = "Available Switches:\n"
" --add-readonly-mounts\n"
" Mounts a read-only /proc. (implies namespace-vfs)\n"
" (TODO other read-only/special mounts)\n"
" --disable-tracing\n"
" Disables ptrace() and core dumps.\n"
" This may break debugging helpers\n"
" --enforce-syscall-benchmark\n"
" Runs system call filtering in a pass-through capacity only for\n"
" benchmarking\n"
" --enforce-syscall-by-source\n"
" Enables kernel enforcement that system calls originate from read-only\n"
" memory areas\n"
" --gid [number]\n"
" Numeric gid to transition to prior to execution.\n"
" (TODO: Supplemental groups will be cleared.)\n"
" --namespace-vfs\n"
" Enables a process-tree specific VFS view.\n"
" --namespace-pid\n"
" Makes the executed process into procss id 1 in its own process view.\n"
" With --add-readonly-mounts, other processes will not be visible\n"
" --sanitize-environment\n"
" Scrubs the environment clean of potentially dangerous values.\n"
" (Note, this is a blacklist and not a whitelist so it may need attention)\n"
" --uid [number]\n"
" Numeric uid to transition to prior to execution.\n"
" --use-capabilities [uint64 bitmask]\n"
" Restricts all root-level capabilities to CAP_SETPCAP and enables\n"
" SECURE_NOROOT.\n"
" -- /path/to/program [arg1 [arg2 [ . . . ] ] ]\n"
" Supplies the required program to execute and its arguments.\n"
" At present, an empty environment will be passed.\n"
"\n";
} // namespace switches
static bool ParseUid(const std::string& str, uid_t *uid) {
int32 v;
if (base::StringToInt(str, &v)) {
*uid = v;
return true;
}
// Not an integer. Let's try for a user.
// Any character except ':' is valid in a username.
if (strchr(str.c_str(), ':'))
return false;
struct passwd *user = getpwnam(str.c_str());
if (user) {
*uid = user->pw_uid;
return true;
}
return false;
}
static bool ParseGid(const std::string& str, gid_t *gid) {
int32 v;
if (base::StringToInt(str, &v)) {
*gid = v;
return true;
}
// Not an integer, look for a group
// Any character except ':' is valid in a group name.
if (strchr(str.c_str(), ':'))
return false;
struct group *group = getgrnam(str.c_str());
if (group) {
*gid = group->gr_gid;
return true;
}
return false;
}
static void ProcessSwitches(CommandLine *cl,
chromeos::MiniJailOptions *jail_opts) {
if (cl->HasSwitch(switches::kHelp)) {
std::cerr << switches::kHelpMessage;
exit(0);
}
// Configure the jail options
jail_opts->set_namespace_pid(cl->HasSwitch(switches::kNamespacePid));
jail_opts->set_namespace_vfs(cl->HasSwitch(switches::kNamespaceVfs));
jail_opts->set_add_readonly_mounts(
cl->HasSwitch(switches::kAddReadonlyMounts));
jail_opts->set_disable_tracing(cl->HasSwitch(switches::kDisableTracing));
jail_opts->set_enforce_syscalls_benchmark(
cl->HasSwitch(switches::kEnforceSyscallsBenchmark));
jail_opts->set_enforce_syscalls_by_source(
cl->HasSwitch(switches::kEnforceSyscallsBySource));
jail_opts->set_use_capabilities(cl->HasSwitch(switches::kUseCapabilities));
jail_opts->set_sanitize_environment(
cl->HasSwitch(switches::kSanitizeEnvironment));
if (jail_opts->use_capabilities()) {
jail_opts->set_caps_bitmask(0);
// TODO(cmasone): switch to something that parses unsigned ints.
int64 caps = 0;
if (base::StringToInt64(
cl->GetSwitchValueASCII(switches::kUseCapabilities), &caps)) {
uint64 bitmask = (caps < 0 ? 0 : caps);
jail_opts->set_caps_bitmask(bitmask);
}
}
std::string uid_string = cl->GetSwitchValueASCII(switches::kUid);
if (!uid_string.empty()) {
uid_t uid;
if (!ParseUid(uid_string.c_str(), &uid)) {
LOG(ERROR) << "Failed to parse uid: " << uid_string;
exit(1);
}
jail_opts->set_uid(uid);
}
std::string gid_string = cl->GetSwitchValueASCII(switches::kGid);
if (!gid_string.empty()) {
gid_t gid;
if (!ParseGid(gid_string.c_str(), &gid)) {
LOG(ERROR) << "Failed to parse gid: " << gid_string;
exit(1);
}
jail_opts->set_gid(gid);
}
if (!jail_opts->FixUpDependencies()) {
LOG(FATAL) << "Irreconcilable jail options given. Aborting.";
}
// Grab the loose args to use as the command line.
// We have to wstring->argv[][] manually. Ugh.
std::vector<std::string> loose_args = cl->args();
char const* *jailed_argv = new char const*[loose_args.size() + 1];
std::vector<std::string>::const_iterator arg_it = loose_args.begin();
char const* *ja = jailed_argv;
for (; arg_it != loose_args.end(); ++arg_it) {
// XXX: clean up this leak even though it doesn't matter.
*ja++ = strdup(arg_it->c_str());
}
*ja = 0;
jail_opts->set_executable_path(jailed_argv[0]);
jail_opts->set_arguments(const_cast<char * const*>(jailed_argv),
loose_args.size());
// XXX We just leak this since we're going to exec anyhow.
// delete jailed_argv;
}
int main(int argc, char *argv[], char **envp) {
CommandLine::Init(argc, argv);
logging::InitLogging(NULL,
logging::LOG_ONLY_TO_SYSTEM_DEBUG_LOG,
logging::DONT_LOCK_LOG_FILE,
logging::APPEND_TO_OLD_LOG_FILE,
logging::DISABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS);
chromeos::MiniJailOptions jail_opts;
CommandLine *cl = CommandLine::ForCurrentProcess();
ProcessSwitches(cl, &jail_opts);
jail_opts.set_environment(envp);
LOG_IF(FATAL, !jail_opts.executable_path()) << "No executable given";
chromeos::MiniJail jail;
jail.Initialize(&jail_opts);
bool ok = jail.Jail() && jail.Run();
return !ok;
}