Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(266)

Unified Diff: sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc

Issue 11411254: SECCOMP-BPF: Added supported for inspection system call arguments from BPF filters. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Changelist is ready for initial review; still needs a few TODO()s to be resolved, though. Created 8 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc
diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc b/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc
index 365092ed4052e21f011997a6912fa2211339ae63..b9cf66ef22c2222302ffd242d543b0c4e0a82c2e 100644
--- a/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc
+++ b/sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc
@@ -8,6 +8,7 @@
#include <ostream>
#include "sandbox/linux/seccomp-bpf/bpf_tests.h"
+#include "sandbox/linux/seccomp-bpf/syscall.h"
#include "sandbox/linux/seccomp-bpf/verifier.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -23,7 +24,7 @@ TEST(SandboxBpf, CallSupports) {
// We check that we don't crash, but it's ok if the kernel doesn't
// support it.
bool seccomp_bpf_supported =
- Sandbox::supportsSeccompSandbox(-1) == Sandbox::STATUS_AVAILABLE;
+ Sandbox::SupportsSeccompSandbox(-1) == Sandbox::STATUS_AVAILABLE;
// We want to log whether or not seccomp BPF is actually supported
// since actual test coverage depends on it.
RecordProperty("SeccompBPFSupported",
@@ -36,8 +37,8 @@ TEST(SandboxBpf, CallSupports) {
}
SANDBOX_TEST(SandboxBpf, CallSupportsTwice) {
- Sandbox::supportsSeccompSandbox(-1);
- Sandbox::supportsSeccompSandbox(-1);
+ Sandbox::SupportsSeccompSandbox(-1);
+ Sandbox::SupportsSeccompSandbox(-1);
}
// BPF_TEST does a lot of the boiler-plate code around setting up a
@@ -54,7 +55,7 @@ intptr_t FakeGetPid(const struct arch_seccomp_data& args, void *aux) {
}
ErrorCode VerboseAPITestingPolicy(int sysno, void *aux) {
- if (!Sandbox::isValidSyscallNumber(sysno)) {
+ if (!Sandbox::IsValidSyscallNumber(sysno)) {
return ErrorCode(ENOSYS);
} else if (sysno == __NR_getpid) {
return Sandbox::Trap(FakeGetPid, aux);
@@ -64,11 +65,11 @@ ErrorCode VerboseAPITestingPolicy(int sysno, void *aux) {
}
SANDBOX_TEST(SandboxBpf, VerboseAPITesting) {
- if (Sandbox::supportsSeccompSandbox(-1) ==
+ if (Sandbox::SupportsSeccompSandbox(-1) ==
playground2::Sandbox::STATUS_AVAILABLE) {
pid_t test_var = 0;
- playground2::Sandbox::setSandboxPolicy(VerboseAPITestingPolicy, &test_var);
- playground2::Sandbox::startSandbox();
+ playground2::Sandbox::SetSandboxPolicy(VerboseAPITestingPolicy, &test_var);
+ playground2::Sandbox::StartSandbox();
BPF_ASSERT(test_var == 0);
BPF_ASSERT(syscall(__NR_getpid) == 0);
@@ -85,7 +86,7 @@ SANDBOX_TEST(SandboxBpf, VerboseAPITesting) {
// A simple blacklist test
ErrorCode BlacklistNanosleepPolicy(int sysno, void *) {
- if (!Sandbox::isValidSyscallNumber(sysno)) {
+ if (!Sandbox::IsValidSyscallNumber(sysno)) {
// FIXME: we should really not have to do that in a trivial policy
return ErrorCode(ENOSYS);
}
@@ -139,7 +140,7 @@ intptr_t EnomemHandler(const struct arch_seccomp_data& args, void *aux) {
}
ErrorCode BlacklistNanosleepPolicySigsys(int sysno, void *aux) {
- if (!Sandbox::isValidSyscallNumber(sysno)) {
+ if (!Sandbox::IsValidSyscallNumber(sysno)) {
// FIXME: we should really not have to do that in a trivial policy
return ErrorCode(ENOSYS);
}
@@ -185,7 +186,7 @@ int SysnoToRandomErrno(int sysno) {
}
ErrorCode SyntheticPolicy(int sysno, void *) {
- if (!Sandbox::isValidSyscallNumber(sysno)) {
+ if (!Sandbox::IsValidSyscallNumber(sysno)) {
// FIXME: we should really not have to do that in a trivial policy
return ErrorCode(ENOSYS);
}
@@ -243,7 +244,7 @@ int ArmPrivateSysnoToErrno(int sysno) {
}
ErrorCode ArmPrivatePolicy(int sysno, void *) {
- if (!Sandbox::isValidSyscallNumber(sysno)) {
+ if (!Sandbox::IsValidSyscallNumber(sysno)) {
// FIXME: we should really not have to do that in a trivial policy.
return ErrorCode(ENOSYS);
}
@@ -305,7 +306,7 @@ ErrorCode GreyListedPolicy(int sysno, void *aux) {
} else if (sysno == __NR_getpid) {
// Disallow getpid()
return ErrorCode(EPERM);
- } else if (Sandbox::isValidSyscallNumber(sysno)) {
+ } else if (Sandbox::IsValidSyscallNumber(sysno)) {
// Allow (and count) all other system calls.
return Sandbox::UnsafeTrap(CountSyscalls, aux);
} else {
@@ -344,7 +345,7 @@ ErrorCode PrctlPolicy(int sysno, void *aux) {
if (sysno == __NR_prctl) {
// Handle prctl() inside an UnsafeTrap()
return Sandbox::UnsafeTrap(PrctlHandler, NULL);
- } else if (Sandbox::isValidSyscallNumber(sysno)) {
+ } else if (Sandbox::IsValidSyscallNumber(sysno)) {
// Allow all other system calls.
return ErrorCode(ErrorCode::ERR_ALLOWED);
} else {
@@ -394,7 +395,7 @@ ErrorCode RedirectAllSyscallsPolicy(int sysno, void *aux) {
#endif
) {
return ErrorCode(ErrorCode::ERR_ALLOWED);
- } else if (Sandbox::isValidSyscallNumber(sysno)) {
+ } else if (Sandbox::IsValidSyscallNumber(sysno)) {
return Sandbox::UnsafeTrap(AllowRedirectedSyscall, aux);
} else {
return ErrorCode(ENOSYS);
@@ -480,4 +481,301 @@ BPF_TEST(SandboxBpf, UnsafeTrapWithErrno, RedirectAllSyscallsPolicy) {
BPF_ASSERT(errno == 0);
}
+// This test exercises the Sandbox::Cond() method by building a complex
+// tree of conditional equality operations. It then makes system calls and
+// verifies that they return the values that we expected from our BPF
+// program.
+class EqualityStressTest {
jln (very slow on Chromium) 2012/12/06 01:48:12 I didn't read this yet. But please consider at lea
Markus (顧孟勤) 2012/12/12 20:54:35 I believe that is done now. The tests at the end o
+ public:
+ EqualityStressTest() {
+ // We want a deterministic test
+ srand(0);
+
+ // Iterates over system call numbers and builds a random tree of
+ // equality tests.
+ // We are actually constructing a graph of ArgValue objects. This
+ // graph will later be used to a) compute our sandbox policy, and
+ // b) drive the code that verifies the output from the BPF program.
+ for (int sysno = 0, end = kNumTestCases; sysno < end; ++sysno) {
+ if (IsReservedSyscall(sysno)) {
+ // Skip reserved system calls. This ensures that our test frame
+ // work isn't impacted by the fact that we are overriding
+ // a lot of different system calls.
+ ++end;
+ arg_values_.push_back(NULL);
+ } else {
+ arg_values_.push_back(RandomArgValue(rand() % kMaxArgs, 0,
+ rand() % kMaxArgs));
+ }
+ }
+ }
+
+ ~EqualityStressTest() {
+ for (std::vector<ArgValue *>::iterator iter = arg_values_.begin();
+ iter != arg_values_.end();
+ ++iter) {
+ DeleteArgValue(*iter);
+ }
+ }
+
+ ErrorCode Policy(int sysno) {
+ if (!Sandbox::IsValidSyscallNumber(sysno)) {
+ // FIXME: we should really not have to do that in a trivial policy
+ return ErrorCode(ENOSYS);
+ } else if (sysno < 0 || sysno >= (int)arg_values_.size() ||
+ IsReservedSyscall(sysno)) {
+ // We only return ErrorCode values for the system calls that
+ // are part of our test data. Every other system call remains
+ // allowed.
+ return ErrorCode(ErrorCode::ERR_ALLOWED);
+ } else {
+ // ToErrorCode() turns an ArgValue object into an ErrorCode that is
+ // suitable for use by a sandbox policy.
+ return ToErrorCode(arg_values_[sysno]);
+ }
+ }
+
+ void VerifyFilter() {
+ // Iterate over all system calls. Skip the system calls that have
+ // previously been determined as being reserved.
+ for (int sysno = 0; sysno < (int)arg_values_.size(); ++sysno) {
+ if (!arg_values[sysno]) {
+ // Skip reserved system calls.
+ continue;
+ }
+ // Verify that system calls return the values that we expect them to
+ // return. This involves passing different combinations of system call
+ // parameters in order to exercise all possible code paths through the
+ // BPF filter program.
+ // We arbitrarily start by setting all six system call arguments to
+ // zero. And we then recursive traverse our tree of ArgValues to
+ // determine the necessary combinations of parameters.
+ intptr_t args[6] = { };
+ Verify(sysno, args, *arg_values_[sysno]);
+ }
+ }
+
+ private:
+ struct ArgValue {
+ int argno; // Argument number to inspect.
+ int size; // Number of test cases (must be > 0).
+ struct Tests {
+ uint32_t k_value; // Value to compare syscall arg against.
+ int err; // If non-zero, errno value to return.
+ struct ArgValue *arg_value; // Otherwise, more args needs inspecting.
+ } *tests;
+ int err; // If none of the tests passed, this is what
+ struct ArgValue *arg_value; // we'll return (this is the "else" branch).
+ };
+
+ bool IsReservedSyscall(int sysno) {
+ // There are a handful of system calls that we should never use in our
+ // test cases. These system calls are needed to allow the test framework
+ // to run properly.
+ return sysno == __NR_read ||
+ sysno == __NR_write ||
+ sysno == __NR_exit ||
+ sysno == __NR_exit_group ||
+ sysno == __NR_restart_syscall;
+ }
+
+ ArgValue *RandomArgValue(int argno, int args_mask, int remaining_args) {
+ // Create a new ArgValue and fill it with random data. We use as bit mask
+ // to keep track of the system call parameters that have previously been
+ // set; this ensures that we won't accidentally define a contradictory
+ // set of equality tests.
+ struct ArgValue *arg_value = new ArgValue();
+ args_mask |= 1 << argno;
+ arg_value->argno = argno;
+
+ // Apply some restrictions on just how complex our tests can be.
+ // Otherwise, we end up with a BPF program that is too complicated for
+ // the kernel to load.
+ int fan_out = kMaxFanOut;
+ if (remaining_args > 3) {
+ fan_out = 1;
+ } else if (remaining_args > 2) {
+ fan_out = 2;
+ }
+
+ // Create a couple of different test cases with randomized values that
+ // we want to use when comparing system call parameter number "argno".
+ arg_value->size = rand() % fan_out + 1;
+ arg_value->tests = new ArgValue::Tests[arg_value->size];
+
+ uint32_t k_value = rand();
+ for (int n = 0; n < arg_value->size; ++n) {
+ // Ensure that we have unique values
+ k_value += rand() % (RAND_MAX/(kMaxFanOut+1)) + 1;
+
+ // There are two possible types of nodes. Either this is a leaf node;
+ // in that case, we have completed all the equality tests that we
+ // wanted to perform, and we can now compute a random "errno" value that
+ // we should return. Or this is part of a more complex boolean
+ // expression; in that case, we have to recursively add tests for some
+ // of system call parameters that we have not yet included in our
+ // tests.
+ arg_value->tests[n].k_value = k_value;
+ if (!remaining_args || (rand() & 1)) {
+ arg_value->tests[n].err = (rand() % 1000) + 1;
+ arg_value->tests[n].arg_value = NULL;
+ } else {
+ arg_value->tests[n].err = 0;
+ arg_value->tests[n].arg_value =
+ RandomArgValue(RandomArg(args_mask), args_mask, remaining_args - 1);
+ }
+ }
+ // Finally, we have to define what we should return if none of the
+ // previous equality tests pass. Again, we can either deal with a leaf
+ // node, or we can randomly add another couple of tests.
+ if (!remaining_args || (rand() & 1)) {
+ arg_value->err = (rand() % 1000) + 1;
+ arg_value->arg_value = NULL;
+ } else {
+ arg_value->err = 0;
+ arg_value->arg_value =
+ RandomArgValue(RandomArg(args_mask), args_mask, remaining_args - 1);
+ }
+ // We have now built a new (sub-)tree of ArgValues defining a set of
+ // boolean expressions for testing random system call arguments against
+ // random values. Return this tree to our caller.
+ return arg_value;
+ }
+
+ int RandomArg(int args_mask) {
+ // Compute a random system call parameter number.
+ int argno = rand() % kMaxArgs;
+
+ // Make sure that this same parameter number has not previously been
+ // used. Otherwise, we could end up with a test that is impossible to
+ // satisfy (e.g. args[0] == 1 && args[0] == 2).
+ while (args_mask & (1 << argno)) {
+ argno = (argno + 1) % kMaxArgs;
+ }
+ return argno;
+ }
+
+ void DeleteArgValue(ArgValue *arg_value) {
+ // Delete an ArgValue and all of its child nodes. This requires
+ // recursively descending into the tree.
+ if (arg_value) {
+ if (arg_value->size) {
+ for (int n = 0; n < arg_value->size; ++n) {
+ if (!arg_value->tests[n].err) {
+ DeleteArgValue(arg_value->tests[n].arg_value);
+ }
+ }
+ delete[] arg_value->tests;
+ }
+ if (!arg_value->err) {
+ DeleteArgValue(arg_value->arg_value);
+ }
+ delete arg_value;
+ }
+ }
+
+ ErrorCode ToErrorCode(ArgValue *arg_value) {
+ // Compute the ErrorCode that should be returned, if none of our
+ // tests succeed (i.e. the system call parameter doesn't match any
+ // of the values in arg_value->tests[].k_value).
+ ErrorCode err;
+ if (arg_value->err) {
+ // If this was a leaf node, return the errno value that we expect to
+ // return from the BPF filter program.
+ err = ErrorCode(arg_value->err);
+ } else {
+ // If this wasn't a leaf node yet, recursively descend into the rest
+ // of the tree. This will end up adding a few more Sandbox::Cond()
+ // tests to our ErrorCode.
+ err = ToErrorCode(arg_value->arg_value);
+ }
+
+ // Now, iterate over all the test cases that we want to compare against.
+ // This builds a chain of Sandbox::Cond() tests
+ // (aka "if ... elif ... elif ... elif ... fi")
+ for (int n = arg_value->size; n-- > 0; ) {
+ ErrorCode matched;
+ // Again, we distinguish between leaf nodes and subtrees.
+ if (arg_value->tests[n].err) {
+ matched = ErrorCode(arg_value->tests[n].err);
+ } else {
+ matched = ToErrorCode(arg_value->tests[n].arg_value);
+ }
+ // TODO(markus): For now, all of our tests are limited to 32bit.
+ err = Sandbox::Cond(arg_value->argno, ErrorCode::TP_32BIT,
+ ErrorCode::OP_EQUAL, arg_value->tests[n].k_value,
+ matched, err);
+ }
+ return err;
+ }
+
+ void Verify(int sysno, intptr_t *args, const ArgValue& arg_value) {
+ uint32_t mismatched = 0;
+ // Iterate over all the k_values in arg_value.tests[] and verify that
+ // we see the expected return values from system calls, when we pass
+ // the k_value as a parameter in a system call.
+ for (int n = arg_value.size; n-- > 0; ) {
+ mismatched += arg_value.tests[n].k_value;
+ args[arg_value.argno] = arg_value.tests[n].k_value;
+ if (arg_value.tests[n].err) {
+ VerifyErrno(sysno, args, arg_value.tests[n].err);
+ } else {
+ Verify(sysno, args, *arg_value.tests[n].arg_value);
+ }
+ }
+ // Find a k_value that doesn't match any of the k_values in
+ // arg_value.tests[]. In most cases, the current value of "mismatched"
+ // would fit this requirement. But on the off-chance that it happens
+ // to collide, we double-check.
+ try_again:
+ for (int n = arg_value.size; n-- > 0; ) {
+ if (mismatched == arg_value.tests[n].k_value) {
+ ++mismatched;
+ goto try_again;
+ }
+ }
+ // Now verify that we see the expected return value from system calls,
+ // if we pass a value that doesn't match any of the conditions (i.e. this
+ // is testing the "else" clause of the conditions).
+ args[arg_value.argno] = mismatched;
+ if (arg_value.err) {
+ VerifyErrno(sysno, args, arg_value.err);
+ } else {
+ Verify(sysno, args, *arg_value.arg_value);
+ }
+ // Reset args[arg_value.argno]. This is not technically needed, but it
+ // makes it easier to reason about the correctness of our tests.
+ args[arg_value.argno] = 0;
+ }
+
+ void VerifyErrno(int sysno, intptr_t *args, int err) {
+ // We installed BPF filters that return different errno values
+ // based on the system call number and the parameters that we decided
+ // to pass in. Verify that this condition holds true.
+ BPF_ASSERT(SandboxSyscall(sysno,
+ args[0], args[1], args[2],
+ args[3], args[4], args[5]) == -err);
+ }
+
+ // Vector of ArgValue trees. These trees define all the possible boolean
+ // expressions that we want to turn into a BPF filter program.
+ std::vector<ArgValue *> arg_values_;
+
+ // Don't increase these values. We are pushing the limits of the maximum
+ // BPF program that the kernel will allow us to load. If the values are
+ // increased too much, the test will start failing.
+ static const int kNumTestCases = 50;
+ static const int kMaxFanOut = 3;
+ static const int kMaxArgs = 6;
+};
+
+ErrorCode EqualityStressTestPolicy(int sysno, void *aux) {
+ return reinterpret_cast<EqualityStressTest *>(aux)->Policy(sysno);
+}
+
+BPF_TEST(SandboxBpf, EqualityTests, EqualityStressTestPolicy,
+ EqualityStressTest /* BPF_AUX */) {
+ BPF_AUX.VerifyFilter();
+}
+
} // namespace

Powered by Google App Engine
This is Rietveld 408576698