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