blob: 3ed06cb4013a68f832a808b26e2d398ef78cab5c [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 "sandbox/linux/seccomp-bpf/codegen.h"
#include <linux/filter.h>
#include <limits>
#include <utility>
#include "base/logging.h"
// This CodeGen implementation strives for simplicity while still
// generating acceptable BPF programs under typical usage patterns
// (e.g., by PolicyCompiler).
//
// The key to its simplicity is that BPF programs only support forward
// jumps/branches, which allows constraining the DAG construction API
// to make instruction nodes immutable. Immutable nodes admits a
// simple greedy approach of emitting new instructions as needed and
// then reusing existing ones that have already been emitted. This
// cleanly avoids any need to compute basic blocks or apply
// topological sorting because the API effectively sorts instructions
// for us (e.g., before MakeInstruction() can be called to emit a
// branch instruction, it must have already been called for each
// branch path).
//
// This greedy algorithm is not without (theoretical) weakness though:
//
// 1. In the general case, we don't eliminate dead code. If needed,
// we could trace back through the program in Compile() and elide
// any unneeded instructions, but in practice we only emit live
// instructions anyway.
//
// 2. By not dividing instructions into basic blocks and sorting, we
// lose an opportunity to move non-branch/non-return instructions
// adjacent to their successor instructions, which means we might
// need to emit additional jumps. But in practice, they'll
// already be nearby as long as callers don't go out of their way
// to interleave MakeInstruction() calls for unrelated code
// sequences.
namespace sandbox {
// kBranchRange is the maximum value that can be stored in
// sock_filter's 8-bit jt and jf fields.
const size_t kBranchRange = std::numeric_limits<uint8_t>::max();
const CodeGen::Node CodeGen::kNullNode;
CodeGen::CodeGen() : program_(), memos_() {
}
CodeGen::~CodeGen() {
}
void CodeGen::Compile(CodeGen::Node head, Program* out) {
DCHECK(out);
out->assign(program_.rbegin() + Offset(head), program_.rend());
}
CodeGen::Node CodeGen::MakeInstruction(uint16_t code,
uint32_t k,
Node jt,
Node jf) {
// To avoid generating redundant code sequences, we memoize the
// results from AppendInstruction().
auto res = memos_.insert(std::make_pair(MemoKey(code, k, jt, jf), kNullNode));
CodeGen::Node* node = &res.first->second;
if (res.second) { // Newly inserted memo entry.
*node = AppendInstruction(code, k, jt, jf);
}
return *node;
}
CodeGen::Node CodeGen::AppendInstruction(uint16_t code,
uint32_t k,
Node jt,
Node jf) {
if (BPF_CLASS(code) == BPF_JMP) {
CHECK_NE(BPF_JA, BPF_OP(code)) << "CodeGen inserts JAs as needed";
// We need to check |jt| twice because it might get pushed
// out-of-range by appending a jump for |jf|.
jt = WithinRange(jt, kBranchRange);
jf = WithinRange(jf, kBranchRange);
jt = WithinRange(jt, kBranchRange);
return Append(code, k, Offset(jt), Offset(jf));
}
CHECK_EQ(kNullNode, jf) << "Non-branch instructions shouldn't provide jf";
if (BPF_CLASS(code) == BPF_RET) {
CHECK_EQ(kNullNode, jt) << "Return instructions shouldn't provide jt";
} else {
// For non-branch/non-return instructions, execution always
// proceeds to the next instruction; so we need to arrange for
// that to be |jt|.
jt = WithinRange(jt, 0);
}
return Append(code, k, 0, 0);
}
CodeGen::Node CodeGen::WithinRange(Node target, size_t range) {
if (Offset(target) > range) {
// TODO(mdempsky): If |range > 0|, we might be able to reuse an
// existing instruction within that range.
// TODO(mdempsky): If |target| is a branch or return, it might be
// possible to duplicate that instruction rather than jump to it.
// Fall back to emitting a jump instruction.
target = Append(BPF_JMP | BPF_JA, Offset(target), 0, 0);
}
CHECK_LE(Offset(target), range) << "ICE: Failed to bring target within range";
return target;
}
CodeGen::Node CodeGen::Append(uint16_t code, uint32_t k, size_t jt, size_t jf) {
if (BPF_CLASS(code) == BPF_JMP && BPF_OP(code) != BPF_JA) {
CHECK_LE(jt, kBranchRange);
CHECK_LE(jf, kBranchRange);
} else {
CHECK_EQ(0U, jt);
CHECK_EQ(0U, jf);
}
CHECK_LT(program_.size(), static_cast<size_t>(BPF_MAXINSNS));
program_.push_back(sock_filter{code, jt, jf, k});
return program_.size() - 1;
}
size_t CodeGen::Offset(Node target) const {
CHECK_LT(target, program_.size()) << "Bogus offset target node";
return (program_.size() - 1) - target;
}
// TODO(mdempsky): Move into a general base::Tuple helper library.
bool CodeGen::MemoKeyLess::operator()(const MemoKey& lhs,
const MemoKey& rhs) const {
if (lhs.a != rhs.a)
return lhs.a < rhs.a;
if (lhs.b != rhs.b)
return lhs.b < rhs.b;
if (lhs.c != rhs.c)
return lhs.c < rhs.c;
if (lhs.d != rhs.d)
return lhs.d < rhs.d;
return false;
}
} // namespace sandbox