summaryrefslogtreecommitdiffstats
path: root/sandbox
diff options
context:
space:
mode:
authorjln <jln@chromium.org>2014-11-24 18:32:30 -0800
committerCommit bot <commit-bot@chromium.org>2014-11-25 02:32:52 +0000
commitbd4df41c57cbfa415675602e0befc8ca128a009a (patch)
treea8ea9fc404f9f153fd0fc364210be23426cbccd2 /sandbox
parent164f18e2da53ac9da9c360749c7de859d5975089 (diff)
downloadchromium_src-bd4df41c57cbfa415675602e0befc8ca128a009a.zip
chromium_src-bd4df41c57cbfa415675602e0befc8ca128a009a.tar.gz
chromium_src-bd4df41c57cbfa415675602e0befc8ca128a009a.tar.bz2
Linux sandbox: change API to start the sandbox
This CL makes SupportsSandbox() more straightforward by returning the level of support in the kernel. One major advantage is that being single threaded is now checked right before engaging the sandbox. Now, StartSandbox() is required to be able to check the number of threads in the current process. For this the LinuxSandbox class in content/ and in nacl/ are modified to always pass a file descriptor to /proc/self/tasks/ to SandboxBPF::StartSandbox(). In content::LinuxSandbox, such a descriptor was only available in DEBUG builds for security reasons. We make sure to always close it, as long as InitializeSandbox() is called. However, a few fringe processes, such as the init process or the ASAN coverage helper need to close it manually. BUG=434820 Review URL: https://codereview.chromium.org/733303004 Cr-Commit-Position: refs/heads/master@{#305569}
Diffstat (limited to 'sandbox')
-rw-r--r--sandbox/linux/BUILD.gn1
-rw-r--r--sandbox/linux/bpf_dsl/bpf_dsl_more_unittest.cc10
-rw-r--r--sandbox/linux/seccomp-bpf/sandbox_bpf.cc110
-rw-r--r--sandbox/linux/seccomp-bpf/sandbox_bpf.h23
-rw-r--r--sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc9
-rw-r--r--sandbox/linux/services/thread_helpers.cc3
6 files changed, 51 insertions, 105 deletions
diff --git a/sandbox/linux/BUILD.gn b/sandbox/linux/BUILD.gn
index 0f8f86a..382c396 100644
--- a/sandbox/linux/BUILD.gn
+++ b/sandbox/linux/BUILD.gn
@@ -173,6 +173,7 @@ component("seccomp_bpf") {
defines = [ "SANDBOX_IMPLEMENTATION" ]
deps = [
+ ":sandbox_services",
":sandbox_services_headers",
"//base",
]
diff --git a/sandbox/linux/bpf_dsl/bpf_dsl_more_unittest.cc b/sandbox/linux/bpf_dsl/bpf_dsl_more_unittest.cc
index 66669e7..5d1809e 100644
--- a/sandbox/linux/bpf_dsl/bpf_dsl_more_unittest.cc
+++ b/sandbox/linux/bpf_dsl/bpf_dsl_more_unittest.cc
@@ -81,7 +81,7 @@ TEST(SandboxBPF, DISABLE_ON_TSAN(CallSupports)) {
// We check that we don't crash, but it's ok if the kernel doesn't
// support it.
bool seccomp_bpf_supported =
- SandboxBPF::SupportsSeccompSandbox(-1) == SandboxBPF::STATUS_AVAILABLE;
+ SandboxBPF::SupportsSeccompSandbox() == SandboxBPF::STATUS_AVAILABLE;
// We want to log whether or not seccomp BPF is actually supported
// since actual test coverage depends on it.
RecordProperty("SeccompBPFSupported",
@@ -93,8 +93,8 @@ TEST(SandboxBPF, DISABLE_ON_TSAN(CallSupports)) {
}
SANDBOX_TEST(SandboxBPF, DISABLE_ON_TSAN(CallSupportsTwice)) {
- SandboxBPF::SupportsSeccompSandbox(-1);
- SandboxBPF::SupportsSeccompSandbox(-1);
+ SandboxBPF::SupportsSeccompSandbox();
+ SandboxBPF::SupportsSeccompSandbox();
}
// BPF_TEST does a lot of the boiler-plate code around setting up a
@@ -131,7 +131,7 @@ class VerboseAPITestingPolicy : public Policy {
};
SANDBOX_TEST(SandboxBPF, DISABLE_ON_TSAN(VerboseAPITesting)) {
- if (SandboxBPF::SupportsSeccompSandbox(-1) ==
+ if (SandboxBPF::SupportsSeccompSandbox() ==
sandbox::SandboxBPF::STATUS_AVAILABLE) {
static int counter = 0;
@@ -2069,7 +2069,7 @@ class TraceAllPolicy : public Policy {
};
SANDBOX_TEST(SandboxBPF, DISABLE_ON_TSAN(SeccompRetTrace)) {
- if (SandboxBPF::SupportsSeccompSandbox(-1) !=
+ if (SandboxBPF::SupportsSeccompSandbox() !=
sandbox::SandboxBPF::STATUS_AVAILABLE) {
return;
}
diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf.cc b/sandbox/linux/seccomp-bpf/sandbox_bpf.cc
index 43c9af6..1894850 100644
--- a/sandbox/linux/seccomp-bpf/sandbox_bpf.cc
+++ b/sandbox/linux/seccomp-bpf/sandbox_bpf.cc
@@ -42,6 +42,7 @@
#include "sandbox/linux/seccomp-bpf/verifier.h"
#include "sandbox/linux/services/linux_syscalls.h"
#include "sandbox/linux/services/syscall_wrappers.h"
+#include "sandbox/linux/services/thread_helpers.h"
using sandbox::bpf_dsl::Allow;
using sandbox::bpf_dsl::Error;
@@ -122,33 +123,19 @@ void TryVsyscallProcess(void) {
}
}
-bool IsSingleThreaded(int proc_fd) {
- if (proc_fd < 0) {
- // Cannot determine whether program is single-threaded. Hope for
- // the best...
- return true;
- }
-
- struct stat sb;
- int task = -1;
- if ((task = openat(proc_fd, "self/task", O_RDONLY | O_DIRECTORY)) < 0 ||
- fstat(task, &sb) != 0 || sb.st_nlink != 3 || IGNORE_EINTR(close(task))) {
- if (task >= 0) {
- if (IGNORE_EINTR(close(task))) {
- }
- }
- return false;
- }
- return true;
+bool IsSingleThreaded(int proc_task_fd) {
+ return ThreadHelpers::IsSingleThreaded(proc_task_fd);
}
} // namespace
SandboxBPF::SandboxBPF()
- : quiet_(false), proc_fd_(-1), sandbox_has_started_(false), policy_() {
+ : quiet_(false), proc_task_fd_(-1), sandbox_has_started_(false), policy_() {
}
SandboxBPF::~SandboxBPF() {
+ if (proc_task_fd_ != -1)
+ IGNORE_EINTR(close(proc_task_fd_));
}
bool SandboxBPF::IsValidSyscallNumber(int sysnum) {
@@ -175,7 +162,7 @@ bool SandboxBPF::RunFunctionInPolicy(void (*code_in_sandbox)(),
// This code is using fork() and should only ever run single-threaded.
// Most of the code below is "async-signal-safe" and only minor changes
// would be needed to support threads.
- DCHECK(IsSingleThreaded(proc_fd_));
+ DCHECK(IsSingleThreaded(proc_task_fd_));
pid_t pid = fork();
if (pid < 0) {
// Die if we cannot fork(). We would probably fail a little later
@@ -282,58 +269,28 @@ bool SandboxBPF::KernelSupportSeccompBPF() {
}
// static
-SandboxBPF::SandboxStatus SandboxBPF::SupportsSeccompSandbox(int proc_fd) {
- // It the sandbox is currently active, we clearly must have support for
- // sandboxing.
- if (status_ == STATUS_ENABLED) {
- return status_;
- }
-
- // Even if the sandbox was previously available, something might have
- // changed in our run-time environment. Check one more time.
- if (status_ == STATUS_AVAILABLE) {
- if (!IsSingleThreaded(proc_fd)) {
- status_ = STATUS_UNAVAILABLE;
- }
- return status_;
- }
-
- if (status_ == STATUS_UNAVAILABLE && IsSingleThreaded(proc_fd)) {
- // All state transitions resulting in STATUS_UNAVAILABLE are immediately
- // preceded by STATUS_AVAILABLE. Furthermore, these transitions all
- // happen, if and only if they are triggered by the process being multi-
- // threaded.
- // In other words, if a single-threaded process is currently in the
- // STATUS_UNAVAILABLE state, it is safe to assume that sandboxing is
- // actually available.
- status_ = STATUS_AVAILABLE;
+SandboxBPF::SandboxStatus SandboxBPF::SupportsSeccompSandbox() {
+ if (status_ != STATUS_UNKNOWN) {
return status_;
}
// If we have not previously checked for availability of the sandbox or if
// we otherwise don't believe to have a good cached value, we have to
// perform a thorough check now.
- if (status_ == STATUS_UNKNOWN) {
- // We create our own private copy of a "Sandbox" object. This ensures that
- // the object does not have any policies configured, that might interfere
- // with the tests done by "KernelSupportSeccompBPF()".
- SandboxBPF sandbox;
-
- // By setting "quiet_ = true" we suppress messages for expected and benign
- // failures (e.g. if the current kernel lacks support for BPF filters).
- sandbox.quiet_ = true;
- sandbox.set_proc_fd(proc_fd);
- status_ = sandbox.KernelSupportSeccompBPF() ? STATUS_AVAILABLE
- : STATUS_UNSUPPORTED;
-
- // As we are performing our tests from a child process, the run-time
- // environment that is visible to the sandbox is always guaranteed to be
- // single-threaded. Let's check here whether the caller is single-
- // threaded. Otherwise, we mark the sandbox as temporarily unavailable.
- if (status_ == STATUS_AVAILABLE && !IsSingleThreaded(proc_fd)) {
- status_ = STATUS_UNAVAILABLE;
- }
- }
+
+ // We create our own private copy of a "Sandbox" object. This ensures that
+ // the object does not have any policies configured, that might interfere
+ // with the tests done by "KernelSupportSeccompBPF()".
+ SandboxBPF sandbox;
+
+ // By setting "quiet_ = true" we suppress messages for expected and benign
+ // failures (e.g. if the current kernel lacks support for BPF filters).
+ // TODO(jln): use kernel API to check for seccomp support now that things
+ // have stabilized.
+ sandbox.quiet_ = true;
+ status_ =
+ sandbox.KernelSupportSeccompBPF() ? STATUS_AVAILABLE : STATUS_UNSUPPORTED;
+
return status_;
}
@@ -355,7 +312,9 @@ SandboxBPF::SupportsSeccompThreadFilterSynchronization() {
}
}
-void SandboxBPF::set_proc_fd(int proc_fd) { proc_fd_ = proc_fd; }
+void SandboxBPF::set_proc_task_fd(int proc_task_fd) {
+ proc_task_fd_ = proc_task_fd;
+}
bool SandboxBPF::StartSandbox(SandboxThreadState thread_state) {
CHECK(thread_state == PROCESS_SINGLE_THREADED ||
@@ -372,24 +331,17 @@ bool SandboxBPF::StartSandbox(SandboxThreadState thread_state) {
"object instead.");
return false;
}
- if (proc_fd_ < 0) {
- proc_fd_ = open("/proc", O_RDONLY | O_DIRECTORY);
- }
- if (proc_fd_ < 0) {
- // For now, continue in degraded mode, if we can't access /proc.
- // In the future, we might want to tighten this requirement.
- }
bool supports_tsync =
SupportsSeccompThreadFilterSynchronization() == STATUS_AVAILABLE;
if (thread_state == PROCESS_SINGLE_THREADED) {
- if (!IsSingleThreaded(proc_fd_)) {
+ if (!IsSingleThreaded(proc_task_fd_)) {
SANDBOX_DIE("Cannot start sandbox; process is already multi-threaded");
return false;
}
} else if (thread_state == PROCESS_MULTI_THREADED) {
- if (IsSingleThreaded(proc_fd_)) {
+ if (IsSingleThreaded(proc_task_fd_)) {
SANDBOX_DIE("Cannot start sandbox; "
"process may be single-threaded when reported as not");
return false;
@@ -404,12 +356,12 @@ bool SandboxBPF::StartSandbox(SandboxThreadState thread_state) {
// We no longer need access to any files in /proc. We want to do this
// before installing the filters, just in case that our policy denies
// close().
- if (proc_fd_ >= 0) {
- if (IGNORE_EINTR(close(proc_fd_))) {
+ if (proc_task_fd_ >= 0) {
+ if (IGNORE_EINTR(close(proc_task_fd_))) {
SANDBOX_DIE("Failed to close file descriptor for /proc");
return false;
}
- proc_fd_ = -1;
+ proc_task_fd_ = -1;
}
// Install the filters.
diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf.h b/sandbox/linux/seccomp-bpf/sandbox_bpf.h
index a7f2d5f..21dcfae 100644
--- a/sandbox/linux/seccomp-bpf/sandbox_bpf.h
+++ b/sandbox/linux/seccomp-bpf/sandbox_bpf.h
@@ -59,23 +59,22 @@ class SANDBOX_EXPORT SandboxBPF {
// system calls.
static bool IsValidSyscallNumber(int sysnum);
- // There are a lot of reasons why the Seccomp sandbox might not be available.
- // This could be because the kernel does not support Seccomp mode, or it
- // could be because another sandbox is already active.
- // "proc_fd" should be a file descriptor for "/proc", or -1 if not
- // provided by the caller.
- static SandboxStatus SupportsSeccompSandbox(int proc_fd);
+ // Detect if the kernel supports the seccomp sandbox. The result of calling
+ // this function will be cached. The first time this function is called, the
+ // running process must be unsandboxed (able to use /proc) and monothreaded.
+ static SandboxStatus SupportsSeccompSandbox();
// Determines if the kernel has support for the seccomp() system call to
// synchronize BPF filters across a thread group.
static SandboxStatus SupportsSeccompThreadFilterSynchronization();
- // The sandbox needs to be able to access files in "/proc/self". If this
- // directory is not accessible when "startSandbox()" gets called, the caller
- // can provide an already opened file descriptor by calling "set_proc_fd()".
+ // The sandbox needs to be able to access files in "/proc/self/task/". If
+ // this directory is not accessible when "startSandbox()" gets called, the
+ // caller must provide an already opened file descriptor by calling
+ // "set_proc_task_fd()".
// The sandbox becomes the new owner of this file descriptor and will
// eventually close it when "StartSandbox()" executes.
- void set_proc_fd(int proc_fd);
+ void set_proc_task_fd(int proc_task_fd);
// Set the BPF policy as |policy|. Ownership of |policy| is transfered here
// to the sandbox object.
@@ -122,7 +121,7 @@ class SANDBOX_EXPORT SandboxBPF {
private:
// Get a file descriptor pointing to "/proc", if currently available.
- int proc_fd() { return proc_fd_; }
+ int proc_task_fd() { return proc_task_fd_; }
// Creates a subprocess and runs "code_in_sandbox" inside of the specified
// policy. The caller has to make sure that "this" has not yet been
@@ -148,7 +147,7 @@ class SANDBOX_EXPORT SandboxBPF {
static SandboxStatus status_;
bool quiet_;
- int proc_fd_;
+ int proc_task_fd_;
bool sandbox_has_started_;
scoped_ptr<bpf_dsl::Policy> policy_;
diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc b/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc
index 59ee26f..cfdb69f 100644
--- a/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc
+++ b/sandbox/linux/seccomp-bpf/sandbox_bpf_test_runner.cc
@@ -31,17 +31,10 @@ void SandboxBPFTestRunner::Run() {
scoped_ptr<bpf_dsl::Policy> policy =
bpf_tester_delegate_->GetSandboxBPFPolicy();
- if (sandbox::SandboxBPF::SupportsSeccompSandbox(-1) ==
+ if (sandbox::SandboxBPF::SupportsSeccompSandbox() ==
sandbox::SandboxBPF::STATUS_AVAILABLE) {
- // Ensure the the sandbox is actually available at this time
- int proc_fd;
- SANDBOX_ASSERT((proc_fd = open("/proc", O_RDONLY | O_DIRECTORY)) >= 0);
- SANDBOX_ASSERT(sandbox::SandboxBPF::SupportsSeccompSandbox(proc_fd) ==
- sandbox::SandboxBPF::STATUS_AVAILABLE);
-
// Initialize and then start the sandbox with our custom policy
sandbox::SandboxBPF sandbox;
- sandbox.set_proc_fd(proc_fd);
sandbox.SetSandboxPolicy(policy.release());
SANDBOX_ASSERT(
sandbox.StartSandbox(sandbox::SandboxBPF::PROCESS_SINGLE_THREADED));
diff --git a/sandbox/linux/services/thread_helpers.cc b/sandbox/linux/services/thread_helpers.cc
index e820449..dbadbd4 100644
--- a/sandbox/linux/services/thread_helpers.cc
+++ b/sandbox/linux/services/thread_helpers.cc
@@ -43,7 +43,8 @@ bool IsSingleThreadedImpl(int proc_self_task) {
bool ThreadHelpers::IsSingleThreaded(int proc_self_task) {
DCHECK_LE(-1, proc_self_task);
if (-1 == proc_self_task) {
- const int task_fd = open("/proc/self/task/", O_RDONLY | O_DIRECTORY);
+ const int task_fd =
+ open("/proc/self/task/", O_RDONLY | O_DIRECTORY | O_CLOEXEC);
PCHECK(0 <= task_fd);
const bool result = IsSingleThreadedImpl(task_fd);
PCHECK(0 == IGNORE_EINTR(close(task_fd)));