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