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