def test_parse_simple_with_arg(self): """Allow simple denylist policy files.""" path = self._write_file( 'test.policy', """ # Comment. @denylist read: return ENOSYS write: arg0 == 0 ; return ENOSYS """) self.assertEqual( self.parser.parse_file(path), parser.ParsedPolicy( default_action=bpf.Allow(), filter_statements=[ parser.FilterStatement( syscall=parser.Syscall('read', 0), frequency=1, filters=[ parser.Filter( None, bpf.ReturnErrno( self.arch.constants['ENOSYS'])), ]), parser.FilterStatement( syscall=parser.Syscall('write', 1), frequency=1, filters=[ parser.Filter([[parser.Atom(0, '==', 0)]], bpf.ReturnErrno( self.arch.constants['ENOSYS'])), parser.Filter(None, bpf.Allow()), ]), ]))
def test_parse_filter(self): """Accept valid filters.""" self.assertEqual( self.parser.parse_filter(self._tokenize('arg0 == 0')), [ parser.Filter([[parser.Atom(0, '==', 0)]], bpf.Allow()), ]) self.assertEqual( self.parser.parse_filter(self._tokenize('kill-process')), [ parser.Filter(None, bpf.KillProcess()), ]) self.assertEqual( self.parser.parse_filter(self._tokenize('kill-thread')), [ parser.Filter(None, bpf.KillThread()), ]) self.assertEqual( self.parser.parse_filter(self._tokenize('trap')), [ parser.Filter(None, bpf.Trap()), ]) self.assertEqual( self.parser.parse_filter(self._tokenize('return ENOSYS')), [ parser.Filter(None, bpf.ReturnErrno(self.arch.constants['ENOSYS'])), ]) self.assertEqual( self.parser.parse_filter(self._tokenize('trace')), [ parser.Filter(None, bpf.Trace()), ]) self.assertEqual( self.parser.parse_filter(self._tokenize('user-notify')), [ parser.Filter(None, bpf.UserNotify()), ]) self.assertEqual( self.parser.parse_filter(self._tokenize('log')), [ parser.Filter(None, bpf.Log()), ]) self.assertEqual( self.parser.parse_filter(self._tokenize('allow')), [ parser.Filter(None, bpf.Allow()), ]) self.assertEqual( self.parser.parse_filter(self._tokenize('1')), [ parser.Filter(None, bpf.Allow()), ]) self.assertEqual( self.parser.parse_filter( self._tokenize( '{ arg0 == 0, arg0 == 1; return ENOSYS, trap }')), [ parser.Filter([[parser.Atom(0, '==', 0)]], bpf.Allow()), parser.Filter([[parser.Atom(0, '==', 1)]], bpf.ReturnErrno(self.arch.constants['ENOSYS'])), parser.Filter(None, bpf.Trap()), ])
def parse_action(self, tokens): if not tokens: self._parser_state.error('missing action') action_token = tokens.pop(0) if action_token.type == 'ACTION': if action_token.value == 'allow': return bpf.Allow() if action_token.value == 'kill': return self._kill_action if action_token.value == 'kill-process': return bpf.KillProcess() if action_token.value == 'kill-thread': return bpf.KillThread() if action_token.value == 'trap': return bpf.Trap() if action_token.value == 'trace': return bpf.Trace() if action_token.value == 'log': return bpf.Log() elif action_token.type == 'NUMERIC_CONSTANT': constant = self._parse_single_constant(action_token) if constant == 1: return bpf.Allow() elif action_token.type == 'RETURN': if not tokens: self._parser_state.error('missing return value') return bpf.ReturnErrno(self._parse_single_constant(tokens.pop(0))) return self._parser_state.error('invalid action', token=action_token)
def test_parse_filter(self): """Accept only filters that return an errno.""" self.assertEqual( self.parser.parse_filter( self._tokenize('arg0 == 0; return ENOSYS')), [ parser.Filter([[parser.Atom(0, '==', 0)]], bpf.ReturnErrno(self.arch.constants['ENOSYS'])), ])
def parse_action(self, tokens): if not tokens: self._parser_state.error('missing action') action_token = tokens.pop(0) # denylist policies must specify a return for every line. if self._denylist: if action_token.type != 'RETURN': self._parser_state.error('invalid denylist policy') if action_token.type == 'ACTION': if action_token.value == 'allow': return bpf.Allow() if action_token.value == 'kill': return self._kill_action if action_token.value == 'kill-process': return bpf.KillProcess() if action_token.value == 'kill-thread': return bpf.KillThread() if action_token.value == 'trap': return bpf.Trap() if action_token.value == 'trace': return bpf.Trace() if action_token.value == 'user-notify': return bpf.UserNotify() if action_token.value == 'log': return bpf.Log() elif action_token.type == 'NUMERIC_CONSTANT': constant = self._parse_single_constant(action_token) if constant == 1: return bpf.Allow() elif action_token.type == 'RETURN': if not tokens: self._parser_state.error('missing return value') if self._ret_log: tokens.pop(0) return bpf.Log() else: return bpf.ReturnErrno( self._parse_single_constant(tokens.pop(0))) return self._parser_state.error('invalid action', token=action_token)
def test_parse_include(self): """Allow including policy files.""" path = self._write_file( 'test.include.policy', """ {read, write}: arg0 == 0; allow """) path = self._write_file( 'test.policy', """ @include ./test.include.policy read: return ENOSYS """) self.assertEqual( self.parser.parse_file(path), parser.ParsedPolicy( default_action=bpf.KillProcess(), filter_statements=[ parser.FilterStatement( syscall=parser.Syscall('read', 0), frequency=1, filters=[ parser.Filter([[parser.Atom(0, '==', 0)]], bpf.Allow()), parser.Filter( None, bpf.ReturnErrno( self.arch.constants['ENOSYS'])), ]), parser.FilterStatement( syscall=parser.Syscall('write', 1), frequency=1, filters=[ parser.Filter([[parser.Atom(0, '==', 0)]], bpf.Allow()), parser.Filter(None, bpf.KillProcess()), ]), ]))
def main(argv=None): """Main entrypoint.""" if argv is None: argv = sys.argv[1:] opts, arg_parser = parse_args(argv) if not os.path.exists(opts.arch_json): arg_parser.error(CONSTANTS_ERR_MSG) parsed_arch = arch.Arch.load_from_json(opts.arch_json) policy_compiler = compiler.PolicyCompiler(parsed_arch) # Set ret_log to true if the MINIJAIL_DEFAULT_RET_LOG environment variable # is present. if 'MINIJAIL_DEFAULT_RET_LOG' in os.environ: print(""" \n********************** Warning: MINJAIL_DEFAULT_RET_LOG is on, policy will not have any effect **********************\n """) opts.use_ret_log = True if opts.use_ret_log: kill_action = bpf.Log() elif opts.denylist: # Default action for a denylist policy is return EPERM kill_action = bpf.ReturnErrno(parsed_arch.constants['EPERM']) elif opts.use_kill_process: kill_action = bpf.KillProcess() else: kill_action = bpf.KillThread() override_default_action = None if opts.default_action: parser_state = parser.ParserState('<memory>') override_default_action = parser.PolicyParser( parsed_arch, kill_action=bpf.KillProcess()).parse_action( next(parser_state.tokenize([opts.default_action]))) compiled_policy = policy_compiler.compile_file( opts.policy.name, optimization_strategy=opts.optimization_strategy, kill_action=kill_action, include_depth_limit=opts.include_depth_limit, override_default_action=override_default_action, denylist=opts.denylist, ret_log=opts.use_ret_log) # Outputs the bpf binary to a c header file instead of a binary file. if opts.output_header_file: output_file_base = opts.output with open(output_file_base + '.h', 'w') as output_file: program = ', '.join('%i' % x for x in compiled_policy.opcodes) output_file.write( HEADER_TEMPLATE % { 'upper_name': output_file_base.upper(), 'name': output_file_base, 'program': program, }) else: with open(opts.output, 'wb') as outf: outf.write(compiled_policy.opcodes) return 0