xref: /aosp_15_r20/external/minijail/tools/generate_seccomp_policy.py (revision 4b9c6d91573e8b3a96609339b46361b5476dd0f9)
1*4b9c6d91SCole Faust#!/usr/bin/env python3
2*4b9c6d91SCole Faust# -*- coding: utf-8 -*-
3*4b9c6d91SCole Faust#
4*4b9c6d91SCole Faust# Copyright (C) 2016 The Android Open Source Project
5*4b9c6d91SCole Faust#
6*4b9c6d91SCole Faust# Licensed under the Apache License, Version 2.0 (the "License");
7*4b9c6d91SCole Faust# you may not use this file except in compliance with the License.
8*4b9c6d91SCole Faust# You may obtain a copy of the License at
9*4b9c6d91SCole Faust#
10*4b9c6d91SCole Faust#      http://www.apache.org/licenses/LICENSE-2.0
11*4b9c6d91SCole Faust#
12*4b9c6d91SCole Faust# Unless required by applicable law or agreed to in writing, software
13*4b9c6d91SCole Faust# distributed under the License is distributed on an "AS IS" BASIS,
14*4b9c6d91SCole Faust# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15*4b9c6d91SCole Faust# See the License for the specific language governing permissions and
16*4b9c6d91SCole Faust# limitations under the License.
17*4b9c6d91SCole Faust#
18*4b9c6d91SCole Faust# This script will take any number of trace files generated by strace(1)
19*4b9c6d91SCole Faust# and output a system call filtering policy suitable for use with Minijail.
20*4b9c6d91SCole Faust
21*4b9c6d91SCole Faust"""Tool to generate a minijail seccomp filter from strace or audit output."""
22*4b9c6d91SCole Faust
23*4b9c6d91SCole Faustfrom __future__ import print_function
24*4b9c6d91SCole Faust
25*4b9c6d91SCole Faustimport argparse
26*4b9c6d91SCole Faustimport collections
27*4b9c6d91SCole Faustimport os
28*4b9c6d91SCole Faustimport re
29*4b9c6d91SCole Faustimport sys
30*4b9c6d91SCole Faust
31*4b9c6d91SCole Faust# auparse may not be installed and is currently optional.
32*4b9c6d91SCole Fausttry:
33*4b9c6d91SCole Faust    import auparse
34*4b9c6d91SCole Faustexcept ImportError:
35*4b9c6d91SCole Faust    auparse = None
36*4b9c6d91SCole Faust
37*4b9c6d91SCole Faust
38*4b9c6d91SCole FaustNOTICE = """# Copyright (C) 2018 The Android Open Source Project
39*4b9c6d91SCole Faust#
40*4b9c6d91SCole Faust# Licensed under the Apache License, Version 2.0 (the "License");
41*4b9c6d91SCole Faust# you may not use this file except in compliance with the License.
42*4b9c6d91SCole Faust# You may obtain a copy of the License at
43*4b9c6d91SCole Faust#
44*4b9c6d91SCole Faust#      http://www.apache.org/licenses/LICENSE-2.0
45*4b9c6d91SCole Faust#
46*4b9c6d91SCole Faust# Unless required by applicable law or agreed to in writing, software
47*4b9c6d91SCole Faust# distributed under the License is distributed on an "AS IS" BASIS,
48*4b9c6d91SCole Faust# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
49*4b9c6d91SCole Faust# See the License for the specific language governing permissions and
50*4b9c6d91SCole Faust# limitations under the License.
51*4b9c6d91SCole Faust"""
52*4b9c6d91SCole Faust
53*4b9c6d91SCole FaustALLOW = '1'
54*4b9c6d91SCole Faust
55*4b9c6d91SCole Faust# This ignores any leading PID tag and trailing <unfinished ...>, and extracts
56*4b9c6d91SCole Faust# the syscall name and the argument list.
57*4b9c6d91SCole FaustLINE_RE = re.compile(r'^\s*(?:\[[^]]*\]|\d+)?\s*([a-zA-Z0-9_]+)\(([^)<]*)')
58*4b9c6d91SCole Faust
59*4b9c6d91SCole FaustSOCKETCALLS = {
60*4b9c6d91SCole Faust    'accept', 'bind', 'connect', 'getpeername', 'getsockname', 'getsockopt',
61*4b9c6d91SCole Faust    'listen', 'recv', 'recvfrom', 'recvmsg', 'send', 'sendmsg', 'sendto',
62*4b9c6d91SCole Faust    'setsockopt', 'shutdown', 'socket', 'socketpair',
63*4b9c6d91SCole Faust}
64*4b9c6d91SCole Faust
65*4b9c6d91SCole Faust# List of private ARM syscalls. These can be found in any ARM specific unistd.h
66*4b9c6d91SCole Faust# such as Linux's arch/arm/include/uapi/asm/unistd.h.
67*4b9c6d91SCole FaustPRIVATE_ARM_SYSCALLS = {
68*4b9c6d91SCole Faust    983041: 'ARM_breakpoint',
69*4b9c6d91SCole Faust    983042: 'ARM_cacheflush',
70*4b9c6d91SCole Faust    983043: 'ARM_usr26',
71*4b9c6d91SCole Faust    983044: 'ARM_usr32',
72*4b9c6d91SCole Faust    983045: 'ARM_set_tls',
73*4b9c6d91SCole Faust}
74*4b9c6d91SCole Faust
75*4b9c6d91SCole FaustArgInspectionEntry = collections.namedtuple('ArgInspectionEntry',
76*4b9c6d91SCole Faust                                            ('arg_index', 'value_set'))
77*4b9c6d91SCole Faust
78*4b9c6d91SCole Faust
79*4b9c6d91SCole Faust# pylint: disable=too-few-public-methods
80*4b9c6d91SCole Faustclass BucketInputFiles(argparse.Action):
81*4b9c6d91SCole Faust    """Buckets input files using simple content based heuristics.
82*4b9c6d91SCole Faust
83*4b9c6d91SCole Faust    Attributes:
84*4b9c6d91SCole Faust      audit_logs: Mutually exclusive list of audit log filenames.
85*4b9c6d91SCole Faust      traces: Mutually exclusive list of strace log filenames.
86*4b9c6d91SCole Faust    """
87*4b9c6d91SCole Faust    def __call__(self, parser, namespace, values, option_string=None):
88*4b9c6d91SCole Faust        audit_logs = []
89*4b9c6d91SCole Faust        traces = []
90*4b9c6d91SCole Faust
91*4b9c6d91SCole Faust        strace_line_re = re.compile(r'[a-z]+[0-9]*\(.+\) += ')
92*4b9c6d91SCole Faust        audit_line_re = re.compile(r'type=(SYSCALL|SECCOMP)')
93*4b9c6d91SCole Faust
94*4b9c6d91SCole Faust        for filename in values:
95*4b9c6d91SCole Faust            if not os.path.exists(filename):
96*4b9c6d91SCole Faust                parser.error(f'Input file {filename} not found.')
97*4b9c6d91SCole Faust            with open(filename, mode='r', encoding='utf8') as input_file:
98*4b9c6d91SCole Faust                for line in input_file.readlines():
99*4b9c6d91SCole Faust                    if strace_line_re.search(line):
100*4b9c6d91SCole Faust                        traces.append(filename)
101*4b9c6d91SCole Faust                        break
102*4b9c6d91SCole Faust                    if audit_line_re.search(line):
103*4b9c6d91SCole Faust                        audit_logs.append(filename)
104*4b9c6d91SCole Faust                        break
105*4b9c6d91SCole Faust                else:
106*4b9c6d91SCole Faust                    # Treat it as an strace log to retain legacy behaviour and
107*4b9c6d91SCole Faust                    # also just in case the strace regex is imperfect.
108*4b9c6d91SCole Faust                    traces.append(filename)
109*4b9c6d91SCole Faust
110*4b9c6d91SCole Faust        setattr(namespace, 'audit_logs', audit_logs)
111*4b9c6d91SCole Faust        setattr(namespace, 'traces', traces)
112*4b9c6d91SCole Faust# pylint: enable=too-few-public-methods
113*4b9c6d91SCole Faust
114*4b9c6d91SCole Faust
115*4b9c6d91SCole Faustdef parse_args(argv):
116*4b9c6d91SCole Faust    """Returns the parsed CLI arguments for this tool."""
117*4b9c6d91SCole Faust    parser = argparse.ArgumentParser(description=__doc__)
118*4b9c6d91SCole Faust    parser.add_argument('--verbose', action='store_true',
119*4b9c6d91SCole Faust                        help='output informational messages to stderr')
120*4b9c6d91SCole Faust    parser.add_argument('--frequency', type=argparse.FileType('w'),
121*4b9c6d91SCole Faust                        help='frequency file')
122*4b9c6d91SCole Faust    parser.add_argument('--policy', type=argparse.FileType('w'),
123*4b9c6d91SCole Faust                        default=sys.stdout, help='policy file')
124*4b9c6d91SCole Faust    parser.add_argument('input-logs', action=BucketInputFiles,
125*4b9c6d91SCole Faust                        help='strace and/or audit logs', nargs='+')
126*4b9c6d91SCole Faust    parser.add_argument('--audit-comm', type=str, metavar='PROCESS_NAME',
127*4b9c6d91SCole Faust                        help='relevant process name from the audit.log files')
128*4b9c6d91SCole Faust    opts = parser.parse_args(argv)
129*4b9c6d91SCole Faust
130*4b9c6d91SCole Faust    if opts.audit_logs and not auparse:
131*4b9c6d91SCole Faust        parser.error('Python bindings for the audit subsystem were not found.\n'
132*4b9c6d91SCole Faust                     'Please install the python3-audit (sometimes python-audit)'
133*4b9c6d91SCole Faust                     ' package for your distro to process audit logs: '
134*4b9c6d91SCole Faust                     f'{opts.audit_logs}')
135*4b9c6d91SCole Faust
136*4b9c6d91SCole Faust    if opts.audit_logs and not opts.audit_comm:
137*4b9c6d91SCole Faust        parser.error(f'--audit-comm is required when using audit logs as input:'
138*4b9c6d91SCole Faust                     f' {opts.audit_logs}')
139*4b9c6d91SCole Faust
140*4b9c6d91SCole Faust    if not opts.audit_logs and opts.audit_comm:
141*4b9c6d91SCole Faust        parser.error('--audit-comm was specified yet none of the input files '
142*4b9c6d91SCole Faust                     'matched our hueristic for an audit log')
143*4b9c6d91SCole Faust
144*4b9c6d91SCole Faust    return opts
145*4b9c6d91SCole Faust
146*4b9c6d91SCole Faust
147*4b9c6d91SCole Faustdef get_seccomp_bpf_filter(syscall, entry):
148*4b9c6d91SCole Faust    """Returns a minijail seccomp-bpf filter expression for the syscall."""
149*4b9c6d91SCole Faust    arg_index = entry.arg_index
150*4b9c6d91SCole Faust    arg_values = entry.value_set
151*4b9c6d91SCole Faust    atoms = []
152*4b9c6d91SCole Faust    if syscall in ('mmap', 'mmap2', 'mprotect') and arg_index == 2:
153*4b9c6d91SCole Faust        # See if there is at least one instance of any of these syscalls trying
154*4b9c6d91SCole Faust        # to map memory with both PROT_EXEC and PROT_WRITE. If there isn't, we
155*4b9c6d91SCole Faust        # can craft a concise expression to forbid this.
156*4b9c6d91SCole Faust        write_and_exec = set(('PROT_EXEC', 'PROT_WRITE'))
157*4b9c6d91SCole Faust        for arg_value in arg_values:
158*4b9c6d91SCole Faust            if write_and_exec.issubset(set(p.strip() for p in
159*4b9c6d91SCole Faust                                           arg_value.split('|'))):
160*4b9c6d91SCole Faust                break
161*4b9c6d91SCole Faust        else:
162*4b9c6d91SCole Faust            atoms.extend(['arg2 in ~PROT_EXEC', 'arg2 in ~PROT_WRITE'])
163*4b9c6d91SCole Faust            arg_values = set()
164*4b9c6d91SCole Faust    atoms.extend(f'arg{arg_index} == {arg_value}' for arg_value in arg_values)
165*4b9c6d91SCole Faust    return ' || '.join(atoms)
166*4b9c6d91SCole Faust
167*4b9c6d91SCole Faust
168*4b9c6d91SCole Faustdef parse_trace_file(trace_filename, syscalls, arg_inspection):
169*4b9c6d91SCole Faust    """Parses one file produced by strace."""
170*4b9c6d91SCole Faust    uses_socketcall = ('i386' in trace_filename or
171*4b9c6d91SCole Faust                       ('x86' in trace_filename and
172*4b9c6d91SCole Faust                        '64' not in trace_filename))
173*4b9c6d91SCole Faust
174*4b9c6d91SCole Faust    with open(trace_filename, encoding='utf8') as trace_file:
175*4b9c6d91SCole Faust        for line in trace_file:
176*4b9c6d91SCole Faust            matches = LINE_RE.match(line)
177*4b9c6d91SCole Faust            if not matches:
178*4b9c6d91SCole Faust                continue
179*4b9c6d91SCole Faust
180*4b9c6d91SCole Faust            syscall, args = matches.groups()
181*4b9c6d91SCole Faust            if uses_socketcall and syscall in SOCKETCALLS:
182*4b9c6d91SCole Faust                syscall = 'socketcall'
183*4b9c6d91SCole Faust
184*4b9c6d91SCole Faust            # strace omits the 'ARM_' prefix on all private ARM syscalls. Add
185*4b9c6d91SCole Faust            # it manually here as a workaround. These syscalls are exclusive
186*4b9c6d91SCole Faust            # to ARM so we don't need to predicate this on a trace_filename
187*4b9c6d91SCole Faust            # based heuristic for the arch.
188*4b9c6d91SCole Faust            if f'ARM_{syscall}' in PRIVATE_ARM_SYSCALLS.values():
189*4b9c6d91SCole Faust                syscall = f'ARM_{syscall}'
190*4b9c6d91SCole Faust
191*4b9c6d91SCole Faust            syscalls[syscall] += 1
192*4b9c6d91SCole Faust
193*4b9c6d91SCole Faust            args = [arg.strip() for arg in args.split(',')]
194*4b9c6d91SCole Faust
195*4b9c6d91SCole Faust            if syscall in arg_inspection:
196*4b9c6d91SCole Faust                arg_value = args[arg_inspection[syscall].arg_index]
197*4b9c6d91SCole Faust                arg_inspection[syscall].value_set.add(arg_value)
198*4b9c6d91SCole Faust
199*4b9c6d91SCole Faust
200*4b9c6d91SCole Faustdef parse_audit_log(audit_log, audit_comm, syscalls, arg_inspection):
201*4b9c6d91SCole Faust    """Parses one audit.log file generated by the Linux audit subsystem."""
202*4b9c6d91SCole Faust
203*4b9c6d91SCole Faust    unknown_syscall_re = re.compile(r'unknown-syscall\((?P<syscall_num>\d+)\)')
204*4b9c6d91SCole Faust
205*4b9c6d91SCole Faust    au = auparse.AuParser(auparse.AUSOURCE_FILE, audit_log)
206*4b9c6d91SCole Faust    # Quick validity check for whether this parses as a valid audit log. The
207*4b9c6d91SCole Faust    # first event should have at least one record.
208*4b9c6d91SCole Faust    if not au.first_record():
209*4b9c6d91SCole Faust        raise ValueError(f'Unable to parse audit log file {audit_log.name}')
210*4b9c6d91SCole Faust
211*4b9c6d91SCole Faust    # Iterate through events where _any_ contained record matches
212*4b9c6d91SCole Faust    # ((type == SECCOMP || type == SYSCALL) && comm == audit_comm).
213*4b9c6d91SCole Faust    au.search_add_item('type', '=', 'SECCOMP', auparse.AUSEARCH_RULE_CLEAR)
214*4b9c6d91SCole Faust    au.search_add_item('type', '=', 'SYSCALL', auparse.AUSEARCH_RULE_OR)
215*4b9c6d91SCole Faust    au.search_add_item('comm', '=', f'"{audit_comm}"',
216*4b9c6d91SCole Faust                       auparse.AUSEARCH_RULE_AND)
217*4b9c6d91SCole Faust
218*4b9c6d91SCole Faust    # auparse_find_field(3) will ignore preceding fields in the record and
219*4b9c6d91SCole Faust    # at the same time happily cross record boundaries when looking for the
220*4b9c6d91SCole Faust    # field. This helper method always seeks the cursor back to the first
221*4b9c6d91SCole Faust    # field in the record and stops searching before crossing over to the
222*4b9c6d91SCole Faust    # next record; making the search far less error prone.
223*4b9c6d91SCole Faust    # Also implicitly seeks the internal 'cursor' to the matching field
224*4b9c6d91SCole Faust    # for any subsequent calls like auparse_interpret_field.
225*4b9c6d91SCole Faust    def _find_field_in_current_record(name):
226*4b9c6d91SCole Faust        au.first_field()
227*4b9c6d91SCole Faust        while True:
228*4b9c6d91SCole Faust            if au.get_field_name() == name:
229*4b9c6d91SCole Faust                return au.get_field_str()
230*4b9c6d91SCole Faust            if not au.next_field():
231*4b9c6d91SCole Faust                return None
232*4b9c6d91SCole Faust
233*4b9c6d91SCole Faust    while au.search_next_event():
234*4b9c6d91SCole Faust        # The event may have multiple records. Loop through all.
235*4b9c6d91SCole Faust        au.first_record()
236*4b9c6d91SCole Faust        for _ in range(au.get_num_records()):
237*4b9c6d91SCole Faust            event_type = _find_field_in_current_record('type')
238*4b9c6d91SCole Faust            comm = _find_field_in_current_record('comm')
239*4b9c6d91SCole Faust            # Some of the records in this event may not be relevant
240*4b9c6d91SCole Faust            # despite the event-specific search filter. Skip those.
241*4b9c6d91SCole Faust            if (event_type not in ('SECCOMP', 'SYSCALL') or
242*4b9c6d91SCole Faust                    comm != f'"{audit_comm}"'):
243*4b9c6d91SCole Faust                au.next_record()
244*4b9c6d91SCole Faust                continue
245*4b9c6d91SCole Faust
246*4b9c6d91SCole Faust            if not _find_field_in_current_record('syscall'):
247*4b9c6d91SCole Faust                raise ValueError(f'Could not find field "syscall" in event of '
248*4b9c6d91SCole Faust                                 f'type {event_type}')
249*4b9c6d91SCole Faust            # Intepret the syscall field that's under our 'cursor' following the
250*4b9c6d91SCole Faust            # find. Interpreting fields yields human friendly names instead
251*4b9c6d91SCole Faust            # of integers. E.g '16' -> 'ioctl'.
252*4b9c6d91SCole Faust            syscall = au.interpret_field()
253*4b9c6d91SCole Faust
254*4b9c6d91SCole Faust            # TODO(crbug/1172449): Add these syscalls to upstream
255*4b9c6d91SCole Faust            # audit-userspace and remove this workaround.
256*4b9c6d91SCole Faust            # This is redundant but safe for non-ARM architectures due to the
257*4b9c6d91SCole Faust            # disjoint set of private syscall numbers.
258*4b9c6d91SCole Faust            match = unknown_syscall_re.match(syscall)
259*4b9c6d91SCole Faust            if match:
260*4b9c6d91SCole Faust                syscall_num = int(match.group('syscall_num'))
261*4b9c6d91SCole Faust                syscall = PRIVATE_ARM_SYSCALLS.get(syscall_num, syscall)
262*4b9c6d91SCole Faust
263*4b9c6d91SCole Faust            if ((syscall in arg_inspection and event_type == 'SECCOMP') or
264*4b9c6d91SCole Faust                (syscall not in arg_inspection and event_type == 'SYSCALL')):
265*4b9c6d91SCole Faust                # Skip SECCOMP records for syscalls that require argument
266*4b9c6d91SCole Faust                # inspection. Similarly, skip SYSCALL records for syscalls
267*4b9c6d91SCole Faust                # that do not require argument inspection. Technically such
268*4b9c6d91SCole Faust                # records wouldn't exist per our setup instructions but audit
269*4b9c6d91SCole Faust                # sometimes lets a few records slip through.
270*4b9c6d91SCole Faust                au.next_record()
271*4b9c6d91SCole Faust                continue
272*4b9c6d91SCole Faust            elif event_type == 'SYSCALL':
273*4b9c6d91SCole Faust                arg_field_name = f'a{arg_inspection[syscall].arg_index}'
274*4b9c6d91SCole Faust                if not _find_field_in_current_record(arg_field_name):
275*4b9c6d91SCole Faust                    raise ValueError(f'Could not find field "{arg_field_name}"'
276*4b9c6d91SCole Faust                                     f'in event of type {event_type}')
277*4b9c6d91SCole Faust                # Intepret the arg field that's under our 'cursor' following the
278*4b9c6d91SCole Faust                # find. This may yield a more human friendly name.
279*4b9c6d91SCole Faust                # E.g '5401' -> 'TCGETS'.
280*4b9c6d91SCole Faust                arg_inspection[syscall].value_set.add(au.interpret_field())
281*4b9c6d91SCole Faust
282*4b9c6d91SCole Faust            syscalls[syscall] += 1
283*4b9c6d91SCole Faust            au.next_record()
284*4b9c6d91SCole Faust
285*4b9c6d91SCole Faust
286*4b9c6d91SCole Faustdef main(argv=None):
287*4b9c6d91SCole Faust    """Main entrypoint."""
288*4b9c6d91SCole Faust
289*4b9c6d91SCole Faust    if argv is None:
290*4b9c6d91SCole Faust        argv = sys.argv[1:]
291*4b9c6d91SCole Faust
292*4b9c6d91SCole Faust    opts = parse_args(argv)
293*4b9c6d91SCole Faust
294*4b9c6d91SCole Faust    syscalls = collections.defaultdict(int)
295*4b9c6d91SCole Faust
296*4b9c6d91SCole Faust    arg_inspection = {
297*4b9c6d91SCole Faust        'socket': ArgInspectionEntry(0, set([])),   # int domain
298*4b9c6d91SCole Faust        'ioctl': ArgInspectionEntry(1, set([])),    # int request
299*4b9c6d91SCole Faust        'prctl': ArgInspectionEntry(0, set([])),    # int option
300*4b9c6d91SCole Faust        'mmap': ArgInspectionEntry(2, set([])),     # int prot
301*4b9c6d91SCole Faust        'mmap2': ArgInspectionEntry(2, set([])),    # int prot
302*4b9c6d91SCole Faust        'mprotect': ArgInspectionEntry(2, set([])), # int prot
303*4b9c6d91SCole Faust    }
304*4b9c6d91SCole Faust
305*4b9c6d91SCole Faust    if opts.verbose:
306*4b9c6d91SCole Faust        # Print an informational message to stderr in case the filetype detection
307*4b9c6d91SCole Faust        # heuristics are wonky.
308*4b9c6d91SCole Faust        print('Generating a seccomp policy using these input files:',
309*4b9c6d91SCole Faust              file=sys.stderr)
310*4b9c6d91SCole Faust        print(f'Strace logs: {opts.traces}', file=sys.stderr)
311*4b9c6d91SCole Faust        print(f'Audit logs: {opts.audit_logs}', file=sys.stderr)
312*4b9c6d91SCole Faust
313*4b9c6d91SCole Faust    for trace_filename in opts.traces:
314*4b9c6d91SCole Faust        parse_trace_file(trace_filename, syscalls, arg_inspection)
315*4b9c6d91SCole Faust
316*4b9c6d91SCole Faust    for audit_log in opts.audit_logs:
317*4b9c6d91SCole Faust        parse_audit_log(audit_log, opts.audit_comm, syscalls, arg_inspection)
318*4b9c6d91SCole Faust
319*4b9c6d91SCole Faust    # Add the basic set if they are not yet present.
320*4b9c6d91SCole Faust    basic_set = [
321*4b9c6d91SCole Faust        'restart_syscall', 'exit', 'exit_group', 'rt_sigreturn',
322*4b9c6d91SCole Faust    ]
323*4b9c6d91SCole Faust    for basic_syscall in basic_set:
324*4b9c6d91SCole Faust        if basic_syscall not in syscalls:
325*4b9c6d91SCole Faust            syscalls[basic_syscall] = 1
326*4b9c6d91SCole Faust
327*4b9c6d91SCole Faust    # If a frequency file isn't used then sort the syscalls based on frequency
328*4b9c6d91SCole Faust    # to make the common case fast (by checking frequent calls earlier).
329*4b9c6d91SCole Faust    # Otherwise, sort alphabetically to make it easier for humans to see which
330*4b9c6d91SCole Faust    # calls are in use (and if necessary manually add a new syscall to the
331*4b9c6d91SCole Faust    # list).
332*4b9c6d91SCole Faust    if opts.frequency is None:
333*4b9c6d91SCole Faust        sorted_syscalls = list(
334*4b9c6d91SCole Faust            x[0] for x in sorted(syscalls.items(), key=lambda pair: pair[1],
335*4b9c6d91SCole Faust                                 reverse=True)
336*4b9c6d91SCole Faust        )
337*4b9c6d91SCole Faust    else:
338*4b9c6d91SCole Faust        sorted_syscalls = list(
339*4b9c6d91SCole Faust            x[0] for x in sorted(syscalls.items(), key=lambda pair: pair[0])
340*4b9c6d91SCole Faust        )
341*4b9c6d91SCole Faust
342*4b9c6d91SCole Faust    print(NOTICE, file=opts.policy)
343*4b9c6d91SCole Faust    if opts.frequency is not None:
344*4b9c6d91SCole Faust        print(NOTICE, file=opts.frequency)
345*4b9c6d91SCole Faust
346*4b9c6d91SCole Faust    for syscall in sorted_syscalls:
347*4b9c6d91SCole Faust        if syscall in arg_inspection:
348*4b9c6d91SCole Faust            arg_filter = get_seccomp_bpf_filter(syscall,
349*4b9c6d91SCole Faust                                                arg_inspection[syscall])
350*4b9c6d91SCole Faust        else:
351*4b9c6d91SCole Faust            arg_filter = ALLOW
352*4b9c6d91SCole Faust        print(f'{syscall}: {arg_filter}', file=opts.policy)
353*4b9c6d91SCole Faust        if opts.frequency is not None:
354*4b9c6d91SCole Faust            print(f'{syscall}: {syscalls[syscall]}', file=opts.frequency)
355*4b9c6d91SCole Faust
356*4b9c6d91SCole Faust
357*4b9c6d91SCole Faustif __name__ == '__main__':
358*4b9c6d91SCole Faust    sys.exit(main(sys.argv[1:]))
359