xref: /aosp_15_r20/external/minijail/tools/compile_seccomp_policy.py (revision 4b9c6d91573e8b3a96609339b46361b5476dd0f9)
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"""Helper tool to compile a BPF program from a Minijail seccomp filter.
18
19This script will take a Minijail seccomp policy file and compile it into a
20BPF program suitable for use with Minijail in the current architecture.
21"""
22
23from __future__ import print_function
24
25import argparse
26import os
27import sys
28
29try:
30    import arch
31    import bpf
32    import compiler
33    import parser
34except ImportError:
35    from minijail import arch
36    from minijail import bpf
37    from minijail import compiler
38    from minijail import parser
39
40CONSTANTS_ERR_MSG = """Could not find 'constants.json' file.
41See 'generate_constants_json.py -h'."""
42
43HEADER_TEMPLATE = """/* DO NOT EDIT GENERATED FILE */
44#ifndef MJ_SECCOMP_%(upper_name)s_H
45#define MJ_SECCOMP_%(upper_name)s_H
46#include <stdint.h>
47
48static const unsigned char %(name)s_binary_seccomp_policy[] __attribute__((__aligned__(4))) = {
49    %(program)s
50};
51
52static const struct {
53    uint16_t cnt;
54    const void *bpf;
55} %(name)s_seccomp_bpf_program = {
56    .cnt = sizeof(%(name)s_binary_seccomp_policy) / 8,
57    .bpf = %(name)s_binary_seccomp_policy,
58};
59
60#endif
61"""
62
63def parse_args(argv):
64    """Return the parsed CLI arguments for this tool."""
65    arg_parser = argparse.ArgumentParser(description=__doc__)
66    arg_parser.add_argument('--optimization-strategy',
67                            default=compiler.OptimizationStrategy.BST,
68                            type=compiler.OptimizationStrategy,
69                            choices=list(compiler.OptimizationStrategy))
70    arg_parser.add_argument('--include-depth-limit', default=10)
71    arg_parser.add_argument('--arch-json', default='constants.json')
72    arg_parser.add_argument(
73        '--denylist',
74        action='store_true',
75        help='Compile as a denylist policy rather than the default allowlist.')
76    arg_parser.add_argument(
77        '--default-action',
78        type=str,
79        help=('Use the specified default action, overriding any @default '
80              'action found in the .policy files. '
81              'This allows the use of permissive actions (allow, log, trace, '
82              'user-notify) since it is not valid to specify a permissive '
83              'action in .policy files. This is useful for debugging.'))
84    arg_parser.add_argument(
85        '--use-kill-process',
86        action='store_true',
87        help=('Use SECCOMP_RET_KILL_PROCESS instead of '
88              'SECCOMP_RET_KILL_THREAD (requires Linux v4.14+).'))
89    arg_parser.add_argument(
90        '--use-ret-log',
91        action='store_true',
92        help=('Change all seccomp failures to return SECCOMP_RET_LOG instead '
93              'of killing (requires SECCOMP_RET_LOG kernel support).'))
94    arg_parser.add_argument(
95        '--output-header-file',
96        action='store_true',
97        help=('Output the compiled bpf to a constant variable in a C header '
98              'file instead of a binary file (output should not have a .h '
99              'extension, one will be added).'))
100    arg_parser.add_argument('policy',
101                            help='The seccomp policy.',
102                            type=argparse.FileType('r'))
103    arg_parser.add_argument('output',
104                            help='The BPF program.')
105    return arg_parser.parse_args(argv), arg_parser
106
107
108def main(argv=None):
109    """Main entrypoint."""
110
111    if argv is None:
112        argv = sys.argv[1:]
113
114    opts, arg_parser = parse_args(argv)
115    if not os.path.exists(opts.arch_json):
116        arg_parser.error(CONSTANTS_ERR_MSG)
117
118    parsed_arch = arch.Arch.load_from_json(opts.arch_json)
119    policy_compiler = compiler.PolicyCompiler(parsed_arch)
120    # Set ret_log to true if the MINIJAIL_DEFAULT_RET_LOG environment variable
121    # is present.
122    if 'MINIJAIL_DEFAULT_RET_LOG' in os.environ:
123        print("""
124            \n**********************
125Warning: MINJAIL_DEFAULT_RET_LOG is on, policy will not have any effect
126**********************\n
127""")
128        opts.use_ret_log = True
129    if opts.use_ret_log:
130        kill_action = bpf.Log()
131    elif opts.denylist:
132        # Default action for a denylist policy is return EPERM
133        kill_action = bpf.ReturnErrno(parsed_arch.constants['EPERM'])
134    elif opts.use_kill_process:
135        kill_action = bpf.KillProcess()
136    else:
137        kill_action = bpf.KillThread()
138    override_default_action = None
139    if opts.default_action:
140        parser_state = parser.ParserState('<memory>')
141        override_default_action = parser.PolicyParser(
142            parsed_arch, kill_action=bpf.KillProcess()).parse_action(
143                next(parser_state.tokenize([opts.default_action])))
144
145    compiled_policy = policy_compiler.compile_file(
146        opts.policy.name,
147        optimization_strategy=opts.optimization_strategy,
148        kill_action=kill_action,
149        include_depth_limit=opts.include_depth_limit,
150        override_default_action=override_default_action,
151        denylist=opts.denylist,
152        ret_log=opts.use_ret_log)
153    # Outputs the bpf binary to a c header file instead of a binary file.
154    if opts.output_header_file:
155        output_file_base = opts.output
156        with open(output_file_base + '.h', 'w') as output_file:
157            program = ', '.join('%i' % x for x in compiled_policy.opcodes)
158            output_file.write(HEADER_TEMPLATE % {
159                'upper_name': output_file_base.upper(),
160                'name': output_file_base,
161                'program': program,
162            })
163
164    else:
165        with open(opts.output, 'wb') as outf:
166            outf.write(compiled_policy.opcodes)
167    return 0
168
169
170if __name__ == '__main__':
171    sys.exit(main(sys.argv[1:]))
172