blob: 96800f1518551b19ac8333592e6ec3573a7ef97e [file] [log] [blame]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2018 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A BPF compiler for the Minijail policy file."""
from __future__ import print_function
import bpf
import parser # pylint: disable=wrong-import-order
class SyscallPolicyEntry:
"""The parsed version of a seccomp policy line."""
def __init__(self, name, number, frequency):
self.name = name
self.number = number
self.frequency = frequency
self.accumulated = 0
self.filter = None
def __repr__(self):
return ('SyscallPolicyEntry<name: %s, number: %d, '
'frequency: %d, filter: %r>') % (self.name, self.number,
self.frequency,
self.filter.instructions
if self.filter else None)
def simulate(self, arch, syscall_number, *args):
"""Simulate the policy with the given arguments."""
if not self.filter:
return (0, 'ALLOW')
return bpf.simulate(self.filter.instructions, arch, syscall_number,
*args)
class PolicyCompiler:
"""A parser for the Minijail seccomp policy file format."""
def __init__(self, arch):
self._arch = arch
def compile_filter_statement(self, filter_statement, *, kill_action):
"""Compile one parser.FilterStatement into BPF."""
policy_entry = SyscallPolicyEntry(filter_statement.syscall.name,
filter_statement.syscall.number,
filter_statement.frequency)
# In each step of the way, the false action is the one that is taken if
# the immediate boolean condition does not match. This means that the
# false action taken here is the one that applies if the whole
# expression fails to match.
false_action = filter_statement.filters[-1].action
if false_action == bpf.Allow():
return policy_entry
# We then traverse the list of filters backwards since we want
# the root of the DAG to be the very first boolean operation in
# the filter chain.
for filt in filter_statement.filters[:-1][::-1]:
for disjunction in filt.expression:
# This is the jump target of the very last comparison in the
# conjunction. Given that any conjunction that succeeds should
# make the whole expression succeed, make the very last
# comparison jump to the accept action if it succeeds.
true_action = filt.action
for atom in disjunction:
block = bpf.Atom(atom.argument_index, atom.op, atom.value,
true_action, false_action)
true_action = block
false_action = true_action
policy_filter = false_action
# Lower all Atoms into WideAtoms.
lowering_visitor = bpf.LoweringVisitor(arch=self._arch)
policy_filter = lowering_visitor.process(policy_filter)
# Flatten the IR DAG into a single BasicBlock.
flattening_visitor = bpf.FlatteningVisitor(
arch=self._arch, kill_action=kill_action)
policy_filter.accept(flattening_visitor)
policy_entry.filter = flattening_visitor.result
return policy_entry