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

Side by Side Diff: sandbox/linux/bpf_dsl/policy_compiler.cc

Issue 670183003: Update from chromium 62675d9fb31fb8cedc40f68e78e8445a74f362e7 (Closed) Base URL: git@github.com:domokit/mojo.git@master
Patch Set: Created 6 years, 1 month 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 unified diff | Download patch
« no previous file with comments | « sandbox/linux/bpf_dsl/policy_compiler.h ('k') | sandbox/linux/bpf_dsl/trap_registry.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 // found in the LICENSE file.
4
5 #include "sandbox/linux/bpf_dsl/policy_compiler.h"
6
7 #include <errno.h>
8 #include <linux/filter.h>
9 #include <sys/syscall.h>
10
11 #include <limits>
12
13 #include "base/logging.h"
14 #include "base/macros.h"
15 #include "sandbox/linux/bpf_dsl/bpf_dsl.h"
16 #include "sandbox/linux/bpf_dsl/bpf_dsl_impl.h"
17 #include "sandbox/linux/seccomp-bpf/codegen.h"
18 #include "sandbox/linux/seccomp-bpf/die.h"
19 #include "sandbox/linux/seccomp-bpf/errorcode.h"
20 #include "sandbox/linux/seccomp-bpf/instruction.h"
21 #include "sandbox/linux/seccomp-bpf/linux_seccomp.h"
22 #include "sandbox/linux/seccomp-bpf/syscall.h"
23 #include "sandbox/linux/seccomp-bpf/syscall_iterator.h"
24
25 namespace sandbox {
26 namespace bpf_dsl {
27
28 namespace {
29
30 #if defined(__i386__) || defined(__x86_64__)
31 const bool kIsIntel = true;
32 #else
33 const bool kIsIntel = false;
34 #endif
35 #if defined(__x86_64__) && defined(__ILP32__)
36 const bool kIsX32 = true;
37 #else
38 const bool kIsX32 = false;
39 #endif
40
41 const int kSyscallsRequiredForUnsafeTraps[] = {
42 __NR_rt_sigprocmask,
43 __NR_rt_sigreturn,
44 #if defined(__NR_sigprocmask)
45 __NR_sigprocmask,
46 #endif
47 #if defined(__NR_sigreturn)
48 __NR_sigreturn,
49 #endif
50 };
51
52 bool HasExactlyOneBit(uint64_t x) {
53 // Common trick; e.g., see http://stackoverflow.com/a/108329.
54 return x != 0 && (x & (x - 1)) == 0;
55 }
56
57 bool IsDenied(const ErrorCode& code) {
58 return (code.err() & SECCOMP_RET_ACTION) == SECCOMP_RET_TRAP ||
59 (code.err() >= (SECCOMP_RET_ERRNO + ErrorCode::ERR_MIN_ERRNO) &&
60 code.err() <= (SECCOMP_RET_ERRNO + ErrorCode::ERR_MAX_ERRNO));
61 }
62
63 // A Trap() handler that returns an "errno" value. The value is encoded
64 // in the "aux" parameter.
65 intptr_t ReturnErrno(const struct arch_seccomp_data&, void* aux) {
66 // TrapFnc functions report error by following the native kernel convention
67 // of returning an exit code in the range of -1..-4096. They do not try to
68 // set errno themselves. The glibc wrapper that triggered the SIGSYS will
69 // ultimately do so for us.
70 int err = reinterpret_cast<intptr_t>(aux) & SECCOMP_RET_DATA;
71 return -err;
72 }
73
74 intptr_t BPFFailure(const struct arch_seccomp_data&, void* aux) {
75 SANDBOX_DIE(static_cast<char*>(aux));
76 }
77
78 bool HasUnsafeTraps(const SandboxBPFDSLPolicy* policy) {
79 for (uint32_t sysnum : SyscallSet::All()) {
80 if (SyscallSet::IsValid(sysnum) &&
81 policy->EvaluateSyscall(sysnum)->HasUnsafeTraps()) {
82 return true;
83 }
84 }
85 return policy->InvalidSyscall()->HasUnsafeTraps();
86 }
87
88 } // namespace
89
90 struct PolicyCompiler::Range {
91 Range(uint32_t f, const ErrorCode& e) : from(f), err(e) {}
92 uint32_t from;
93 ErrorCode err;
94 };
95
96 PolicyCompiler::PolicyCompiler(const SandboxBPFDSLPolicy* policy,
97 TrapRegistry* registry)
98 : policy_(policy),
99 registry_(registry),
100 conds_(),
101 gen_(),
102 has_unsafe_traps_(HasUnsafeTraps(policy_)) {
103 }
104
105 PolicyCompiler::~PolicyCompiler() {
106 }
107
108 scoped_ptr<CodeGen::Program> PolicyCompiler::Compile() {
109 if (!IsDenied(policy_->InvalidSyscall()->Compile(this))) {
110 SANDBOX_DIE("Policies should deny invalid system calls.");
111 }
112
113 // If our BPF program has unsafe traps, enable support for them.
114 if (has_unsafe_traps_) {
115 // As support for unsafe jumps essentially defeats all the security
116 // measures that the sandbox provides, we print a big warning message --
117 // and of course, we make sure to only ever enable this feature if it
118 // is actually requested by the sandbox policy.
119 if (Syscall::Call(-1) == -1 && errno == ENOSYS) {
120 SANDBOX_DIE(
121 "Support for UnsafeTrap() has not yet been ported to this "
122 "architecture");
123 }
124
125 for (int sysnum : kSyscallsRequiredForUnsafeTraps) {
126 if (!policy_->EvaluateSyscall(sysnum)->Compile(this)
127 .Equals(ErrorCode(ErrorCode::ERR_ALLOWED))) {
128 SANDBOX_DIE(
129 "Policies that use UnsafeTrap() must unconditionally allow all "
130 "required system calls");
131 }
132 }
133
134 if (!registry_->EnableUnsafeTraps()) {
135 // We should never be able to get here, as UnsafeTrap() should never
136 // actually return a valid ErrorCode object unless the user set the
137 // CHROME_SANDBOX_DEBUGGING environment variable; and therefore,
138 // "has_unsafe_traps" would always be false. But better double-check
139 // than enabling dangerous code.
140 SANDBOX_DIE("We'd rather die than enable unsafe traps");
141 }
142 }
143
144 // Assemble the BPF filter program.
145 scoped_ptr<CodeGen::Program> program(new CodeGen::Program());
146 gen_.Compile(AssemblePolicy(), program.get());
147 return program.Pass();
148 }
149
150 Instruction* PolicyCompiler::AssemblePolicy() {
151 // A compiled policy consists of three logical parts:
152 // 1. Check that the "arch" field matches the expected architecture.
153 // 2. If the policy involves unsafe traps, check if the syscall was
154 // invoked by Syscall::Call, and then allow it unconditionally.
155 // 3. Check the system call number and jump to the appropriate compiled
156 // system call policy number.
157 return CheckArch(MaybeAddEscapeHatch(DispatchSyscall()));
158 }
159
160 Instruction* PolicyCompiler::CheckArch(Instruction* passed) {
161 // If the architecture doesn't match SECCOMP_ARCH, disallow the
162 // system call.
163 return gen_.MakeInstruction(
164 BPF_LD + BPF_W + BPF_ABS,
165 SECCOMP_ARCH_IDX,
166 gen_.MakeInstruction(
167 BPF_JMP + BPF_JEQ + BPF_K,
168 SECCOMP_ARCH,
169 passed,
170 RetExpression(Kill("Invalid audit architecture in BPF filter"))));
171 }
172
173 Instruction* PolicyCompiler::MaybeAddEscapeHatch(Instruction* rest) {
174 // If no unsafe traps, then simply return |rest|.
175 if (!has_unsafe_traps_) {
176 return rest;
177 }
178
179 // Allow system calls, if they originate from our magic return address
180 // (which we can query by calling Syscall::Call(-1)).
181 uint64_t syscall_entry_point =
182 static_cast<uint64_t>(static_cast<uintptr_t>(Syscall::Call(-1)));
183 uint32_t low = static_cast<uint32_t>(syscall_entry_point);
184 uint32_t hi = static_cast<uint32_t>(syscall_entry_point >> 32);
185
186 // BPF cannot do native 64-bit comparisons, so we have to compare
187 // both 32-bit halves of the instruction pointer. If they match what
188 // we expect, we return ERR_ALLOWED. If either or both don't match,
189 // we continue evalutating the rest of the sandbox policy.
190 //
191 // For simplicity, we check the full 64-bit instruction pointer even
192 // on 32-bit architectures.
193 return gen_.MakeInstruction(
194 BPF_LD + BPF_W + BPF_ABS,
195 SECCOMP_IP_LSB_IDX,
196 gen_.MakeInstruction(
197 BPF_JMP + BPF_JEQ + BPF_K,
198 low,
199 gen_.MakeInstruction(
200 BPF_LD + BPF_W + BPF_ABS,
201 SECCOMP_IP_MSB_IDX,
202 gen_.MakeInstruction(
203 BPF_JMP + BPF_JEQ + BPF_K,
204 hi,
205 RetExpression(ErrorCode(ErrorCode::ERR_ALLOWED)),
206 rest)),
207 rest));
208 }
209
210 Instruction* PolicyCompiler::DispatchSyscall() {
211 // Evaluate all possible system calls and group their ErrorCodes into
212 // ranges of identical codes.
213 Ranges ranges;
214 FindRanges(&ranges);
215
216 // Compile the system call ranges to an optimized BPF jumptable
217 Instruction* jumptable = AssembleJumpTable(ranges.begin(), ranges.end());
218
219 // Grab the system call number, so that we can check it and then
220 // execute the jump table.
221 return gen_.MakeInstruction(
222 BPF_LD + BPF_W + BPF_ABS, SECCOMP_NR_IDX, CheckSyscallNumber(jumptable));
223 }
224
225 Instruction* PolicyCompiler::CheckSyscallNumber(Instruction* passed) {
226 if (kIsIntel) {
227 // On Intel architectures, verify that system call numbers are in the
228 // expected number range.
229 Instruction* invalidX32 =
230 RetExpression(Kill("Illegal mixing of system call ABIs"));
231 if (kIsX32) {
232 // The newer x32 API always sets bit 30.
233 return gen_.MakeInstruction(
234 BPF_JMP + BPF_JSET + BPF_K, 0x40000000, passed, invalidX32);
235 } else {
236 // The older i386 and x86-64 APIs clear bit 30 on all system calls.
237 return gen_.MakeInstruction(
238 BPF_JMP + BPF_JSET + BPF_K, 0x40000000, invalidX32, passed);
239 }
240 }
241
242 // TODO(mdempsky): Similar validation for other architectures?
243 return passed;
244 }
245
246 void PolicyCompiler::FindRanges(Ranges* ranges) {
247 // Please note that "struct seccomp_data" defines system calls as a signed
248 // int32_t, but BPF instructions always operate on unsigned quantities. We
249 // deal with this disparity by enumerating from MIN_SYSCALL to MAX_SYSCALL,
250 // and then verifying that the rest of the number range (both positive and
251 // negative) all return the same ErrorCode.
252 const ErrorCode invalid_err = policy_->InvalidSyscall()->Compile(this);
253 uint32_t old_sysnum = 0;
254 ErrorCode old_err = SyscallSet::IsValid(old_sysnum)
255 ? policy_->EvaluateSyscall(old_sysnum)->Compile(this)
256 : invalid_err;
257
258 for (uint32_t sysnum : SyscallSet::All()) {
259 ErrorCode err =
260 SyscallSet::IsValid(sysnum)
261 ? policy_->EvaluateSyscall(static_cast<int>(sysnum))->Compile(this)
262 : invalid_err;
263 if (!err.Equals(old_err)) {
264 ranges->push_back(Range(old_sysnum, old_err));
265 old_sysnum = sysnum;
266 old_err = err;
267 }
268 }
269 ranges->push_back(Range(old_sysnum, old_err));
270 }
271
272 Instruction* PolicyCompiler::AssembleJumpTable(Ranges::const_iterator start,
273 Ranges::const_iterator stop) {
274 // We convert the list of system call ranges into jump table that performs
275 // a binary search over the ranges.
276 // As a sanity check, we need to have at least one distinct ranges for us
277 // to be able to build a jump table.
278 if (stop - start <= 0) {
279 SANDBOX_DIE("Invalid set of system call ranges");
280 } else if (stop - start == 1) {
281 // If we have narrowed things down to a single range object, we can
282 // return from the BPF filter program.
283 return RetExpression(start->err);
284 }
285
286 // Pick the range object that is located at the mid point of our list.
287 // We compare our system call number against the lowest valid system call
288 // number in this range object. If our number is lower, it is outside of
289 // this range object. If it is greater or equal, it might be inside.
290 Ranges::const_iterator mid = start + (stop - start) / 2;
291
292 // Sub-divide the list of ranges and continue recursively.
293 Instruction* jf = AssembleJumpTable(start, mid);
294 Instruction* jt = AssembleJumpTable(mid, stop);
295 return gen_.MakeInstruction(BPF_JMP + BPF_JGE + BPF_K, mid->from, jt, jf);
296 }
297
298 Instruction* PolicyCompiler::RetExpression(const ErrorCode& err) {
299 switch (err.error_type()) {
300 case ErrorCode::ET_COND:
301 return CondExpression(err);
302 case ErrorCode::ET_SIMPLE:
303 case ErrorCode::ET_TRAP:
304 return gen_.MakeInstruction(BPF_RET + BPF_K, err.err());
305 default:
306 SANDBOX_DIE("ErrorCode is not suitable for returning from a BPF program");
307 }
308 }
309
310 Instruction* PolicyCompiler::CondExpression(const ErrorCode& cond) {
311 // Sanity check that |cond| makes sense.
312 if (cond.argno_ < 0 || cond.argno_ >= 6) {
313 SANDBOX_DIE("sandbox_bpf: invalid argument number");
314 }
315 if (cond.width_ != ErrorCode::TP_32BIT &&
316 cond.width_ != ErrorCode::TP_64BIT) {
317 SANDBOX_DIE("sandbox_bpf: invalid argument width");
318 }
319 if (cond.mask_ == 0) {
320 SANDBOX_DIE("sandbox_bpf: zero mask is invalid");
321 }
322 if ((cond.value_ & cond.mask_) != cond.value_) {
323 SANDBOX_DIE("sandbox_bpf: value contains masked out bits");
324 }
325 if (cond.width_ == ErrorCode::TP_32BIT &&
326 ((cond.mask_ >> 32) != 0 || (cond.value_ >> 32) != 0)) {
327 SANDBOX_DIE("sandbox_bpf: test exceeds argument size");
328 }
329 // TODO(mdempsky): Reject TP_64BIT on 32-bit platforms. For now we allow it
330 // because some SandboxBPF unit tests exercise it.
331
332 Instruction* passed = RetExpression(*cond.passed_);
333 Instruction* failed = RetExpression(*cond.failed_);
334
335 // We want to emit code to check "(arg & mask) == value" where arg, mask, and
336 // value are 64-bit values, but the BPF machine is only 32-bit. We implement
337 // this by independently testing the upper and lower 32-bits and continuing to
338 // |passed| if both evaluate true, or to |failed| if either evaluate false.
339 return CondExpressionHalf(cond,
340 UpperHalf,
341 CondExpressionHalf(cond, LowerHalf, passed, failed),
342 failed);
343 }
344
345 Instruction* PolicyCompiler::CondExpressionHalf(const ErrorCode& cond,
346 ArgHalf half,
347 Instruction* passed,
348 Instruction* failed) {
349 if (cond.width_ == ErrorCode::TP_32BIT && half == UpperHalf) {
350 // Special logic for sanity checking the upper 32-bits of 32-bit system
351 // call arguments.
352
353 // TODO(mdempsky): Compile Unexpected64bitArgument() just per program.
354 Instruction* invalid_64bit = RetExpression(Unexpected64bitArgument());
355
356 const uint32_t upper = SECCOMP_ARG_MSB_IDX(cond.argno_);
357 const uint32_t lower = SECCOMP_ARG_LSB_IDX(cond.argno_);
358
359 if (sizeof(void*) == 4) {
360 // On 32-bit platforms, the upper 32-bits should always be 0:
361 // LDW [upper]
362 // JEQ 0, passed, invalid
363 return gen_.MakeInstruction(
364 BPF_LD + BPF_W + BPF_ABS,
365 upper,
366 gen_.MakeInstruction(
367 BPF_JMP + BPF_JEQ + BPF_K, 0, passed, invalid_64bit));
368 }
369
370 // On 64-bit platforms, the upper 32-bits may be 0 or ~0; but we only allow
371 // ~0 if the sign bit of the lower 32-bits is set too:
372 // LDW [upper]
373 // JEQ 0, passed, (next)
374 // JEQ ~0, (next), invalid
375 // LDW [lower]
376 // JSET (1<<31), passed, invalid
377 //
378 // TODO(mdempsky): The JSET instruction could perhaps jump to passed->next
379 // instead, as the first instruction of passed should be "LDW [lower]".
380 return gen_.MakeInstruction(
381 BPF_LD + BPF_W + BPF_ABS,
382 upper,
383 gen_.MakeInstruction(
384 BPF_JMP + BPF_JEQ + BPF_K,
385 0,
386 passed,
387 gen_.MakeInstruction(
388 BPF_JMP + BPF_JEQ + BPF_K,
389 std::numeric_limits<uint32_t>::max(),
390 gen_.MakeInstruction(
391 BPF_LD + BPF_W + BPF_ABS,
392 lower,
393 gen_.MakeInstruction(BPF_JMP + BPF_JSET + BPF_K,
394 1U << 31,
395 passed,
396 invalid_64bit)),
397 invalid_64bit)));
398 }
399
400 const uint32_t idx = (half == UpperHalf) ? SECCOMP_ARG_MSB_IDX(cond.argno_)
401 : SECCOMP_ARG_LSB_IDX(cond.argno_);
402 const uint32_t mask = (half == UpperHalf) ? cond.mask_ >> 32 : cond.mask_;
403 const uint32_t value = (half == UpperHalf) ? cond.value_ >> 32 : cond.value_;
404
405 // Emit a suitable instruction sequence for (arg & mask) == value.
406
407 // For (arg & 0) == 0, just return passed.
408 if (mask == 0) {
409 CHECK_EQ(0U, value);
410 return passed;
411 }
412
413 // For (arg & ~0) == value, emit:
414 // LDW [idx]
415 // JEQ value, passed, failed
416 if (mask == std::numeric_limits<uint32_t>::max()) {
417 return gen_.MakeInstruction(
418 BPF_LD + BPF_W + BPF_ABS,
419 idx,
420 gen_.MakeInstruction(BPF_JMP + BPF_JEQ + BPF_K, value, passed, failed));
421 }
422
423 // For (arg & mask) == 0, emit:
424 // LDW [idx]
425 // JSET mask, failed, passed
426 // (Note: failed and passed are intentionally swapped.)
427 if (value == 0) {
428 return gen_.MakeInstruction(
429 BPF_LD + BPF_W + BPF_ABS,
430 idx,
431 gen_.MakeInstruction(BPF_JMP + BPF_JSET + BPF_K, mask, failed, passed));
432 }
433
434 // For (arg & x) == x where x is a single-bit value, emit:
435 // LDW [idx]
436 // JSET mask, passed, failed
437 if (mask == value && HasExactlyOneBit(mask)) {
438 return gen_.MakeInstruction(
439 BPF_LD + BPF_W + BPF_ABS,
440 idx,
441 gen_.MakeInstruction(BPF_JMP + BPF_JSET + BPF_K, mask, passed, failed));
442 }
443
444 // Generic fallback:
445 // LDW [idx]
446 // AND mask
447 // JEQ value, passed, failed
448 return gen_.MakeInstruction(
449 BPF_LD + BPF_W + BPF_ABS,
450 idx,
451 gen_.MakeInstruction(
452 BPF_ALU + BPF_AND + BPF_K,
453 mask,
454 gen_.MakeInstruction(
455 BPF_JMP + BPF_JEQ + BPF_K, value, passed, failed)));
456 }
457
458 ErrorCode PolicyCompiler::Unexpected64bitArgument() {
459 return Kill("Unexpected 64bit argument detected");
460 }
461
462 ErrorCode PolicyCompiler::Error(int err) {
463 if (has_unsafe_traps_) {
464 // When inside an UnsafeTrap() callback, we want to allow all system calls.
465 // This means, we must conditionally disable the sandbox -- and that's not
466 // something that kernel-side BPF filters can do, as they cannot inspect
467 // any state other than the syscall arguments.
468 // But if we redirect all error handlers to user-space, then we can easily
469 // make this decision.
470 // The performance penalty for this extra round-trip to user-space is not
471 // actually that bad, as we only ever pay it for denied system calls; and a
472 // typical program has very few of these.
473 return Trap(ReturnErrno, reinterpret_cast<void*>(err));
474 }
475
476 return ErrorCode(err);
477 }
478
479 ErrorCode PolicyCompiler::MakeTrap(TrapRegistry::TrapFnc fnc,
480 const void* aux,
481 bool safe) {
482 uint16_t trap_id = registry_->Add(fnc, aux, safe);
483 return ErrorCode(trap_id, fnc, aux, safe);
484 }
485
486 ErrorCode PolicyCompiler::Trap(TrapRegistry::TrapFnc fnc, const void* aux) {
487 return MakeTrap(fnc, aux, true /* Safe Trap */);
488 }
489
490 ErrorCode PolicyCompiler::UnsafeTrap(TrapRegistry::TrapFnc fnc,
491 const void* aux) {
492 return MakeTrap(fnc, aux, false /* Unsafe Trap */);
493 }
494
495 bool PolicyCompiler::IsRequiredForUnsafeTrap(int sysno) {
496 for (size_t i = 0; i < arraysize(kSyscallsRequiredForUnsafeTraps); ++i) {
497 if (sysno == kSyscallsRequiredForUnsafeTraps[i]) {
498 return true;
499 }
500 }
501 return false;
502 }
503
504 ErrorCode PolicyCompiler::CondMaskedEqual(int argno,
505 ErrorCode::ArgType width,
506 uint64_t mask,
507 uint64_t value,
508 const ErrorCode& passed,
509 const ErrorCode& failed) {
510 return ErrorCode(argno,
511 width,
512 mask,
513 value,
514 &*conds_.insert(passed).first,
515 &*conds_.insert(failed).first);
516 }
517
518 ErrorCode PolicyCompiler::Kill(const char* msg) {
519 return Trap(BPFFailure, const_cast<char*>(msg));
520 }
521
522 } // namespace bpf_dsl
523 } // namespace sandbox
OLDNEW
« no previous file with comments | « sandbox/linux/bpf_dsl/policy_compiler.h ('k') | sandbox/linux/bpf_dsl/trap_registry.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698