blob: 1292504decc012e9cfbc6c3e6eca39cc440b8cf6 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string.h>
#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 {
struct State {
State(const std::vector<struct sock_filter>& p,
const struct arch_seccomp_data& d)
: program(p), data(d), ip(0), accumulator(0), acc_is_valid(false) {}
const std::vector<struct sock_filter>& program;
const struct arch_seccomp_data& data;
unsigned int ip;
uint32_t accumulator;
bool acc_is_valid;
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(State);
};
uint32_t EvaluateErrorCode(SandboxBPF* sandbox,
const ErrorCode& code,
const struct arch_seccomp_data& data) {
if (code.error_type() == ErrorCode::ET_SIMPLE ||
code.error_type() == ErrorCode::ET_TRAP) {
return code.err();
} else if (code.error_type() == ErrorCode::ET_COND) {
if (code.width() == ErrorCode::TP_32BIT &&
(data.args[code.argno()] >> 32) &&
(data.args[code.argno()] & 0xFFFFFFFF80000000ull) !=
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;
}
} else {
return SECCOMP_RET_INVALID;
}
}
bool VerifyErrorCode(SandboxBPF* sandbox,
const std::vector<struct sock_filter>& program,
struct arch_seccomp_data* data,
const ErrorCode& root_code,
const ErrorCode& code,
const char** err) {
if (code.error_type() == ErrorCode::ET_SIMPLE ||
code.error_type() == ErrorCode::ET_TRAP) {
uint32_t computed_ret = Verifier::EvaluateBPF(program, *data, err);
if (*err) {
return false;
} else if (computed_ret != EvaluateErrorCode(sandbox, root_code, *data)) {
// For efficiency's sake, we'd much rather compare "computed_ret"
// against "code.err()". This works most of the time, but it doesn't
// always work for nested conditional expressions. The test values
// that we generate on the fly to probe expressions can trigger
// code flow decisions in multiple nodes of the decision tree, and the
// only way to compute the correct error code in that situation is by
// calling EvaluateErrorCode().
*err = "Exit code from BPF program doesn't match";
return false;
}
} else if (code.error_type() == ErrorCode::ET_COND) {
if (code.argno() < 0 || code.argno() >= 6) {
*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;
}
// 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.");
}
// 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) ||
// 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();
// 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();
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";
return false;
}
} else {
*err = "Attempting to return invalid error code from BPF program";
return false;
}
return true;
}
void Ld(State* state, const struct sock_filter& insn, const char** err) {
if (BPF_SIZE(insn.code) != BPF_W || BPF_MODE(insn.code) != BPF_ABS) {
*err = "Invalid BPF_LD instruction";
return;
}
if (insn.k < sizeof(struct arch_seccomp_data) && (insn.k & 3) == 0) {
// We only allow loading of properly aligned 32bit quantities.
memcpy(&state->accumulator,
reinterpret_cast<const char*>(&state->data) + insn.k,
4);
} else {
*err = "Invalid operand in BPF_LD instruction";
return;
}
state->acc_is_valid = true;
return;
}
void Jmp(State* state, const struct sock_filter& insn, const char** err) {
if (BPF_OP(insn.code) == BPF_JA) {
if (state->ip + insn.k + 1 >= state->program.size() ||
state->ip + insn.k + 1 <= state->ip) {
compilation_failure:
*err = "Invalid BPF_JMP instruction";
return;
}
state->ip += insn.k;
} else {
if (BPF_SRC(insn.code) != BPF_K || !state->acc_is_valid ||
state->ip + insn.jt + 1 >= state->program.size() ||
state->ip + insn.jf + 1 >= state->program.size()) {
goto compilation_failure;
}
switch (BPF_OP(insn.code)) {
case BPF_JEQ:
if (state->accumulator == insn.k) {
state->ip += insn.jt;
} else {
state->ip += insn.jf;
}
break;
case BPF_JGT:
if (state->accumulator > insn.k) {
state->ip += insn.jt;
} else {
state->ip += insn.jf;
}
break;
case BPF_JGE:
if (state->accumulator >= insn.k) {
state->ip += insn.jt;
} else {
state->ip += insn.jf;
}
break;
case BPF_JSET:
if (state->accumulator & insn.k) {
state->ip += insn.jt;
} else {
state->ip += insn.jf;
}
break;
default:
goto compilation_failure;
}
}
}
uint32_t Ret(State*, const struct sock_filter& insn, const char** err) {
if (BPF_SRC(insn.code) != BPF_K) {
*err = "Invalid BPF_RET instruction";
return 0;
}
return insn.k;
}
void Alu(State* state, const struct sock_filter& insn, const char** err) {
if (BPF_OP(insn.code) == BPF_NEG) {
state->accumulator = -state->accumulator;
return;
} else {
if (BPF_SRC(insn.code) != BPF_K) {
*err = "Unexpected source operand in arithmetic operation";
return;
}
switch (BPF_OP(insn.code)) {
case BPF_ADD:
state->accumulator += insn.k;
break;
case BPF_SUB:
state->accumulator -= insn.k;
break;
case BPF_MUL:
state->accumulator *= insn.k;
break;
case BPF_DIV:
if (!insn.k) {
*err = "Illegal division by zero";
break;
}
state->accumulator /= insn.k;
break;
case BPF_MOD:
if (!insn.k) {
*err = "Illegal division by zero";
break;
}
state->accumulator %= insn.k;
break;
case BPF_OR:
state->accumulator |= insn.k;
break;
case BPF_XOR:
state->accumulator ^= insn.k;
break;
case BPF_AND:
state->accumulator &= insn.k;
break;
case BPF_LSH:
if (insn.k > 32) {
*err = "Illegal shift operation";
break;
}
state->accumulator <<= insn.k;
break;
case BPF_RSH:
if (insn.k > 32) {
*err = "Illegal shift operation";
break;
}
state->accumulator >>= insn.k;
break;
default:
*err = "Invalid operator in arithmetic operation";
break;
}
}
}
} // namespace
bool Verifier::VerifyBPF(SandboxBPF* sandbox,
const std::vector<struct sock_filter>& program,
const SandboxBPFPolicy& policy,
const char** err) {
*err = NULL;
for (SyscallIterator iter(false); !iter.Done();) {
uint32_t sysnum = iter.Next();
// We ideally want to iterate over the full system call range and values
// just above and just below this range. This gives us the full result set
// of the "evaluators".
// On Intel systems, this can fail in a surprising way, as a cleared bit 30
// indicates either i386 or x86-64; and a set bit 30 indicates x32. And
// unless we pay attention to setting this bit correctly, an early check in
// our BPF program will make us fail with a misleading error code.
struct arch_seccomp_data data = {static_cast<int>(sysnum),
static_cast<uint32_t>(SECCOMP_ARCH)};
#if defined(__i386__) || defined(__x86_64__)
#if defined(__x86_64__) && defined(__ILP32__)
if (!(sysnum & 0x40000000u)) {
continue;
}
#else
if (sysnum & 0x40000000u) {
continue;
}
#endif
#endif
ErrorCode code = policy.EvaluateSyscall(sandbox, sysnum);
if (!VerifyErrorCode(sandbox, program, &data, code, code, err)) {
return false;
}
}
return true;
}
uint32_t Verifier::EvaluateBPF(const std::vector<struct sock_filter>& program,
const struct arch_seccomp_data& data,
const char** err) {
*err = NULL;
if (program.size() < 1 || program.size() >= SECCOMP_MAX_PROGRAM_SIZE) {
*err = "Invalid program length";
return 0;
}
for (State state(program, data); !*err; ++state.ip) {
if (state.ip >= program.size()) {
*err = "Invalid instruction pointer in BPF program";
break;
}
const struct sock_filter& insn = program[state.ip];
switch (BPF_CLASS(insn.code)) {
case BPF_LD:
Ld(&state, insn, err);
break;
case BPF_JMP:
Jmp(&state, insn, err);
break;
case BPF_RET: {
uint32_t r = Ret(&state, insn, err);
switch (r & SECCOMP_RET_ACTION) {
case SECCOMP_RET_TRAP:
case SECCOMP_RET_ERRNO:
case SECCOMP_RET_ALLOW:
break;
case SECCOMP_RET_KILL: // We don't ever generate this
case SECCOMP_RET_TRACE: // We don't ever generate this
case SECCOMP_RET_INVALID: // Should never show up in BPF program
default:
*err = "Unexpected return code found in BPF program";
return 0;
}
return r;
}
case BPF_ALU:
Alu(&state, insn, err);
break;
default:
*err = "Unexpected instruction in BPF program";
break;
}
}
return 0;
}
} // namespace sandbox