blob: 96800f1518551b19ac8333592e6ec3573a7ef97e [file] [log] [blame]
Luis Hector Chavez05392b82018-10-28 21:40:10 -07001#!/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
19from __future__ import print_function
20
21import bpf
22import parser # pylint: disable=wrong-import-order
23
24
25class 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
50class 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