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/bpf_dsl/policy_compiler.h" | 5 #include "sandbox/linux/bpf_dsl/policy_compiler.h" |
6 | 6 |
7 #include <errno.h> | 7 #include <errno.h> |
8 #include <linux/filter.h> | 8 #include <linux/filter.h> |
9 #include <sys/syscall.h> | 9 #include <sys/syscall.h> |
10 | 10 |
11 #include <limits> | 11 #include <limits> |
12 | 12 |
13 #include "base/logging.h" | 13 #include "base/logging.h" |
14 #include "base/macros.h" | 14 #include "base/macros.h" |
15 #include "sandbox/linux/bpf_dsl/bpf_dsl.h" | 15 #include "sandbox/linux/bpf_dsl/bpf_dsl.h" |
16 #include "sandbox/linux/bpf_dsl/bpf_dsl_impl.h" | 16 #include "sandbox/linux/bpf_dsl/bpf_dsl_impl.h" |
17 #include "sandbox/linux/bpf_dsl/codegen.h" | 17 #include "sandbox/linux/bpf_dsl/codegen.h" |
| 18 #include "sandbox/linux/bpf_dsl/dump_bpf.h" |
18 #include "sandbox/linux/bpf_dsl/policy.h" | 19 #include "sandbox/linux/bpf_dsl/policy.h" |
19 #include "sandbox/linux/bpf_dsl/seccomp_macros.h" | 20 #include "sandbox/linux/bpf_dsl/seccomp_macros.h" |
20 #include "sandbox/linux/bpf_dsl/syscall_set.h" | 21 #include "sandbox/linux/bpf_dsl/syscall_set.h" |
21 #include "sandbox/linux/seccomp-bpf/die.h" | 22 #include "sandbox/linux/bpf_dsl/verifier.h" |
22 #include "sandbox/linux/seccomp-bpf/errorcode.h" | 23 #include "sandbox/linux/seccomp-bpf/errorcode.h" |
23 #include "sandbox/linux/seccomp-bpf/syscall.h" | |
24 #include "sandbox/linux/system_headers/linux_seccomp.h" | 24 #include "sandbox/linux/system_headers/linux_seccomp.h" |
25 | 25 |
26 namespace sandbox { | 26 namespace sandbox { |
27 namespace bpf_dsl { | 27 namespace bpf_dsl { |
28 | 28 |
29 namespace { | 29 namespace { |
30 | 30 |
31 #if defined(__i386__) || defined(__x86_64__) | 31 #if defined(__i386__) || defined(__x86_64__) |
32 const bool kIsIntel = true; | 32 const bool kIsIntel = true; |
33 #else | 33 #else |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
79 } // namespace | 79 } // namespace |
80 | 80 |
81 struct PolicyCompiler::Range { | 81 struct PolicyCompiler::Range { |
82 uint32_t from; | 82 uint32_t from; |
83 CodeGen::Node node; | 83 CodeGen::Node node; |
84 }; | 84 }; |
85 | 85 |
86 PolicyCompiler::PolicyCompiler(const Policy* policy, TrapRegistry* registry) | 86 PolicyCompiler::PolicyCompiler(const Policy* policy, TrapRegistry* registry) |
87 : policy_(policy), | 87 : policy_(policy), |
88 registry_(registry), | 88 registry_(registry), |
| 89 escapepc_(0), |
89 conds_(), | 90 conds_(), |
90 gen_(), | 91 gen_(), |
91 has_unsafe_traps_(HasUnsafeTraps(policy_)) { | 92 has_unsafe_traps_(HasUnsafeTraps(policy_)) { |
92 DCHECK(policy); | 93 DCHECK(policy); |
93 } | 94 } |
94 | 95 |
95 PolicyCompiler::~PolicyCompiler() { | 96 PolicyCompiler::~PolicyCompiler() { |
96 } | 97 } |
97 | 98 |
98 scoped_ptr<CodeGen::Program> PolicyCompiler::Compile() { | 99 scoped_ptr<CodeGen::Program> PolicyCompiler::Compile(bool verify) { |
99 if (!policy_->InvalidSyscall()->IsDeny()) { | 100 CHECK(policy_->InvalidSyscall()->IsDeny()) |
100 SANDBOX_DIE("Policies should deny invalid system calls."); | 101 << "Policies should deny invalid system calls"; |
101 } | |
102 | 102 |
103 // If our BPF program has unsafe traps, enable support for them. | 103 // If our BPF program has unsafe traps, enable support for them. |
104 if (has_unsafe_traps_) { | 104 if (has_unsafe_traps_) { |
105 // As support for unsafe jumps essentially defeats all the security | 105 CHECK_NE(0U, escapepc_) << "UnsafeTrap() requires a valid escape PC"; |
106 // measures that the sandbox provides, we print a big warning message -- | 106 |
107 // and of course, we make sure to only ever enable this feature if it | 107 for (int sysnum : kSyscallsRequiredForUnsafeTraps) { |
108 // is actually requested by the sandbox policy. | 108 CHECK(policy_->EvaluateSyscall(sysnum)->IsAllow()) |
109 if (Syscall::Call(-1) == -1 && errno == ENOSYS) { | 109 << "Policies that use UnsafeTrap() must unconditionally allow all " |
110 SANDBOX_DIE( | 110 "required system calls"; |
111 "Support for UnsafeTrap() has not yet been ported to this " | |
112 "architecture"); | |
113 } | 111 } |
114 | 112 |
115 for (int sysnum : kSyscallsRequiredForUnsafeTraps) { | 113 CHECK(registry_->EnableUnsafeTraps()) |
116 if (!policy_->EvaluateSyscall(sysnum)->IsAllow()) { | 114 << "We'd rather die than enable unsafe traps"; |
117 SANDBOX_DIE( | |
118 "Policies that use UnsafeTrap() must unconditionally allow all " | |
119 "required system calls"); | |
120 } | |
121 } | |
122 | |
123 if (!registry_->EnableUnsafeTraps()) { | |
124 // We should never be able to get here, as UnsafeTrap() should never | |
125 // actually return a valid ErrorCode object unless the user set the | |
126 // CHROME_SANDBOX_DEBUGGING environment variable; and therefore, | |
127 // "has_unsafe_traps" would always be false. But better double-check | |
128 // than enabling dangerous code. | |
129 SANDBOX_DIE("We'd rather die than enable unsafe traps"); | |
130 } | |
131 } | 115 } |
132 | 116 |
133 // Assemble the BPF filter program. | 117 // Assemble the BPF filter program. |
134 scoped_ptr<CodeGen::Program> program(new CodeGen::Program()); | 118 scoped_ptr<CodeGen::Program> program(new CodeGen::Program()); |
135 gen_.Compile(AssemblePolicy(), program.get()); | 119 gen_.Compile(AssemblePolicy(), program.get()); |
| 120 |
| 121 // Make sure compilation resulted in a BPF program that executes |
| 122 // correctly. Otherwise, there is an internal error in our BPF compiler. |
| 123 // There is really nothing the caller can do until the bug is fixed. |
| 124 if (verify) { |
| 125 const char* err = nullptr; |
| 126 if (!Verifier::VerifyBPF(this, *program, *policy_, &err)) { |
| 127 DumpBPF::PrintProgram(*program); |
| 128 LOG(FATAL) << err; |
| 129 } |
| 130 } |
| 131 |
136 return program.Pass(); | 132 return program.Pass(); |
137 } | 133 } |
138 | 134 |
| 135 void PolicyCompiler::DangerousSetEscapePC(uint64_t escapepc) { |
| 136 escapepc_ = escapepc; |
| 137 } |
| 138 |
139 CodeGen::Node PolicyCompiler::AssemblePolicy() { | 139 CodeGen::Node PolicyCompiler::AssemblePolicy() { |
140 // A compiled policy consists of three logical parts: | 140 // A compiled policy consists of three logical parts: |
141 // 1. Check that the "arch" field matches the expected architecture. | 141 // 1. Check that the "arch" field matches the expected architecture. |
142 // 2. If the policy involves unsafe traps, check if the syscall was | 142 // 2. If the policy involves unsafe traps, check if the syscall was |
143 // invoked by Syscall::Call, and then allow it unconditionally. | 143 // invoked by Syscall::Call, and then allow it unconditionally. |
144 // 3. Check the system call number and jump to the appropriate compiled | 144 // 3. Check the system call number and jump to the appropriate compiled |
145 // system call policy number. | 145 // system call policy number. |
146 return CheckArch(MaybeAddEscapeHatch(DispatchSyscall())); | 146 return CheckArch(MaybeAddEscapeHatch(DispatchSyscall())); |
147 } | 147 } |
148 | 148 |
149 CodeGen::Node PolicyCompiler::CheckArch(CodeGen::Node passed) { | 149 CodeGen::Node PolicyCompiler::CheckArch(CodeGen::Node passed) { |
150 // If the architecture doesn't match SECCOMP_ARCH, disallow the | 150 // If the architecture doesn't match SECCOMP_ARCH, disallow the |
151 // system call. | 151 // system call. |
152 return gen_.MakeInstruction( | 152 return gen_.MakeInstruction( |
153 BPF_LD + BPF_W + BPF_ABS, SECCOMP_ARCH_IDX, | 153 BPF_LD + BPF_W + BPF_ABS, SECCOMP_ARCH_IDX, |
154 gen_.MakeInstruction( | 154 gen_.MakeInstruction( |
155 BPF_JMP + BPF_JEQ + BPF_K, SECCOMP_ARCH, passed, | 155 BPF_JMP + BPF_JEQ + BPF_K, SECCOMP_ARCH, passed, |
156 CompileResult(Kill("Invalid audit architecture in BPF filter")))); | 156 CompileResult(Kill("Invalid audit architecture in BPF filter")))); |
157 } | 157 } |
158 | 158 |
159 CodeGen::Node PolicyCompiler::MaybeAddEscapeHatch(CodeGen::Node rest) { | 159 CodeGen::Node PolicyCompiler::MaybeAddEscapeHatch(CodeGen::Node rest) { |
160 // If no unsafe traps, then simply return |rest|. | 160 // If no unsafe traps, then simply return |rest|. |
161 if (!has_unsafe_traps_) { | 161 if (!has_unsafe_traps_) { |
162 return rest; | 162 return rest; |
163 } | 163 } |
164 | 164 |
165 // Allow system calls, if they originate from our magic return address | 165 // We already enabled unsafe traps in Compile, but enable them again to give |
166 // (which we can query by calling Syscall::Call(-1)). | 166 // the trap registry a second chance to complain before we add the backdoor. |
167 uint64_t syscall_entry_point = | 167 CHECK(registry_->EnableUnsafeTraps()); |
168 static_cast<uint64_t>(static_cast<uintptr_t>(Syscall::Call(-1))); | 168 |
169 uint32_t low = static_cast<uint32_t>(syscall_entry_point); | 169 // Allow system calls, if they originate from our magic return address. |
170 uint32_t hi = static_cast<uint32_t>(syscall_entry_point >> 32); | 170 const uint32_t lopc = static_cast<uint32_t>(escapepc_); |
| 171 const uint32_t hipc = static_cast<uint32_t>(escapepc_ >> 32); |
171 | 172 |
172 // BPF cannot do native 64-bit comparisons, so we have to compare | 173 // BPF cannot do native 64-bit comparisons, so we have to compare |
173 // both 32-bit halves of the instruction pointer. If they match what | 174 // both 32-bit halves of the instruction pointer. If they match what |
174 // we expect, we return ERR_ALLOWED. If either or both don't match, | 175 // we expect, we return ERR_ALLOWED. If either or both don't match, |
175 // we continue evalutating the rest of the sandbox policy. | 176 // we continue evalutating the rest of the sandbox policy. |
176 // | 177 // |
177 // For simplicity, we check the full 64-bit instruction pointer even | 178 // For simplicity, we check the full 64-bit instruction pointer even |
178 // on 32-bit architectures. | 179 // on 32-bit architectures. |
179 return gen_.MakeInstruction( | 180 return gen_.MakeInstruction( |
180 BPF_LD + BPF_W + BPF_ABS, SECCOMP_IP_LSB_IDX, | 181 BPF_LD + BPF_W + BPF_ABS, SECCOMP_IP_LSB_IDX, |
181 gen_.MakeInstruction( | 182 gen_.MakeInstruction( |
182 BPF_JMP + BPF_JEQ + BPF_K, low, | 183 BPF_JMP + BPF_JEQ + BPF_K, lopc, |
183 gen_.MakeInstruction( | 184 gen_.MakeInstruction( |
184 BPF_LD + BPF_W + BPF_ABS, SECCOMP_IP_MSB_IDX, | 185 BPF_LD + BPF_W + BPF_ABS, SECCOMP_IP_MSB_IDX, |
185 gen_.MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, hi, | 186 gen_.MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, hipc, |
186 CompileResult(Allow()), rest)), | 187 CompileResult(Allow()), rest)), |
187 rest)); | 188 rest)); |
188 } | 189 } |
189 | 190 |
190 CodeGen::Node PolicyCompiler::DispatchSyscall() { | 191 CodeGen::Node PolicyCompiler::DispatchSyscall() { |
191 // Evaluate all possible system calls and group their ErrorCodes into | 192 // Evaluate all possible system calls and group their ErrorCodes into |
192 // ranges of identical codes. | 193 // ranges of identical codes. |
193 Ranges ranges; | 194 Ranges ranges; |
194 FindRanges(&ranges); | 195 FindRanges(&ranges); |
195 | 196 |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
252 } | 253 } |
253 ranges->push_back(Range{old_sysnum, old_node}); | 254 ranges->push_back(Range{old_sysnum, old_node}); |
254 } | 255 } |
255 | 256 |
256 CodeGen::Node PolicyCompiler::AssembleJumpTable(Ranges::const_iterator start, | 257 CodeGen::Node PolicyCompiler::AssembleJumpTable(Ranges::const_iterator start, |
257 Ranges::const_iterator stop) { | 258 Ranges::const_iterator stop) { |
258 // We convert the list of system call ranges into jump table that performs | 259 // We convert the list of system call ranges into jump table that performs |
259 // a binary search over the ranges. | 260 // a binary search over the ranges. |
260 // As a sanity check, we need to have at least one distinct ranges for us | 261 // As a sanity check, we need to have at least one distinct ranges for us |
261 // to be able to build a jump table. | 262 // to be able to build a jump table. |
262 if (stop - start <= 0) { | 263 CHECK(start < stop) << "Invalid iterator range"; |
263 SANDBOX_DIE("Invalid set of system call ranges"); | 264 const auto n = stop - start; |
264 } else if (stop - start == 1) { | 265 if (n == 1) { |
265 // If we have narrowed things down to a single range object, we can | 266 // If we have narrowed things down to a single range object, we can |
266 // return from the BPF filter program. | 267 // return from the BPF filter program. |
267 return start->node; | 268 return start->node; |
268 } | 269 } |
269 | 270 |
270 // Pick the range object that is located at the mid point of our list. | 271 // Pick the range object that is located at the mid point of our list. |
271 // We compare our system call number against the lowest valid system call | 272 // We compare our system call number against the lowest valid system call |
272 // number in this range object. If our number is lower, it is outside of | 273 // number in this range object. If our number is lower, it is outside of |
273 // this range object. If it is greater or equal, it might be inside. | 274 // this range object. If it is greater or equal, it might be inside. |
274 Ranges::const_iterator mid = start + (stop - start) / 2; | 275 Ranges::const_iterator mid = start + n / 2; |
275 | 276 |
276 // Sub-divide the list of ranges and continue recursively. | 277 // Sub-divide the list of ranges and continue recursively. |
277 CodeGen::Node jf = AssembleJumpTable(start, mid); | 278 CodeGen::Node jf = AssembleJumpTable(start, mid); |
278 CodeGen::Node jt = AssembleJumpTable(mid, stop); | 279 CodeGen::Node jt = AssembleJumpTable(mid, stop); |
279 return gen_.MakeInstruction(BPF_JMP + BPF_JGE + BPF_K, mid->from, jt, jf); | 280 return gen_.MakeInstruction(BPF_JMP + BPF_JGE + BPF_K, mid->from, jt, jf); |
280 } | 281 } |
281 | 282 |
282 CodeGen::Node PolicyCompiler::CompileResult(const ResultExpr& res) { | 283 CodeGen::Node PolicyCompiler::CompileResult(const ResultExpr& res) { |
283 return RetExpression(res->Compile(this)); | 284 return RetExpression(res->Compile(this)); |
284 } | 285 } |
285 | 286 |
286 CodeGen::Node PolicyCompiler::RetExpression(const ErrorCode& err) { | 287 CodeGen::Node PolicyCompiler::RetExpression(const ErrorCode& err) { |
287 switch (err.error_type()) { | 288 switch (err.error_type()) { |
288 case ErrorCode::ET_COND: | 289 case ErrorCode::ET_COND: |
289 return CondExpression(err); | 290 return CondExpression(err); |
290 case ErrorCode::ET_SIMPLE: | 291 case ErrorCode::ET_SIMPLE: |
291 case ErrorCode::ET_TRAP: | 292 case ErrorCode::ET_TRAP: |
292 return gen_.MakeInstruction(BPF_RET + BPF_K, err.err()); | 293 return gen_.MakeInstruction(BPF_RET + BPF_K, err.err()); |
293 default: | 294 default: |
294 SANDBOX_DIE("ErrorCode is not suitable for returning from a BPF program"); | 295 LOG(FATAL) |
| 296 << "ErrorCode is not suitable for returning from a BPF program"; |
| 297 return CodeGen::kNullNode; |
295 } | 298 } |
296 } | 299 } |
297 | 300 |
298 CodeGen::Node PolicyCompiler::CondExpression(const ErrorCode& cond) { | 301 CodeGen::Node PolicyCompiler::CondExpression(const ErrorCode& cond) { |
299 // Sanity check that |cond| makes sense. | 302 // Sanity check that |cond| makes sense. |
300 if (cond.argno_ < 0 || cond.argno_ >= 6) { | 303 CHECK(cond.argno_ >= 0 && cond.argno_ < 6) << "Invalid argument number " |
301 SANDBOX_DIE("sandbox_bpf: invalid argument number"); | 304 << cond.argno_; |
| 305 CHECK(cond.width_ == ErrorCode::TP_32BIT || |
| 306 cond.width_ == ErrorCode::TP_64BIT) |
| 307 << "Invalid argument width " << cond.width_; |
| 308 CHECK_NE(0U, cond.mask_) << "Zero mask is invalid"; |
| 309 CHECK_EQ(cond.value_, cond.value_ & cond.mask_) |
| 310 << "Value contains masked out bits"; |
| 311 if (sizeof(void*) == 4) { |
| 312 CHECK_EQ(ErrorCode::TP_32BIT, cond.width_) |
| 313 << "Invalid width on 32-bit platform"; |
302 } | 314 } |
303 if (cond.width_ != ErrorCode::TP_32BIT && | 315 if (cond.width_ == ErrorCode::TP_32BIT) { |
304 cond.width_ != ErrorCode::TP_64BIT) { | 316 CHECK_EQ(0U, cond.mask_ >> 32) << "Mask exceeds argument size"; |
305 SANDBOX_DIE("sandbox_bpf: invalid argument width"); | 317 CHECK_EQ(0U, cond.value_ >> 32) << "Value exceeds argument size"; |
306 } | 318 } |
307 if (cond.mask_ == 0) { | |
308 SANDBOX_DIE("sandbox_bpf: zero mask is invalid"); | |
309 } | |
310 if ((cond.value_ & cond.mask_) != cond.value_) { | |
311 SANDBOX_DIE("sandbox_bpf: value contains masked out bits"); | |
312 } | |
313 if (cond.width_ == ErrorCode::TP_32BIT && | |
314 ((cond.mask_ >> 32) != 0 || (cond.value_ >> 32) != 0)) { | |
315 SANDBOX_DIE("sandbox_bpf: test exceeds argument size"); | |
316 } | |
317 // TODO(mdempsky): Reject TP_64BIT on 32-bit platforms. For now we allow it | |
318 // because some SandboxBPF unit tests exercise it. | |
319 | 319 |
320 CodeGen::Node passed = RetExpression(*cond.passed_); | 320 CodeGen::Node passed = RetExpression(*cond.passed_); |
321 CodeGen::Node failed = RetExpression(*cond.failed_); | 321 CodeGen::Node failed = RetExpression(*cond.failed_); |
322 | 322 |
323 // We want to emit code to check "(arg & mask) == value" where arg, mask, and | 323 // We want to emit code to check "(arg & mask) == value" where arg, mask, and |
324 // value are 64-bit values, but the BPF machine is only 32-bit. We implement | 324 // value are 64-bit values, but the BPF machine is only 32-bit. We implement |
325 // this by independently testing the upper and lower 32-bits and continuing to | 325 // this by independently testing the upper and lower 32-bits and continuing to |
326 // |passed| if both evaluate true, or to |failed| if either evaluate false. | 326 // |passed| if both evaluate true, or to |failed| if either evaluate false. |
327 return CondExpressionHalf(cond, | 327 return CondExpressionHalf(cond, |
328 UpperHalf, | 328 UpperHalf, |
(...skipping 160 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
489 return ErrorCode(argno, | 489 return ErrorCode(argno, |
490 width, | 490 width, |
491 mask, | 491 mask, |
492 value, | 492 value, |
493 &*conds_.insert(passed).first, | 493 &*conds_.insert(passed).first, |
494 &*conds_.insert(failed).first); | 494 &*conds_.insert(failed).first); |
495 } | 495 } |
496 | 496 |
497 } // namespace bpf_dsl | 497 } // namespace bpf_dsl |
498 } // namespace sandbox | 498 } // namespace sandbox |
OLD | NEW |