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

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

Issue 530133003: bpf_dsl: support arbitrary (arg & mask) == val expressions (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Add unit tests to sandbox_bpf_unittest.cc Created 6 years, 3 months 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/verifier.cc
diff --git a/sandbox/linux/seccomp-bpf/verifier.cc b/sandbox/linux/seccomp-bpf/verifier.cc
index 0863556e0808e7e28d4805b0245dda7fa390bdd3..2f5195a6256a1a2f5d8a7cf75898680e7208bf60 100644
--- a/sandbox/linux/seccomp-bpf/verifier.cc
+++ b/sandbox/linux/seccomp-bpf/verifier.cc
@@ -4,16 +4,21 @@
#include <string.h>
+#include <limits>
+
#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h"
#include "sandbox/linux/seccomp-bpf/sandbox_bpf_policy.h"
#include "sandbox/linux/seccomp-bpf/syscall_iterator.h"
#include "sandbox/linux/seccomp-bpf/verifier.h"
-
namespace sandbox {
namespace {
+const uint64_t kLower32Bits = std::numeric_limits<uint32_t>::max();
+const uint64_t kUpper32Bits = static_cast<uint64_t>(kLower32Bits) << 32;
+const uint64_t kFull64Bits = std::numeric_limits<uint64_t>::max();
+
struct State {
State(const std::vector<struct sock_filter>& p,
const struct arch_seccomp_data& d)
@@ -41,36 +46,9 @@ uint32_t EvaluateErrorCode(SandboxBPF* sandbox,
0xFFFFFFFF80000000ull) {
return sandbox->Unexpected64bitArgument().err();
}
- switch (code.op()) {
- case ErrorCode::OP_EQUAL:
- return EvaluateErrorCode(sandbox,
- (code.width() == ErrorCode::TP_32BIT
- ? uint32_t(data.args[code.argno()])
- : data.args[code.argno()]) == code.value()
- ? *code.passed()
- : *code.failed(),
- data);
- case ErrorCode::OP_HAS_ALL_BITS:
- return EvaluateErrorCode(sandbox,
- ((code.width() == ErrorCode::TP_32BIT
- ? uint32_t(data.args[code.argno()])
- : data.args[code.argno()]) &
- code.value()) == code.value()
- ? *code.passed()
- : *code.failed(),
- data);
- case ErrorCode::OP_HAS_ANY_BITS:
- return EvaluateErrorCode(sandbox,
- (code.width() == ErrorCode::TP_32BIT
- ? uint32_t(data.args[code.argno()])
- : data.args[code.argno()]) &
- code.value()
- ? *code.passed()
- : *code.failed(),
- data);
- default:
- return SECCOMP_RET_INVALID;
- }
+ bool equal = (data.args[code.argno()] & code.mask()) == code.value();
+ return EvaluateErrorCode(
+ sandbox, equal ? *code.passed() : *code.failed(), data);
} else {
return SECCOMP_RET_INVALID;
}
@@ -103,114 +81,83 @@ bool VerifyErrorCode(SandboxBPF* sandbox,
*err = "Invalid argument number in error code";
return false;
}
- switch (code.op()) {
- case ErrorCode::OP_EQUAL:
- // Verify that we can check a 32bit value (or the LSB of a 64bit value)
- // for equality.
- data->args[code.argno()] = code.value();
- if (!VerifyErrorCode(
- sandbox, program, data, root_code, *code.passed(), err)) {
- return false;
- }
- // Change the value to no longer match and verify that this is detected
- // as an inequality.
- data->args[code.argno()] = code.value() ^ 0x55AA55AA;
- if (!VerifyErrorCode(
- sandbox, program, data, root_code, *code.failed(), err)) {
- return false;
- }
+ // TODO(mdempsky): The test values generated here try to provide good
+ // coverage for generated BPF instructions while avoiding combinatorial
+ // explosion on large policies. Ideally we would instead take a fuzzing-like
+ // approach and generate a bounded number of test cases regardless of policy
+ // size.
- // BPF programs can only ever operate on 32bit values. So, we have
- // generated additional BPF instructions that inspect the MSB. Verify
- // that they behave as intended.
- if (code.width() == ErrorCode::TP_32BIT) {
- if (code.value() >> 32) {
- SANDBOX_DIE(
- "Invalid comparison of a 32bit system call argument "
- "against a 64bit constant; this test is always false.");
- }
+ // Verify that we can check a value for simple equality.
+ data->args[code.argno()] = code.value();
+ if (!VerifyErrorCode(
+ sandbox, program, data, root_code, *code.passed(), err)) {
+ return false;
+ }
- // If the system call argument was intended to be a 32bit parameter,
- // verify that it is a fatal error if a 64bit value is ever passed
- // here.
- data->args[code.argno()] = 0x100000000ull;
- if (!VerifyErrorCode(sandbox,
- program,
- data,
- root_code,
- sandbox->Unexpected64bitArgument(),
- err)) {
- return false;
- }
- } else {
- // If the system call argument was intended to be a 64bit parameter,
- // verify that we can handle (in-)equality for the MSB. This is
- // essentially the same test that we did earlier for the LSB.
- // We only need to verify the behavior of the inequality test. We
- // know that the equality test already passed, as unlike the kernel
- // the Verifier does operate on 64bit quantities.
- data->args[code.argno()] = code.value() ^ 0x55AA55AA00000000ull;
- if (!VerifyErrorCode(
- sandbox, program, data, root_code, *code.failed(), err)) {
- return false;
- }
- }
- break;
- case ErrorCode::OP_HAS_ALL_BITS:
- case ErrorCode::OP_HAS_ANY_BITS:
- // A comprehensive test of bit values is difficult and potentially
- // rather
- // time-expensive. We avoid doing so at run-time and instead rely on the
- // unittest for full testing. The test that we have here covers just the
- // common cases. We test against the bitmask itself, all zeros and all
- // ones.
- {
- // Testing "any" bits against a zero mask is always false. So, there
- // are some cases, where we expect tests to take the "failed()" branch
- // even though this is a test that normally should take "passed()".
- const ErrorCode& passed =
- (!code.value() && code.op() == ErrorCode::OP_HAS_ANY_BITS) ||
+ // If mask ignores any bits, verify that setting those bits is still
+ // detected as equality.
+ uint64_t ignored_bits = ~code.mask();
+ if (code.width() == ErrorCode::TP_32BIT) {
+ ignored_bits = static_cast<uint32_t>(ignored_bits);
+ }
+ if ((ignored_bits & kLower32Bits) != 0) {
+ data->args[code.argno()] = code.value() | (ignored_bits & kLower32Bits);
+ if (!VerifyErrorCode(
+ sandbox, program, data, root_code, *code.passed(), err)) {
+ return false;
+ }
+ }
+ if ((ignored_bits & kUpper32Bits) != 0) {
+ data->args[code.argno()] = code.value() | (ignored_bits & kUpper32Bits);
+ if (!VerifyErrorCode(
+ sandbox, program, data, root_code, *code.passed(), err)) {
+ return false;
+ }
+ }
- // On a 32bit system, it is impossible to pass a 64bit
- // value as a
- // system call argument. So, some additional tests always
- // evaluate
- // as false.
- ((code.value() & ~uint64_t(uintptr_t(-1))) &&
- code.op() == ErrorCode::OP_HAS_ALL_BITS) ||
- (code.value() && !(code.value() & uintptr_t(-1)) &&
- code.op() == ErrorCode::OP_HAS_ANY_BITS)
- ? *code.failed()
- : *code.passed();
+ // Verify that changing bits included in the mask is detected as inequality.
+ if ((code.mask() & kLower32Bits) != 0) {
+ data->args[code.argno()] = code.value() ^ (code.mask() & kLower32Bits);
+ if (!VerifyErrorCode(
+ sandbox, program, data, root_code, *code.failed(), err)) {
+ return false;
+ }
+ }
+ if ((code.mask() & kUpper32Bits) != 0) {
+ data->args[code.argno()] = code.value() ^ (code.mask() & kUpper32Bits);
+ if (!VerifyErrorCode(
+ sandbox, program, data, root_code, *code.failed(), err)) {
+ return false;
+ }
+ }
- // Similary, testing for "all" bits in a zero mask is always true. So,
- // some cases pass despite them normally failing.
- const ErrorCode& failed =
- !code.value() && code.op() == ErrorCode::OP_HAS_ALL_BITS
- ? *code.passed()
- : *code.failed();
+ if (code.width() == ErrorCode::TP_32BIT) {
+ // For 32-bit system call arguments, we emit additional instructions to
+ // validate the upper 32-bits. Here we test that validation.
- data->args[code.argno()] = code.value() & uintptr_t(-1);
- if (!VerifyErrorCode(
- sandbox, program, data, root_code, passed, err)) {
- return false;
- }
- data->args[code.argno()] = uintptr_t(-1);
- if (!VerifyErrorCode(
- sandbox, program, data, root_code, passed, err)) {
- return false;
- }
- data->args[code.argno()] = 0;
- if (!VerifyErrorCode(
- sandbox, program, data, root_code, failed, err)) {
- return false;
- }
- }
- break;
- default: // TODO(markus): Need to add support for OP_GREATER
- *err = "Unsupported operation in conditional error code";
+ // Arbitrary 64-bit values should be rejected.
+ data->args[code.argno()] = 1ULL << 32;
+ if (!VerifyErrorCode(sandbox,
+ program,
+ data,
+ root_code,
+ sandbox->Unexpected64bitArgument(),
+ err)) {
return false;
+ }
+
+ // Upper 32-bits set without the MSB of the lower 32-bits set should be
+ // rejected too.
+ data->args[code.argno()] = kUpper32Bits;
+ if (!VerifyErrorCode(sandbox,
+ program,
+ data,
+ root_code,
+ sandbox->Unexpected64bitArgument(),
+ err)) {
+ return false;
+ }
}
} else {
*err = "Attempting to return invalid error code from BPF program";
« sandbox/linux/seccomp-bpf/sandbox_bpf.cc ('K') | « sandbox/linux/seccomp-bpf/sandbox_bpf_unittest.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698