tools/compile_seccomp_policy: Allow permissive default actions
am: 891d355207

Change-Id: Idd46a48d7912829888ea0f096aaeef607f84dd54
diff --git a/tools/compile_seccomp_policy.py b/tools/compile_seccomp_policy.py
index 62f8c1f..4278f90 100755
--- a/tools/compile_seccomp_policy.py
+++ b/tools/compile_seccomp_policy.py
@@ -28,6 +28,7 @@
 import arch
 import bpf
 import compiler
+import parser
 
 
 def parse_args(argv):
@@ -41,6 +42,14 @@
     parser.add_argument('--include-depth-limit', default=10)
     parser.add_argument('--arch-json', default='constants.json')
     parser.add_argument(
+        '--default-action',
+        type=str,
+        help=('Use the specified default action, overriding any @default '
+              'action found in the .policy files. '
+              'This allows the use of permissive actions (allow, log, trace) '
+              'since it is not valid to specify a permissive action in '
+              '.policy files. This is useful for debugging.'))
+    parser.add_argument(
         '--use-kill-process',
         action='store_true',
         help=('Use SECCOMP_RET_KILL_PROCESS instead of '
@@ -55,19 +64,27 @@
 def main(argv):
     """Main entrypoint."""
     opts = parse_args(argv)
-    policy_compiler = compiler.PolicyCompiler(
-        arch.Arch.load_from_json(opts.arch_json))
+    parsed_arch = arch.Arch.load_from_json(opts.arch_json)
+    policy_compiler = compiler.PolicyCompiler(parsed_arch)
     if opts.use_kill_process:
         kill_action = bpf.KillProcess()
     else:
         kill_action = bpf.KillThread()
+    override_default_action = None
+    if opts.default_action:
+        parser_state = parser.ParserState('<memory>')
+        parser_state.set_line(opts.default_action)
+        override_default_action = parser.PolicyParser(
+            parsed_arch, kill_action=bpf.KillProcess()).parse_action(
+                parser_state.tokenize())
     with opts.output as outf:
         outf.write(
             policy_compiler.compile_file(
                 opts.policy.name,
                 optimization_strategy=opts.optimization_strategy,
                 kill_action=kill_action,
-                include_depth_limit=opts.include_depth_limit).opcodes)
+                include_depth_limit=opts.include_depth_limit,
+                override_default_action=override_default_action).opcodes)
     return 0
 
 
diff --git a/tools/compiler.py b/tools/compiler.py
index 9895df8..4e3881a 100644
--- a/tools/compiler.py
+++ b/tools/compiler.py
@@ -51,10 +51,9 @@
 
     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)
+                '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."""
@@ -75,8 +74,8 @@
 
     def __repr__(self):
         return 'SyscallPolicyRange<numbers: %r, frequency: %d, filter: %r>' % (
-            self.numbers, self.frequency, self.filter.instructions
-            if self.filter else None)
+            self.numbers, self.frequency,
+            self.filter.instructions if self.filter else None)
 
     def simulate(self, arch, syscall_number, *args):
         """Simulate the policy with the given arguments."""
@@ -221,7 +220,8 @@
 
         # Now recursively go through all possible partitions of the interval
         # currently being considered.
-        previous_accumulated = ranges[indices[0]].accumulated - ranges[indices[0]].frequency
+        previous_accumulated = ranges[indices[0]].accumulated - ranges[
+            indices[0]].frequency
         bst_comparison_cost = (
             ranges[indices[1] - 1].accumulated - previous_accumulated)
         for i, entry in enumerate(ranges[slice(*indices)]):
@@ -265,12 +265,14 @@
                      *,
                      optimization_strategy,
                      kill_action,
-                     include_depth_limit=10):
+                     include_depth_limit=10,
+                     override_default_action=None):
         """Return a compiled BPF program from the provided policy file."""
         policy_parser = parser.PolicyParser(
             self._arch,
             kill_action=kill_action,
-            include_depth_limit=include_depth_limit)
+            include_depth_limit=include_depth_limit,
+            override_default_action=override_default_action)
         parsed_policy = policy_parser.parse_file(policy_filename)
         entries = [
             self.compile_filter_statement(
diff --git a/tools/parser.py b/tools/parser.py
index 7179717..f3c5331 100644
--- a/tools/parser.py
+++ b/tools/parser.py
@@ -207,11 +207,17 @@
 class PolicyParser:
     """A parser for the Minijail seccomp policy file format."""
 
-    def __init__(self, arch, *, kill_action, include_depth_limit=10):
+    def __init__(self,
+                 arch,
+                 *,
+                 kill_action,
+                 include_depth_limit=10,
+                 override_default_action=None):
         self._parser_states = [ParserState("<memory>")]
         self._kill_action = kill_action
         self._include_depth_limit = include_depth_limit
         self._default_action = self._kill_action
+        self._override_default_action = override_default_action
         self._frequency_mapping = collections.defaultdict(int)
         self._arch = arch
 
@@ -385,7 +391,7 @@
     #        | 'log'
     #        | 'return' , single-constant
     #        ;
-    def _parse_action(self, tokens):
+    def parse_action(self, tokens):
         if not tokens:
             self._parser_state.error('missing action')
         action_token = tokens.pop(0)
@@ -425,12 +431,12 @@
             argument_expression = self.parse_argument_expression(tokens)
             if tokens and tokens[0].type == 'SEMICOLON':
                 tokens.pop(0)
-                action = self._parse_action(tokens)
+                action = self.parse_action(tokens)
             else:
                 action = bpf.Allow()
             return Filter(argument_expression, action)
         else:
-            return Filter(None, self._parse_action(tokens))
+            return Filter(None, self.parse_action(tokens))
 
     # filter = '{' , single-filter , [ { ',' , single-filter } ] , '}'
     #        | single-filter
@@ -722,6 +728,7 @@
                             []))
                     syscall_filter_mapping[syscall] = filter_statements[-1]
                 syscall_filter_mapping[syscall].filters.extend(filters)
+        default_action = self._override_default_action or self._default_action
         for filter_statement in filter_statements:
             unconditional_actions_suffix = list(
                 itertools.dropwhile(lambda filt: filt.expression is not None,
@@ -740,5 +747,5 @@
                     line=self._parser_states[-1].line)
             assert not unconditional_actions_suffix
             filter_statement.filters.append(
-                Filter(expression=None, action=self._default_action))
-        return ParsedPolicy(self._default_action, filter_statements)
+                Filter(expression=None, action=default_action))
+        return ParsedPolicy(default_action, filter_statements)