Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "sandbox/linux/seccomp-bpf/verifier.h" | 5 #include "sandbox/linux/bpf_dsl/verifier.h" |
| 6 | 6 |
| 7 #include <string.h> | 7 #include <string.h> |
| 8 | 8 |
| 9 #include <limits> | 9 #include <limits> |
| 10 | 10 |
| 11 #include "sandbox/linux/bpf_dsl/bpf_dsl.h" | 11 #include "sandbox/linux/bpf_dsl/bpf_dsl.h" |
| 12 #include "sandbox/linux/bpf_dsl/bpf_dsl_impl.h" | 12 #include "sandbox/linux/bpf_dsl/bpf_dsl_impl.h" |
| 13 #include "sandbox/linux/bpf_dsl/policy.h" | 13 #include "sandbox/linux/bpf_dsl/policy.h" |
| 14 #include "sandbox/linux/bpf_dsl/policy_compiler.h" | 14 #include "sandbox/linux/bpf_dsl/policy_compiler.h" |
| 15 #include "sandbox/linux/bpf_dsl/seccomp_macros.h" | 15 #include "sandbox/linux/bpf_dsl/seccomp_macros.h" |
| 16 #include "sandbox/linux/bpf_dsl/syscall_set.h" | 16 #include "sandbox/linux/bpf_dsl/syscall_set.h" |
| 17 #include "sandbox/linux/seccomp-bpf/errorcode.h" | 17 #include "sandbox/linux/seccomp-bpf/errorcode.h" |
| 18 #include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" | |
| 19 #include "sandbox/linux/system_headers/linux_seccomp.h" | 18 #include "sandbox/linux/system_headers/linux_seccomp.h" |
| 20 | 19 |
| 21 namespace sandbox { | 20 namespace sandbox { |
| 21 namespace bpf_dsl { | |
| 22 | 22 |
| 23 namespace { | 23 namespace { |
| 24 | 24 |
| 25 const uint64_t kLower32Bits = std::numeric_limits<uint32_t>::max(); | 25 const uint64_t kLower32Bits = std::numeric_limits<uint32_t>::max(); |
| 26 const uint64_t kUpper32Bits = static_cast<uint64_t>(kLower32Bits) << 32; | 26 const uint64_t kUpper32Bits = static_cast<uint64_t>(kLower32Bits) << 32; |
| 27 const uint64_t kFull64Bits = std::numeric_limits<uint64_t>::max(); | |
| 28 | 27 |
| 29 struct State { | 28 struct State { |
| 30 State(const std::vector<struct sock_filter>& p, | 29 State(const std::vector<struct sock_filter>& p, |
| 31 const struct arch_seccomp_data& d) | 30 const struct arch_seccomp_data& d) |
| 32 : program(p), data(d), ip(0), accumulator(0), acc_is_valid(false) {} | 31 : program(p), data(d), ip(0), accumulator(0), acc_is_valid(false) {} |
| 33 const std::vector<struct sock_filter>& program; | 32 const std::vector<struct sock_filter>& program; |
| 34 const struct arch_seccomp_data& data; | 33 const struct arch_seccomp_data& data; |
| 35 unsigned int ip; | 34 unsigned int ip; |
| 36 uint32_t accumulator; | 35 uint32_t accumulator; |
| 37 bool acc_is_valid; | 36 bool acc_is_valid; |
| 38 | 37 |
| 39 private: | 38 private: |
| 40 DISALLOW_IMPLICIT_CONSTRUCTORS(State); | 39 DISALLOW_IMPLICIT_CONSTRUCTORS(State); |
| 41 }; | 40 }; |
| 42 | 41 |
| 43 uint32_t EvaluateErrorCode(bpf_dsl::PolicyCompiler* compiler, | 42 uint32_t EvaluateErrorCode(bpf_dsl::PolicyCompiler* compiler, |
| 44 const ErrorCode& code, | 43 const ErrorCode& code, |
| 45 const struct arch_seccomp_data& data) { | 44 const struct arch_seccomp_data& data) { |
| 46 if (code.error_type() == ErrorCode::ET_SIMPLE || | 45 if (code.error_type() == ErrorCode::ET_SIMPLE || |
| 47 code.error_type() == ErrorCode::ET_TRAP) { | 46 code.error_type() == ErrorCode::ET_TRAP) { |
| 48 return code.err(); | 47 return code.err(); |
| 49 } else if (code.error_type() == ErrorCode::ET_COND) { | 48 } else if (code.error_type() == ErrorCode::ET_COND) { |
| 50 if (code.width() == ErrorCode::TP_32BIT && | 49 if (code.width() == ErrorCode::TP_32BIT && |
| 51 (data.args[code.argno()] >> 32) && | 50 (data.args[code.argno()] >> 32) && |
| 52 (data.args[code.argno()] & 0xFFFFFFFF80000000ull) != | 51 (data.args[code.argno()] & 0xFFFFFFFF80000000ull) != |
| 53 0xFFFFFFFF80000000ull) { | 52 0xFFFFFFFF80000000ull) { |
| 54 return compiler->Unexpected64bitArgument().err(); | 53 return compiler->Unexpected64bitArgument().err(); |
| 55 } | 54 } |
| 56 bool equal = (data.args[code.argno()] & code.mask()) == code.value(); | 55 bool equal = (data.args[code.argno()] & code.mask()) == code.value(); |
| 57 return EvaluateErrorCode( | 56 return EvaluateErrorCode(compiler, equal ? *code.passed() : *code.failed(), |
| 58 compiler, equal ? *code.passed() : *code.failed(), data); | 57 data); |
| 59 } else { | 58 } else { |
| 60 return SECCOMP_RET_INVALID; | 59 return SECCOMP_RET_INVALID; |
| 61 } | 60 } |
| 62 } | 61 } |
| 63 | 62 |
| 64 bool VerifyErrorCode(bpf_dsl::PolicyCompiler* compiler, | 63 bool VerifyErrorCode(bpf_dsl::PolicyCompiler* compiler, |
| 65 const std::vector<struct sock_filter>& program, | 64 const std::vector<struct sock_filter>& program, |
| 66 struct arch_seccomp_data* data, | 65 struct arch_seccomp_data* data, |
| 67 const ErrorCode& root_code, | 66 const ErrorCode& root_code, |
| 68 const ErrorCode& code, | 67 const ErrorCode& code, |
| 69 const char** err) { | 68 const char** err) { |
| 70 if (code.error_type() == ErrorCode::ET_SIMPLE || | 69 if (code.error_type() == ErrorCode::ET_SIMPLE || |
| 71 code.error_type() == ErrorCode::ET_TRAP) { | 70 code.error_type() == ErrorCode::ET_TRAP) { |
| 72 uint32_t computed_ret = Verifier::EvaluateBPF(program, *data, err); | 71 uint32_t computed_ret = Verifier::EvaluateBPF(program, *data, err); |
| 73 if (*err) { | 72 if (*err) { |
| 74 return false; | 73 return false; |
| 75 } else if (computed_ret != EvaluateErrorCode(compiler, root_code, *data)) { | 74 } |
| 75 uint32_t policy_ret = EvaluateErrorCode(compiler, root_code, *data); | |
|
jln (very slow on Chromium)
2015/02/21 01:48:48
const?
mdempsky
2015/02/23 03:54:24
Done.
| |
| 76 if (computed_ret != policy_ret) { | |
| 76 // For efficiency's sake, we'd much rather compare "computed_ret" | 77 // For efficiency's sake, we'd much rather compare "computed_ret" |
| 77 // against "code.err()". This works most of the time, but it doesn't | 78 // against "code.err()". This works most of the time, but it doesn't |
| 78 // always work for nested conditional expressions. The test values | 79 // always work for nested conditional expressions. The test values |
| 79 // that we generate on the fly to probe expressions can trigger | 80 // that we generate on the fly to probe expressions can trigger |
| 80 // code flow decisions in multiple nodes of the decision tree, and the | 81 // code flow decisions in multiple nodes of the decision tree, and the |
| 81 // only way to compute the correct error code in that situation is by | 82 // only way to compute the correct error code in that situation is by |
| 82 // calling EvaluateErrorCode(). | 83 // calling EvaluateErrorCode(). |
| 83 *err = "Exit code from BPF program doesn't match"; | 84 *err = "Exit code from BPF program doesn't match"; |
| 84 return false; | 85 return false; |
| 85 } | 86 } |
| 86 } else if (code.error_type() == ErrorCode::ET_COND) { | 87 } else if (code.error_type() == ErrorCode::ET_COND) { |
| 87 if (code.argno() < 0 || code.argno() >= 6) { | 88 if (code.argno() < 0 || code.argno() >= 6) { |
| 88 *err = "Invalid argument number in error code"; | 89 *err = "Invalid argument number in error code"; |
| 89 return false; | 90 return false; |
| 90 } | 91 } |
| 91 | 92 |
| 92 // TODO(mdempsky): The test values generated here try to provide good | 93 // TODO(mdempsky): The test values generated here try to provide good |
| 93 // coverage for generated BPF instructions while avoiding combinatorial | 94 // coverage for generated BPF instructions while avoiding combinatorial |
| 94 // explosion on large policies. Ideally we would instead take a fuzzing-like | 95 // explosion on large policies. Ideally we would instead take a fuzzing-like |
| 95 // approach and generate a bounded number of test cases regardless of policy | 96 // approach and generate a bounded number of test cases regardless of policy |
| 96 // size. | 97 // size. |
| 97 | 98 |
| 98 // Verify that we can check a value for simple equality. | 99 // Verify that we can check a value for simple equality. |
| 99 data->args[code.argno()] = code.value(); | 100 data->args[code.argno()] = code.value(); |
| 100 if (!VerifyErrorCode( | 101 if (!VerifyErrorCode(compiler, program, data, root_code, *code.passed(), |
| 101 compiler, program, data, root_code, *code.passed(), err)) { | 102 err)) { |
| 102 return false; | 103 return false; |
| 103 } | 104 } |
| 104 | 105 |
| 105 // If mask ignores any bits, verify that setting those bits is still | 106 // If mask ignores any bits, verify that setting those bits is still |
| 106 // detected as equality. | 107 // detected as equality. |
| 107 uint64_t ignored_bits = ~code.mask(); | 108 uint64_t ignored_bits = ~code.mask(); |
| 108 if (code.width() == ErrorCode::TP_32BIT) { | 109 if (code.width() == ErrorCode::TP_32BIT) { |
| 109 ignored_bits = static_cast<uint32_t>(ignored_bits); | 110 ignored_bits = static_cast<uint32_t>(ignored_bits); |
| 110 } | 111 } |
| 111 if ((ignored_bits & kLower32Bits) != 0) { | 112 if ((ignored_bits & kLower32Bits) != 0) { |
| 112 data->args[code.argno()] = code.value() | (ignored_bits & kLower32Bits); | 113 data->args[code.argno()] = code.value() | (ignored_bits & kLower32Bits); |
| 113 if (!VerifyErrorCode( | 114 if (!VerifyErrorCode(compiler, program, data, root_code, *code.passed(), |
| 114 compiler, program, data, root_code, *code.passed(), err)) { | 115 err)) { |
| 115 return false; | 116 return false; |
| 116 } | 117 } |
| 117 } | 118 } |
| 118 if ((ignored_bits & kUpper32Bits) != 0) { | 119 if ((ignored_bits & kUpper32Bits) != 0) { |
| 119 data->args[code.argno()] = code.value() | (ignored_bits & kUpper32Bits); | 120 data->args[code.argno()] = code.value() | (ignored_bits & kUpper32Bits); |
| 120 if (!VerifyErrorCode( | 121 if (!VerifyErrorCode(compiler, program, data, root_code, *code.passed(), |
| 121 compiler, program, data, root_code, *code.passed(), err)) { | 122 err)) { |
| 122 return false; | 123 return false; |
| 123 } | 124 } |
| 124 } | 125 } |
| 125 | 126 |
| 126 // Verify that changing bits included in the mask is detected as inequality. | 127 // Verify that changing bits included in the mask is detected as inequality. |
| 127 if ((code.mask() & kLower32Bits) != 0) { | 128 if ((code.mask() & kLower32Bits) != 0) { |
| 128 data->args[code.argno()] = code.value() ^ (code.mask() & kLower32Bits); | 129 data->args[code.argno()] = code.value() ^ (code.mask() & kLower32Bits); |
| 129 if (!VerifyErrorCode( | 130 if (!VerifyErrorCode(compiler, program, data, root_code, *code.failed(), |
| 130 compiler, program, data, root_code, *code.failed(), err)) { | 131 err)) { |
| 131 return false; | 132 return false; |
| 132 } | 133 } |
| 133 } | 134 } |
| 134 if ((code.mask() & kUpper32Bits) != 0) { | 135 if ((code.mask() & kUpper32Bits) != 0) { |
| 135 data->args[code.argno()] = code.value() ^ (code.mask() & kUpper32Bits); | 136 data->args[code.argno()] = code.value() ^ (code.mask() & kUpper32Bits); |
| 136 if (!VerifyErrorCode( | 137 if (!VerifyErrorCode(compiler, program, data, root_code, *code.failed(), |
| 137 compiler, program, data, root_code, *code.failed(), err)) { | 138 err)) { |
| 138 return false; | 139 return false; |
| 139 } | 140 } |
| 140 } | 141 } |
| 141 | 142 |
| 142 if (code.width() == ErrorCode::TP_32BIT) { | 143 if (code.width() == ErrorCode::TP_32BIT) { |
| 143 // For 32-bit system call arguments, we emit additional instructions to | 144 // For 32-bit system call arguments, we emit additional instructions to |
| 144 // validate the upper 32-bits. Here we test that validation. | 145 // validate the upper 32-bits. Here we test that validation. |
| 145 | 146 |
| 146 // Arbitrary 64-bit values should be rejected. | 147 // Arbitrary 64-bit values should be rejected. |
| 147 data->args[code.argno()] = 1ULL << 32; | 148 data->args[code.argno()] = 1ULL << 32; |
| 148 if (!VerifyErrorCode(compiler, | 149 if (!VerifyErrorCode(compiler, program, data, root_code, |
| 149 program, | 150 compiler->Unexpected64bitArgument(), err)) { |
| 150 data, | |
| 151 root_code, | |
| 152 compiler->Unexpected64bitArgument(), | |
| 153 err)) { | |
| 154 return false; | 151 return false; |
| 155 } | 152 } |
| 156 | 153 |
| 157 // Upper 32-bits set without the MSB of the lower 32-bits set should be | 154 // Upper 32-bits set without the MSB of the lower 32-bits set should be |
| 158 // rejected too. | 155 // rejected too. |
| 159 data->args[code.argno()] = kUpper32Bits; | 156 data->args[code.argno()] = kUpper32Bits; |
| 160 if (!VerifyErrorCode(compiler, | 157 if (!VerifyErrorCode(compiler, program, data, root_code, |
| 161 program, | 158 compiler->Unexpected64bitArgument(), err)) { |
| 162 data, | |
| 163 root_code, | |
| 164 compiler->Unexpected64bitArgument(), | |
| 165 err)) { | |
| 166 return false; | 159 return false; |
| 167 } | 160 } |
| 168 } | 161 } |
| 169 } else { | 162 } else { |
| 170 *err = "Attempting to return invalid error code from BPF program"; | 163 *err = "Attempting to return invalid error code from BPF program"; |
| 171 return false; | 164 return false; |
| 172 } | 165 } |
| 173 return true; | 166 return true; |
| 174 } | 167 } |
| 175 | 168 |
| 176 void Ld(State* state, const struct sock_filter& insn, const char** err) { | 169 void Ld(State* state, const struct sock_filter& insn, const char** err) { |
| 177 if (BPF_SIZE(insn.code) != BPF_W || BPF_MODE(insn.code) != BPF_ABS || | 170 if (BPF_SIZE(insn.code) != BPF_W || BPF_MODE(insn.code) != BPF_ABS || |
| 178 insn.jt != 0 || insn.jf != 0) { | 171 insn.jt != 0 || insn.jf != 0) { |
| 179 *err = "Invalid BPF_LD instruction"; | 172 *err = "Invalid BPF_LD instruction"; |
| 180 return; | 173 return; |
| 181 } | 174 } |
| 182 if (insn.k < sizeof(struct arch_seccomp_data) && (insn.k & 3) == 0) { | 175 if (insn.k < sizeof(struct arch_seccomp_data) && (insn.k & 3) == 0) { |
| 183 // We only allow loading of properly aligned 32bit quantities. | 176 // We only allow loading of properly aligned 32bit quantities. |
| 184 memcpy(&state->accumulator, | 177 memcpy(&state->accumulator, |
| 185 reinterpret_cast<const char*>(&state->data) + insn.k, | 178 reinterpret_cast<const char*>(&state->data) + insn.k, 4); |
| 186 4); | |
| 187 } else { | 179 } else { |
| 188 *err = "Invalid operand in BPF_LD instruction"; | 180 *err = "Invalid operand in BPF_LD instruction"; |
| 189 return; | 181 return; |
| 190 } | 182 } |
| 191 state->acc_is_valid = true; | 183 state->acc_is_valid = true; |
| 192 return; | 184 return; |
| 193 } | 185 } |
| 194 | 186 |
| 195 void Jmp(State* state, const struct sock_filter& insn, const char** err) { | 187 void Jmp(State* state, const struct sock_filter& insn, const char** err) { |
| 196 if (BPF_OP(insn.code) == BPF_JA) { | 188 if (BPF_OP(insn.code) == BPF_JA) { |
| (...skipping 195 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 392 Alu(&state, insn, err); | 384 Alu(&state, insn, err); |
| 393 break; | 385 break; |
| 394 default: | 386 default: |
| 395 *err = "Unexpected instruction in BPF program"; | 387 *err = "Unexpected instruction in BPF program"; |
| 396 break; | 388 break; |
| 397 } | 389 } |
| 398 } | 390 } |
| 399 return 0; | 391 return 0; |
| 400 } | 392 } |
| 401 | 393 |
| 394 } // namespace bpf_dsl | |
| 402 } // namespace sandbox | 395 } // namespace sandbox |
| OLD | NEW |