Luis Hector Chavez | 05392b8 | 2018-10-28 21:40:10 -0700 | [diff] [blame^] | 1 | #!/usr/bin/env python3 |
| 2 | # -*- coding: utf-8 -*- |
| 3 | # |
| 4 | # Copyright (C) 2018 The Android Open Source Project |
| 5 | # |
| 6 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 7 | # you may not use this file except in compliance with the License. |
| 8 | # You may obtain a copy of the License at |
| 9 | # |
| 10 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | # |
| 12 | # Unless required by applicable law or agreed to in writing, software |
| 13 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 15 | # See the License for the specific language governing permissions and |
| 16 | # limitations under the License. |
| 17 | """A BPF compiler for the Minijail policy file.""" |
| 18 | |
| 19 | from __future__ import print_function |
| 20 | |
| 21 | import bpf |
| 22 | import parser # pylint: disable=wrong-import-order |
| 23 | |
| 24 | |
| 25 | class SyscallPolicyEntry: |
| 26 | """The parsed version of a seccomp policy line.""" |
| 27 | |
| 28 | def __init__(self, name, number, frequency): |
| 29 | self.name = name |
| 30 | self.number = number |
| 31 | self.frequency = frequency |
| 32 | self.accumulated = 0 |
| 33 | self.filter = None |
| 34 | |
| 35 | def __repr__(self): |
| 36 | return ('SyscallPolicyEntry<name: %s, number: %d, ' |
| 37 | 'frequency: %d, filter: %r>') % (self.name, self.number, |
| 38 | self.frequency, |
| 39 | self.filter.instructions |
| 40 | if self.filter else None) |
| 41 | |
| 42 | def simulate(self, arch, syscall_number, *args): |
| 43 | """Simulate the policy with the given arguments.""" |
| 44 | if not self.filter: |
| 45 | return (0, 'ALLOW') |
| 46 | return bpf.simulate(self.filter.instructions, arch, syscall_number, |
| 47 | *args) |
| 48 | |
| 49 | |
| 50 | class PolicyCompiler: |
| 51 | """A parser for the Minijail seccomp policy file format.""" |
| 52 | |
| 53 | def __init__(self, arch): |
| 54 | self._arch = arch |
| 55 | |
| 56 | def compile_filter_statement(self, filter_statement, *, kill_action): |
| 57 | """Compile one parser.FilterStatement into BPF.""" |
| 58 | policy_entry = SyscallPolicyEntry(filter_statement.syscall.name, |
| 59 | filter_statement.syscall.number, |
| 60 | filter_statement.frequency) |
| 61 | # In each step of the way, the false action is the one that is taken if |
| 62 | # the immediate boolean condition does not match. This means that the |
| 63 | # false action taken here is the one that applies if the whole |
| 64 | # expression fails to match. |
| 65 | false_action = filter_statement.filters[-1].action |
| 66 | if false_action == bpf.Allow(): |
| 67 | return policy_entry |
| 68 | # We then traverse the list of filters backwards since we want |
| 69 | # the root of the DAG to be the very first boolean operation in |
| 70 | # the filter chain. |
| 71 | for filt in filter_statement.filters[:-1][::-1]: |
| 72 | for disjunction in filt.expression: |
| 73 | # This is the jump target of the very last comparison in the |
| 74 | # conjunction. Given that any conjunction that succeeds should |
| 75 | # make the whole expression succeed, make the very last |
| 76 | # comparison jump to the accept action if it succeeds. |
| 77 | true_action = filt.action |
| 78 | for atom in disjunction: |
| 79 | block = bpf.Atom(atom.argument_index, atom.op, atom.value, |
| 80 | true_action, false_action) |
| 81 | true_action = block |
| 82 | false_action = true_action |
| 83 | policy_filter = false_action |
| 84 | |
| 85 | # Lower all Atoms into WideAtoms. |
| 86 | lowering_visitor = bpf.LoweringVisitor(arch=self._arch) |
| 87 | policy_filter = lowering_visitor.process(policy_filter) |
| 88 | |
| 89 | # Flatten the IR DAG into a single BasicBlock. |
| 90 | flattening_visitor = bpf.FlatteningVisitor( |
| 91 | arch=self._arch, kill_action=kill_action) |
| 92 | policy_filter.accept(flattening_visitor) |
| 93 | policy_entry.filter = flattening_visitor.result |
| 94 | return policy_entry |