summaryrefslogtreecommitdiff
path: root/chromium/sandbox/linux/seccomp-bpf/codegen_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/sandbox/linux/seccomp-bpf/codegen_unittest.cc')
-rw-r--r--chromium/sandbox/linux/seccomp-bpf/codegen_unittest.cc445
1 files changed, 445 insertions, 0 deletions
diff --git a/chromium/sandbox/linux/seccomp-bpf/codegen_unittest.cc b/chromium/sandbox/linux/seccomp-bpf/codegen_unittest.cc
new file mode 100644
index 00000000000..d24bcf280a6
--- /dev/null
+++ b/chromium/sandbox/linux/seccomp-bpf/codegen_unittest.cc
@@ -0,0 +1,445 @@
+// 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 <algorithm>
+#include <set>
+#include <vector>
+
+#include "sandbox/linux/seccomp-bpf/codegen.h"
+#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h"
+#include "sandbox/linux/tests/unit_tests.h"
+
+namespace playground2 {
+
+class SandboxUnittestHelper : public Sandbox {
+ public:
+ typedef Sandbox::Program Program;
+};
+
+// We want to access some of the private methods in the code generator. We
+// do so by defining a "friend" that makes these methods public for us.
+class CodeGenUnittestHelper : public CodeGen {
+ public:
+ void FindBranchTargets(const Instruction& instructions,
+ BranchTargets *branch_targets) {
+ CodeGen::FindBranchTargets(instructions, branch_targets);
+ }
+
+ BasicBlock *CutGraphIntoBasicBlocks(Instruction *insns,
+ const BranchTargets& branch_targets,
+ TargetsToBlocks *blocks) {
+ return CodeGen::CutGraphIntoBasicBlocks(insns, branch_targets, blocks);
+ }
+
+ void MergeTails(TargetsToBlocks *blocks) {
+ CodeGen::MergeTails(blocks);
+ }
+};
+
+enum { NO_FLAGS = 0x0000,
+ HAS_MERGEABLE_TAILS = 0x0001,
+};
+
+Instruction *SampleProgramOneInstruction(CodeGen *codegen, int *flags) {
+ // Create the most basic valid BPF program:
+ // RET ERR_ALLOWED
+ *flags = NO_FLAGS;
+ return codegen->MakeInstruction(BPF_RET+BPF_K,
+ ErrorCode(ErrorCode::ERR_ALLOWED));
+}
+
+Instruction *SampleProgramSimpleBranch(CodeGen *codegen, int *flags) {
+ // Create a program with a single branch:
+ // JUMP if eq 42 then $0 else $1
+ // 0: RET EPERM
+ // 1: RET ERR_ALLOWED
+ *flags = NO_FLAGS;
+ return codegen->MakeInstruction(BPF_JMP+BPF_JEQ+BPF_K, 42,
+ codegen->MakeInstruction(BPF_RET+BPF_K,
+ ErrorCode(EPERM)),
+ codegen->MakeInstruction(BPF_RET+BPF_K,
+ ErrorCode(ErrorCode::ERR_ALLOWED)));
+}
+
+Instruction *SampleProgramAtypicalBranch(CodeGen *codegen, int *flags) {
+ // Create a program with a single branch:
+ // JUMP if eq 42 then $0 else $0
+ // 0: RET ERR_ALLOWED
+
+ // N.B.: As the instructions in both sides of the branch are already
+ // the same object, we do not actually have any "mergeable" branches.
+ // This needs to be reflected in our choice of "flags".
+ *flags = NO_FLAGS;
+
+ Instruction *ret =
+ codegen->MakeInstruction(BPF_RET+BPF_K,
+ ErrorCode(ErrorCode::ERR_ALLOWED));
+ return codegen->MakeInstruction(BPF_JMP+BPF_JEQ+BPF_K, 42, ret, ret);
+}
+
+Instruction *SampleProgramComplex(CodeGen *codegen, int *flags) {
+ // Creates a basic BPF program that we'll use to test some of the code:
+ // JUMP if eq 42 the $0 else $1 (insn6)
+ // 0: LD 23 (insn5)
+ // 1: JUMP if eq 42 then $2 else $4 (insn4)
+ // 2: JUMP to $3 (insn1)
+ // 3: LD 42 (insn0)
+ // RET ErrorCode(42) (insn2)
+ // 4: LD 42 (insn3)
+ // RET ErrorCode(42) (insn3+)
+ *flags = HAS_MERGEABLE_TAILS;
+
+ Instruction *insn0 = codegen->MakeInstruction(BPF_LD+BPF_W+BPF_ABS, 42);
+ SANDBOX_ASSERT(insn0);
+ SANDBOX_ASSERT(insn0->code == BPF_LD+BPF_W+BPF_ABS);
+ SANDBOX_ASSERT(insn0->k == 42);
+ SANDBOX_ASSERT(insn0->next == NULL);
+
+ Instruction *insn1 = codegen->MakeInstruction(BPF_JMP+BPF_JA, 0, insn0);
+ SANDBOX_ASSERT(insn1);
+ SANDBOX_ASSERT(insn1->code == BPF_JMP+BPF_JA);
+ SANDBOX_ASSERT(insn1->jt_ptr == insn0);
+
+ Instruction *insn2 = codegen->MakeInstruction(BPF_RET+BPF_K, ErrorCode(42));
+ SANDBOX_ASSERT(insn2);
+ SANDBOX_ASSERT(insn2->code == BPF_RET+BPF_K);
+ SANDBOX_ASSERT(insn2->next == NULL);
+
+ // We explicitly duplicate instructions so that MergeTails() can coalesce
+ // them later.
+ Instruction *insn3 = codegen->MakeInstruction(BPF_LD+BPF_W+BPF_ABS, 42,
+ codegen->MakeInstruction(BPF_RET+BPF_K, ErrorCode(42)));
+
+ Instruction *insn4 = codegen->MakeInstruction(BPF_JMP+BPF_JEQ+BPF_K, 42,
+ insn1, insn3);
+ SANDBOX_ASSERT(insn4);
+ SANDBOX_ASSERT(insn4->code == BPF_JMP+BPF_JEQ+BPF_K);
+ SANDBOX_ASSERT(insn4->k == 42);
+ SANDBOX_ASSERT(insn4->jt_ptr == insn1);
+ SANDBOX_ASSERT(insn4->jf_ptr == insn3);
+
+ codegen->JoinInstructions(insn0, insn2);
+ SANDBOX_ASSERT(insn0->next == insn2);
+
+ Instruction *insn5 = codegen->MakeInstruction(BPF_LD+BPF_W+BPF_ABS,
+ 23, insn4);
+ SANDBOX_ASSERT(insn5);
+ SANDBOX_ASSERT(insn5->code == BPF_LD+BPF_W+BPF_ABS);
+ SANDBOX_ASSERT(insn5->k == 23);
+ SANDBOX_ASSERT(insn5->next == insn4);
+
+ // Force a basic block that ends in neither a jump instruction nor a return
+ // instruction. It only contains "insn5". This exercises one of the less
+ // common code paths in the topo-sort algorithm.
+ // This also gives us a diamond-shaped pattern in our graph, which stresses
+ // another aspect of the topo-sort algorithm (namely, the ability to
+ // correctly count the incoming branches for subtrees that are not disjunct).
+ Instruction *insn6 = codegen->MakeInstruction(BPF_JMP+BPF_JEQ+BPF_K, 42,
+ insn5, insn4);
+
+ return insn6;
+}
+
+void ForAllPrograms(void (*test)(CodeGenUnittestHelper *, Instruction *, int)){
+ Instruction *(*function_table[])(CodeGen *codegen, int *flags) = {
+ SampleProgramOneInstruction,
+ SampleProgramSimpleBranch,
+ SampleProgramAtypicalBranch,
+ SampleProgramComplex,
+ };
+
+ for (size_t i = 0; i < arraysize(function_table); ++i) {
+ CodeGenUnittestHelper codegen;
+ int flags = NO_FLAGS;
+ Instruction *prg = function_table[i](&codegen, &flags);
+ test(&codegen, prg, flags);
+ }
+}
+
+void MakeInstruction(CodeGenUnittestHelper *codegen,
+ Instruction *program, int) {
+ // Nothing to do here
+}
+
+SANDBOX_TEST(CodeGen, MakeInstruction) {
+ ForAllPrograms(MakeInstruction);
+}
+
+void FindBranchTargets(CodeGenUnittestHelper *codegen, Instruction *prg, int) {
+ BranchTargets branch_targets;
+ codegen->FindBranchTargets(*prg, &branch_targets);
+
+ // Verifying the general properties that should be true for every
+ // well-formed BPF program.
+ // Perform a depth-first traversal of the BPF program an verify that all
+ // targets of BPF_JMP instructions are represented in the "branch_targets".
+ // At the same time, compute a set of both the branch targets and all the
+ // instructions in the program.
+ std::vector<Instruction *> stack;
+ std::set<Instruction *> all_instructions;
+ std::set<Instruction *> target_instructions;
+ BranchTargets::const_iterator end = branch_targets.end();
+ for (Instruction *insn = prg;;) {
+ all_instructions.insert(insn);
+ if (BPF_CLASS(insn->code) == BPF_JMP) {
+ target_instructions.insert(insn->jt_ptr);
+ SANDBOX_ASSERT(insn->jt_ptr != NULL);
+ SANDBOX_ASSERT(branch_targets.find(insn->jt_ptr) != end);
+ if (BPF_OP(insn->code) != BPF_JA) {
+ target_instructions.insert(insn->jf_ptr);
+ SANDBOX_ASSERT(insn->jf_ptr != NULL);
+ SANDBOX_ASSERT(branch_targets.find(insn->jf_ptr) != end);
+ stack.push_back(insn->jf_ptr);
+ }
+ insn = insn->jt_ptr;
+ } else if (BPF_CLASS(insn->code) == BPF_RET) {
+ SANDBOX_ASSERT(insn->next == NULL);
+ if (stack.empty()) {
+ break;
+ }
+ insn = stack.back();
+ stack.pop_back();
+ } else {
+ SANDBOX_ASSERT(insn->next != NULL);
+ insn = insn->next;
+ }
+ }
+ SANDBOX_ASSERT(target_instructions.size() == branch_targets.size());
+
+ // We can now subtract the set of the branch targets from the set of all
+ // instructions. This gives us a set with the instructions that nobody
+ // ever jumps to. Verify that they are no included in the
+ // "branch_targets" that FindBranchTargets() computed for us.
+ Instructions non_target_instructions(all_instructions.size() -
+ target_instructions.size());
+ set_difference(all_instructions.begin(), all_instructions.end(),
+ target_instructions.begin(), target_instructions.end(),
+ non_target_instructions.begin());
+ for (Instructions::const_iterator iter = non_target_instructions.begin();
+ iter != non_target_instructions.end();
+ ++iter) {
+ SANDBOX_ASSERT(branch_targets.find(*iter) == end);
+ }
+}
+
+SANDBOX_TEST(CodeGen, FindBranchTargets) {
+ ForAllPrograms(FindBranchTargets);
+}
+
+void CutGraphIntoBasicBlocks(CodeGenUnittestHelper *codegen,
+ Instruction *prg, int) {
+ BranchTargets branch_targets;
+ codegen->FindBranchTargets(*prg, &branch_targets);
+ TargetsToBlocks all_blocks;
+ BasicBlock *first_block =
+ codegen->CutGraphIntoBasicBlocks(prg, branch_targets, &all_blocks);
+ SANDBOX_ASSERT(first_block != NULL);
+ SANDBOX_ASSERT(first_block->instructions.size() > 0);
+ Instruction *first_insn = first_block->instructions[0];
+
+ // Basic blocks are supposed to start with a branch target and end with
+ // either a jump or a return instruction. It can also end, if the next
+ // instruction forms the beginning of a new basic block. There should be
+ // no other jumps or return instructions in the middle of a basic block.
+ for (TargetsToBlocks::const_iterator bb_iter = all_blocks.begin();
+ bb_iter != all_blocks.end();
+ ++bb_iter) {
+ BasicBlock *bb = bb_iter->second;
+ SANDBOX_ASSERT(bb != NULL);
+ SANDBOX_ASSERT(bb->instructions.size() > 0);
+ Instruction *insn = bb->instructions[0];
+ SANDBOX_ASSERT(insn == first_insn ||
+ branch_targets.find(insn) != branch_targets.end());
+ for (Instructions::const_iterator insn_iter = bb->instructions.begin();;){
+ insn = *insn_iter;
+ if (++insn_iter != bb->instructions.end()) {
+ SANDBOX_ASSERT(BPF_CLASS(insn->code) != BPF_JMP);
+ SANDBOX_ASSERT(BPF_CLASS(insn->code) != BPF_RET);
+ } else {
+ SANDBOX_ASSERT(BPF_CLASS(insn->code) == BPF_JMP ||
+ BPF_CLASS(insn->code) == BPF_RET ||
+ branch_targets.find(insn->next) !=
+ branch_targets.end());
+ break;
+ }
+ SANDBOX_ASSERT(branch_targets.find(*insn_iter) == branch_targets.end());
+ }
+ }
+}
+
+SANDBOX_TEST(CodeGen, CutGraphIntoBasicBlocks) {
+ ForAllPrograms(CutGraphIntoBasicBlocks);
+}
+
+void MergeTails(CodeGenUnittestHelper *codegen, Instruction *prg,
+ int flags) {
+ BranchTargets branch_targets;
+ codegen->FindBranchTargets(*prg, &branch_targets);
+ TargetsToBlocks all_blocks;
+ BasicBlock *first_block =
+ codegen->CutGraphIntoBasicBlocks(prg, branch_targets, &all_blocks);
+
+ // The shape of our graph and thus the function of our program should
+ // still be unchanged after we run MergeTails(). We verify this by
+ // serializing the graph and verifying that it is still the same.
+ // We also verify that at least some of the edges changed because of
+ // tail merging.
+ std::string graph[2];
+ std::string edges[2];
+
+ // The loop executes twice. After the first run, we call MergeTails() on
+ // our graph.
+ for (int i = 0;;) {
+ // Traverse the entire program in depth-first order.
+ std::vector<BasicBlock *> stack;
+ for (BasicBlock *bb = first_block;;) {
+ // Serialize the instructions in this basic block. In general, we only
+ // need to serialize "code" and "k"; except for a BPF_JA instruction
+ // where "k" isn't set.
+ // The stream of instructions should be unchanged after MergeTails().
+ for (Instructions::const_iterator iter = bb->instructions.begin();
+ iter != bb->instructions.end();
+ ++iter) {
+ graph[i].append(reinterpret_cast<char *>(&(*iter)->code),
+ sizeof((*iter)->code));
+ if (BPF_CLASS((*iter)->code) != BPF_JMP ||
+ BPF_OP((*iter)->code) != BPF_JA) {
+ graph[i].append(reinterpret_cast<char *>(&(*iter)->k),
+ sizeof((*iter)->k));
+ }
+ }
+
+ // Also serialize the addresses the basic blocks as we encounter them.
+ // This will change as basic blocks are coalesed by MergeTails().
+ edges[i].append(reinterpret_cast<char *>(&bb), sizeof(bb));
+
+ // Depth-first traversal of the graph. We only ever need to look at the
+ // very last instruction in the basic block, as that is the only one that
+ // can change code flow.
+ Instruction *insn = bb->instructions.back();
+ if (BPF_CLASS(insn->code) == BPF_JMP) {
+ // For jump instructions, we need to remember the "false" branch while
+ // traversing the "true" branch. This is not necessary for BPF_JA which
+ // only has a single branch.
+ if (BPF_OP(insn->code) != BPF_JA) {
+ stack.push_back(all_blocks[insn->jf_ptr]);
+ }
+ bb = all_blocks[insn->jt_ptr];
+ } else if (BPF_CLASS(insn->code) == BPF_RET) {
+ // After a BPF_RET, see if we need to back track.
+ if (stack.empty()) {
+ break;
+ }
+ bb = stack.back();
+ stack.pop_back();
+ } else {
+ // For "normal" instructions, just follow to the next basic block.
+ bb = all_blocks[insn->next];
+ }
+ }
+
+ // Our loop runs exactly two times.
+ if (++i > 1) {
+ break;
+ }
+ codegen->MergeTails(&all_blocks);
+ }
+ SANDBOX_ASSERT(graph[0] == graph[1]);
+ if (flags & HAS_MERGEABLE_TAILS) {
+ SANDBOX_ASSERT(edges[0] != edges[1]);
+ } else {
+ SANDBOX_ASSERT(edges[0] == edges[1]);
+ }
+}
+
+SANDBOX_TEST(CodeGen, MergeTails) {
+ ForAllPrograms(MergeTails);
+}
+
+void CompileAndCompare(CodeGenUnittestHelper *codegen, Instruction *prg, int) {
+ // TopoSortBasicBlocks() has internal checks that cause it to fail, if it
+ // detects a problem. Typically, if anything goes wrong, this looks to the
+ // TopoSort algorithm as if there had been cycles in the input data.
+ // This provides a pretty good unittest.
+ // We hand-crafted the program returned by SampleProgram() to exercise
+ // several of the more interesting code-paths. See comments in
+ // SampleProgram() for details.
+ // In addition to relying on the internal consistency checks in the compiler,
+ // we also serialize the graph and the resulting BPF program and compare
+ // them. With the exception of BPF_JA instructions that might have been
+ // inserted, both instruction streams should be equivalent.
+ // As Compile() modifies the instructions, we have to serialize the graph
+ // before calling Compile().
+ std::string source;
+ Instructions source_stack;
+ for (const Instruction *insn = prg, *next; insn; insn = next) {
+ if (BPF_CLASS(insn->code) == BPF_JMP) {
+ if (BPF_OP(insn->code) == BPF_JA) {
+ // Do not serialize BPF_JA instructions (see above).
+ next = insn->jt_ptr;
+ continue;
+ } else {
+ source_stack.push_back(insn->jf_ptr);
+ next = insn->jt_ptr;
+ }
+ } else if (BPF_CLASS(insn->code) == BPF_RET) {
+ if (source_stack.empty()) {
+ next = NULL;
+ } else {
+ next = source_stack.back();
+ source_stack.pop_back();
+ }
+ } else {
+ next = insn->next;
+ }
+ // Only serialize "code" and "k". That's all the information we need to
+ // compare. The rest of the information is encoded in the order of
+ // instructions.
+ source.append(reinterpret_cast<const char *>(&insn->code),
+ sizeof(insn->code));
+ source.append(reinterpret_cast<const char *>(&insn->k),
+ sizeof(insn->k));
+ }
+
+ // Compile the program
+ SandboxUnittestHelper::Program bpf;
+ codegen->Compile(prg, &bpf);
+
+ // Serialize the resulting BPF instructions.
+ std::string assembly;
+ std::vector<int> assembly_stack;
+ for (int idx = 0; idx >= 0;) {
+ SANDBOX_ASSERT(idx < (int)bpf.size());
+ struct sock_filter& insn = bpf[idx];
+ if (BPF_CLASS(insn.code) == BPF_JMP) {
+ if (BPF_OP(insn.code) == BPF_JA) {
+ // Do not serialize BPF_JA instructions (see above).
+ idx += insn.k + 1;
+ continue;
+ } else {
+ assembly_stack.push_back(idx + insn.jf + 1);
+ idx += insn.jt + 1;
+ }
+ } else if (BPF_CLASS(insn.code) == BPF_RET) {
+ if (assembly_stack.empty()) {
+ idx = -1;
+ } else {
+ idx = assembly_stack.back();
+ assembly_stack.pop_back();
+ }
+ } else {
+ ++idx;
+ }
+ // Serialize the same information that we serialized before compilation.
+ assembly.append(reinterpret_cast<char *>(&insn.code), sizeof(insn.code));
+ assembly.append(reinterpret_cast<char *>(&insn.k), sizeof(insn.k));
+ }
+ SANDBOX_ASSERT(source == assembly);
+}
+
+SANDBOX_TEST(CodeGen, All) {
+ ForAllPrograms(CompileAndCompare);
+}
+
+} // namespace playground2