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