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"""Unittests for the parser module.""" 18*4b9c6d91SCole Faust 19*4b9c6d91SCole Faustfrom __future__ import absolute_import 20*4b9c6d91SCole Faustfrom __future__ import division 21*4b9c6d91SCole Faustfrom __future__ import print_function 22*4b9c6d91SCole Faust 23*4b9c6d91SCole Faustimport os 24*4b9c6d91SCole Faustimport shutil 25*4b9c6d91SCole Faustimport tempfile 26*4b9c6d91SCole Faustimport unittest 27*4b9c6d91SCole Faustfrom importlib import resources 28*4b9c6d91SCole Faust 29*4b9c6d91SCole Faustimport arch 30*4b9c6d91SCole Faustimport bpf 31*4b9c6d91SCole Faustimport parser # pylint: disable=wrong-import-order 32*4b9c6d91SCole Faust 33*4b9c6d91SCole FaustARCH_64 = arch.Arch.load_from_json_bytes( 34*4b9c6d91SCole Faust resources.files("testdata").joinpath("arch_64.json").read_bytes() 35*4b9c6d91SCole Faust) 36*4b9c6d91SCole Faust 37*4b9c6d91SCole Faust 38*4b9c6d91SCole Faustclass TokenizerTests(unittest.TestCase): 39*4b9c6d91SCole Faust """Tests for ParserState.tokenize.""" 40*4b9c6d91SCole Faust 41*4b9c6d91SCole Faust @staticmethod 42*4b9c6d91SCole Faust def _tokenize(line): 43*4b9c6d91SCole Faust parser_state = parser.ParserState('<memory>') 44*4b9c6d91SCole Faust return list(parser_state.tokenize([line]))[0] 45*4b9c6d91SCole Faust 46*4b9c6d91SCole Faust def test_tokenize(self): 47*4b9c6d91SCole Faust """Accept valid tokens.""" 48*4b9c6d91SCole Faust self.assertEqual([ 49*4b9c6d91SCole Faust (token.type, token.value) 50*4b9c6d91SCole Faust for token in TokenizerTests._tokenize('@include /minijail.policy') 51*4b9c6d91SCole Faust ], [ 52*4b9c6d91SCole Faust ('INCLUDE', '@include'), 53*4b9c6d91SCole Faust ('PATH', '/minijail.policy'), 54*4b9c6d91SCole Faust ]) 55*4b9c6d91SCole Faust self.assertEqual([ 56*4b9c6d91SCole Faust (token.type, token.value) 57*4b9c6d91SCole Faust for token in TokenizerTests._tokenize('@include ./minijail.policy') 58*4b9c6d91SCole Faust ], [ 59*4b9c6d91SCole Faust ('INCLUDE', '@include'), 60*4b9c6d91SCole Faust ('PATH', './minijail.policy'), 61*4b9c6d91SCole Faust ]) 62*4b9c6d91SCole Faust self.assertEqual( 63*4b9c6d91SCole Faust [(token.type, token.value) for token in TokenizerTests._tokenize( 64*4b9c6d91SCole Faust 'read: arg0 in ~0xffff || arg0 & (1|2) && arg0 == 0755; ' 65*4b9c6d91SCole Faust 'return ENOSYS # ignored')], [ 66*4b9c6d91SCole Faust ('IDENTIFIER', 'read'), 67*4b9c6d91SCole Faust ('COLON', ':'), 68*4b9c6d91SCole Faust ('ARGUMENT', 'arg0'), 69*4b9c6d91SCole Faust ('OP', 'in'), 70*4b9c6d91SCole Faust ('BITWISE_COMPLEMENT', '~'), 71*4b9c6d91SCole Faust ('NUMERIC_CONSTANT', '0xffff'), 72*4b9c6d91SCole Faust ('OR', '||'), 73*4b9c6d91SCole Faust ('ARGUMENT', 'arg0'), 74*4b9c6d91SCole Faust ('OP', '&'), 75*4b9c6d91SCole Faust ('LPAREN', '('), 76*4b9c6d91SCole Faust ('NUMERIC_CONSTANT', '1'), 77*4b9c6d91SCole Faust ('BITWISE_OR', '|'), 78*4b9c6d91SCole Faust ('NUMERIC_CONSTANT', '2'), 79*4b9c6d91SCole Faust ('RPAREN', ')'), 80*4b9c6d91SCole Faust ('AND', '&&'), 81*4b9c6d91SCole Faust ('ARGUMENT', 'arg0'), 82*4b9c6d91SCole Faust ('OP', '=='), 83*4b9c6d91SCole Faust ('NUMERIC_CONSTANT', '0755'), 84*4b9c6d91SCole Faust ('SEMICOLON', ';'), 85*4b9c6d91SCole Faust ('RETURN', 'return'), 86*4b9c6d91SCole Faust ('IDENTIFIER', 'ENOSYS'), 87*4b9c6d91SCole Faust ]) 88*4b9c6d91SCole Faust # Ensure that tokens that have an otherwise valid token as prefix are 89*4b9c6d91SCole Faust # still matched correctly. 90*4b9c6d91SCole Faust self.assertEqual([ 91*4b9c6d91SCole Faust (token.type, token.value) 92*4b9c6d91SCole Faust for token in TokenizerTests._tokenize( 93*4b9c6d91SCole Faust 'inotify_wait return_sys killall trace_sys') 94*4b9c6d91SCole Faust ], [ 95*4b9c6d91SCole Faust ('IDENTIFIER', 'inotify_wait'), 96*4b9c6d91SCole Faust ('IDENTIFIER', 'return_sys'), 97*4b9c6d91SCole Faust ('IDENTIFIER', 'killall'), 98*4b9c6d91SCole Faust ('IDENTIFIER', 'trace_sys'), 99*4b9c6d91SCole Faust ]) 100*4b9c6d91SCole Faust 101*4b9c6d91SCole Faust def test_tokenize_invalid_token(self): 102*4b9c6d91SCole Faust """Reject tokenizer errors.""" 103*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 104*4b9c6d91SCole Faust (r'<memory>\(1:1\): invalid token\n' 105*4b9c6d91SCole Faust r' %invalid-token%\n' 106*4b9c6d91SCole Faust r' \^')): 107*4b9c6d91SCole Faust TokenizerTests._tokenize('%invalid-token%') 108*4b9c6d91SCole Faust 109*4b9c6d91SCole Faust 110*4b9c6d91SCole Faustclass ParseConstantTests(unittest.TestCase): 111*4b9c6d91SCole Faust """Tests for PolicyParser.parse_value.""" 112*4b9c6d91SCole Faust 113*4b9c6d91SCole Faust def setUp(self): 114*4b9c6d91SCole Faust self.arch = ARCH_64 115*4b9c6d91SCole Faust self.parser = parser.PolicyParser( 116*4b9c6d91SCole Faust self.arch, kill_action=bpf.KillProcess()) 117*4b9c6d91SCole Faust 118*4b9c6d91SCole Faust def _tokenize(self, line): 119*4b9c6d91SCole Faust # pylint: disable=protected-access 120*4b9c6d91SCole Faust return list(self.parser._parser_state.tokenize([line]))[0] 121*4b9c6d91SCole Faust 122*4b9c6d91SCole Faust def test_parse_constant_unsigned(self): 123*4b9c6d91SCole Faust """Accept reasonably-sized unsigned constants.""" 124*4b9c6d91SCole Faust self.assertEqual( 125*4b9c6d91SCole Faust self.parser.parse_value(self._tokenize('0x80000000')), 0x80000000) 126*4b9c6d91SCole Faust if self.arch.bits == 64: 127*4b9c6d91SCole Faust self.assertEqual( 128*4b9c6d91SCole Faust self.parser.parse_value(self._tokenize('0x8000000000000000')), 129*4b9c6d91SCole Faust 0x8000000000000000) 130*4b9c6d91SCole Faust 131*4b9c6d91SCole Faust def test_parse_constant_unsigned_too_big(self): 132*4b9c6d91SCole Faust """Reject unreasonably-sized unsigned constants.""" 133*4b9c6d91SCole Faust if self.arch.bits == 32: 134*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 135*4b9c6d91SCole Faust 'unsigned overflow'): 136*4b9c6d91SCole Faust self.parser.parse_value(self._tokenize('0x100000000')) 137*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 138*4b9c6d91SCole Faust 'unsigned overflow'): 139*4b9c6d91SCole Faust self.parser.parse_value(self._tokenize('0x10000000000000000')) 140*4b9c6d91SCole Faust 141*4b9c6d91SCole Faust def test_parse_constant_signed(self): 142*4b9c6d91SCole Faust """Accept reasonably-sized signed constants.""" 143*4b9c6d91SCole Faust self.assertEqual( 144*4b9c6d91SCole Faust self.parser.parse_value(self._tokenize('-1')), 145*4b9c6d91SCole Faust self.arch.max_unsigned) 146*4b9c6d91SCole Faust 147*4b9c6d91SCole Faust def test_parse_constant_signed_too_negative(self): 148*4b9c6d91SCole Faust """Reject unreasonably-sized signed constants.""" 149*4b9c6d91SCole Faust if self.arch.bits == 32: 150*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 151*4b9c6d91SCole Faust 'signed underflow'): 152*4b9c6d91SCole Faust self.parser.parse_value(self._tokenize('-0x800000001')) 153*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 'signed underflow'): 154*4b9c6d91SCole Faust self.parser.parse_value(self._tokenize('-0x8000000000000001')) 155*4b9c6d91SCole Faust 156*4b9c6d91SCole Faust def test_parse_mask(self): 157*4b9c6d91SCole Faust """Accept parsing a mask value.""" 158*4b9c6d91SCole Faust self.assertEqual( 159*4b9c6d91SCole Faust self.parser.parse_value(self._tokenize('0x1|0x2|0x4|0x8')), 0xf) 160*4b9c6d91SCole Faust 161*4b9c6d91SCole Faust def test_parse_parenthesized_expressions(self): 162*4b9c6d91SCole Faust """Accept parsing parenthesized expressions.""" 163*4b9c6d91SCole Faust bad_expressions = [ 164*4b9c6d91SCole Faust '(1', 165*4b9c6d91SCole Faust '|(1)', 166*4b9c6d91SCole Faust '(1)|', 167*4b9c6d91SCole Faust '()', 168*4b9c6d91SCole Faust '(', 169*4b9c6d91SCole Faust '((', 170*4b9c6d91SCole Faust '(()', 171*4b9c6d91SCole Faust '(()1', 172*4b9c6d91SCole Faust ] 173*4b9c6d91SCole Faust for expression in bad_expressions: 174*4b9c6d91SCole Faust with self.assertRaises(parser.ParseException, msg=expression): 175*4b9c6d91SCole Faust self.parser.parse_value(self._tokenize(expression)) 176*4b9c6d91SCole Faust 177*4b9c6d91SCole Faust bad_partial_expressions = [ 178*4b9c6d91SCole Faust '1)', 179*4b9c6d91SCole Faust '(1)1', 180*4b9c6d91SCole Faust '1(0)', 181*4b9c6d91SCole Faust ] 182*4b9c6d91SCole Faust for expression in bad_partial_expressions: 183*4b9c6d91SCole Faust tokens = self._tokenize(expression) 184*4b9c6d91SCole Faust self.parser.parse_value(tokens) 185*4b9c6d91SCole Faust self.assertNotEqual(tokens, []) 186*4b9c6d91SCole Faust 187*4b9c6d91SCole Faust good_expressions = [ 188*4b9c6d91SCole Faust '(3)', 189*4b9c6d91SCole Faust '(1)|2', 190*4b9c6d91SCole Faust '1|(2)', 191*4b9c6d91SCole Faust '(1)|(2)', 192*4b9c6d91SCole Faust '((3))', 193*4b9c6d91SCole Faust '0|(1|2)', 194*4b9c6d91SCole Faust '(0|1|2)', 195*4b9c6d91SCole Faust ] 196*4b9c6d91SCole Faust for expression in good_expressions: 197*4b9c6d91SCole Faust self.assertEqual( 198*4b9c6d91SCole Faust self.parser.parse_value(self._tokenize(expression)), 3) 199*4b9c6d91SCole Faust 200*4b9c6d91SCole Faust def test_parse_constant_complements(self): 201*4b9c6d91SCole Faust """Accept complementing constants.""" 202*4b9c6d91SCole Faust self.assertEqual( 203*4b9c6d91SCole Faust self.parser.parse_value(self._tokenize('~0')), 204*4b9c6d91SCole Faust self.arch.max_unsigned) 205*4b9c6d91SCole Faust self.assertEqual( 206*4b9c6d91SCole Faust self.parser.parse_value(self._tokenize('~0|~0')), 207*4b9c6d91SCole Faust self.arch.max_unsigned) 208*4b9c6d91SCole Faust if self.arch.bits == 32: 209*4b9c6d91SCole Faust self.assertEqual( 210*4b9c6d91SCole Faust self.parser.parse_value( 211*4b9c6d91SCole Faust self._tokenize('~0x005AF0FF|~0xFFA50FFF')), 0xFFFFFF00) 212*4b9c6d91SCole Faust self.assertEqual( 213*4b9c6d91SCole Faust self.parser.parse_value( 214*4b9c6d91SCole Faust self._tokenize('0x0F|~(0x005AF000|0x00A50FFF)|0xF0')), 215*4b9c6d91SCole Faust 0xFF0000FF) 216*4b9c6d91SCole Faust else: 217*4b9c6d91SCole Faust self.assertEqual( 218*4b9c6d91SCole Faust self.parser.parse_value( 219*4b9c6d91SCole Faust self._tokenize('~0x00005A5AF0F0FFFF|~0xFFFFA5A50F0FFFFF')), 220*4b9c6d91SCole Faust 0xFFFFFFFFFFFF0000) 221*4b9c6d91SCole Faust self.assertEqual( 222*4b9c6d91SCole Faust self.parser.parse_value( 223*4b9c6d91SCole Faust self._tokenize( 224*4b9c6d91SCole Faust '0x00FF|~(0x00005A5AF0F00000|0x0000A5A50F0FFFFF)|0xFF00' 225*4b9c6d91SCole Faust )), 0xFFFF00000000FFFF) 226*4b9c6d91SCole Faust 227*4b9c6d91SCole Faust def test_parse_double_complement(self): 228*4b9c6d91SCole Faust """Reject double-complementing constants.""" 229*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 230*4b9c6d91SCole Faust 'double complement'): 231*4b9c6d91SCole Faust self.parser.parse_value(self._tokenize('~~0')) 232*4b9c6d91SCole Faust 233*4b9c6d91SCole Faust def test_parse_empty_complement(self): 234*4b9c6d91SCole Faust """Reject complementing nothing.""" 235*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 'empty complement'): 236*4b9c6d91SCole Faust self.parser.parse_value(self._tokenize('0|~')) 237*4b9c6d91SCole Faust 238*4b9c6d91SCole Faust def test_parse_named_constant(self): 239*4b9c6d91SCole Faust """Accept parsing a named constant.""" 240*4b9c6d91SCole Faust self.assertEqual( 241*4b9c6d91SCole Faust self.parser.parse_value(self._tokenize('O_RDONLY')), 0) 242*4b9c6d91SCole Faust 243*4b9c6d91SCole Faust def test_parse_empty_constant(self): 244*4b9c6d91SCole Faust """Reject parsing nothing.""" 245*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 'empty constant'): 246*4b9c6d91SCole Faust self.parser.parse_value([]) 247*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 'empty constant'): 248*4b9c6d91SCole Faust self.parser.parse_value(self._tokenize('0|')) 249*4b9c6d91SCole Faust 250*4b9c6d91SCole Faust def test_parse_invalid_constant(self): 251*4b9c6d91SCole Faust """Reject parsing invalid constants.""" 252*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 'invalid constant'): 253*4b9c6d91SCole Faust self.parser.parse_value(self._tokenize('foo')) 254*4b9c6d91SCole Faust 255*4b9c6d91SCole Faust 256*4b9c6d91SCole Faustclass ParseFilterExpressionTests(unittest.TestCase): 257*4b9c6d91SCole Faust """Tests for PolicyParser.parse_argument_expression.""" 258*4b9c6d91SCole Faust 259*4b9c6d91SCole Faust def setUp(self): 260*4b9c6d91SCole Faust self.arch = ARCH_64 261*4b9c6d91SCole Faust self.parser = parser.PolicyParser( 262*4b9c6d91SCole Faust self.arch, kill_action=bpf.KillProcess()) 263*4b9c6d91SCole Faust 264*4b9c6d91SCole Faust def _tokenize(self, line): 265*4b9c6d91SCole Faust # pylint: disable=protected-access 266*4b9c6d91SCole Faust return list(self.parser._parser_state.tokenize([line]))[0] 267*4b9c6d91SCole Faust 268*4b9c6d91SCole Faust def test_parse_argument_expression(self): 269*4b9c6d91SCole Faust """Accept valid argument expressions.""" 270*4b9c6d91SCole Faust self.assertEqual( 271*4b9c6d91SCole Faust self.parser.parse_argument_expression( 272*4b9c6d91SCole Faust self._tokenize( 273*4b9c6d91SCole Faust 'arg0 in 0xffff || arg0 == PROT_EXEC && arg1 == PROT_WRITE' 274*4b9c6d91SCole Faust )), [ 275*4b9c6d91SCole Faust [parser.Atom(0, 'in', 0xffff)], 276*4b9c6d91SCole Faust [parser.Atom(0, '==', 4), 277*4b9c6d91SCole Faust parser.Atom(1, '==', 2)], 278*4b9c6d91SCole Faust ]) 279*4b9c6d91SCole Faust 280*4b9c6d91SCole Faust def test_parse_number_argument_expression(self): 281*4b9c6d91SCole Faust """Accept valid argument expressions with any octal/decimal/hex number.""" 282*4b9c6d91SCole Faust # 4607 == 010777 == 0x11ff 283*4b9c6d91SCole Faust self.assertEqual( 284*4b9c6d91SCole Faust self.parser.parse_argument_expression( 285*4b9c6d91SCole Faust self._tokenize('arg0 in 4607')), [ 286*4b9c6d91SCole Faust [parser.Atom(0, 'in', 4607)], 287*4b9c6d91SCole Faust ]) 288*4b9c6d91SCole Faust 289*4b9c6d91SCole Faust self.assertEqual( 290*4b9c6d91SCole Faust self.parser.parse_argument_expression( 291*4b9c6d91SCole Faust self._tokenize('arg0 in 010777')), [ 292*4b9c6d91SCole Faust [parser.Atom(0, 'in', 4607)], 293*4b9c6d91SCole Faust ]) 294*4b9c6d91SCole Faust 295*4b9c6d91SCole Faust self.assertEqual( 296*4b9c6d91SCole Faust self.parser.parse_argument_expression( 297*4b9c6d91SCole Faust self._tokenize('arg0 in 0x11ff')), [ 298*4b9c6d91SCole Faust [parser.Atom(0, 'in', 4607)], 299*4b9c6d91SCole Faust ]) 300*4b9c6d91SCole Faust 301*4b9c6d91SCole Faust def test_parse_empty_argument_expression(self): 302*4b9c6d91SCole Faust """Reject empty argument expressions.""" 303*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 304*4b9c6d91SCole Faust 'empty argument expression'): 305*4b9c6d91SCole Faust self.parser.parse_argument_expression( 306*4b9c6d91SCole Faust self._tokenize('arg0 in 0xffff ||')) 307*4b9c6d91SCole Faust 308*4b9c6d91SCole Faust def test_parse_empty_clause(self): 309*4b9c6d91SCole Faust """Reject empty clause.""" 310*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 'empty clause'): 311*4b9c6d91SCole Faust self.parser.parse_argument_expression( 312*4b9c6d91SCole Faust self._tokenize('arg0 in 0xffff &&')) 313*4b9c6d91SCole Faust 314*4b9c6d91SCole Faust def test_parse_invalid_argument(self): 315*4b9c6d91SCole Faust """Reject invalid argument.""" 316*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 'invalid argument'): 317*4b9c6d91SCole Faust self.parser.parse_argument_expression( 318*4b9c6d91SCole Faust self._tokenize('argX in 0xffff')) 319*4b9c6d91SCole Faust 320*4b9c6d91SCole Faust def test_parse_invalid_operator(self): 321*4b9c6d91SCole Faust """Reject invalid operator.""" 322*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 'invalid operator'): 323*4b9c6d91SCole Faust self.parser.parse_argument_expression( 324*4b9c6d91SCole Faust self._tokenize('arg0 = 0xffff')) 325*4b9c6d91SCole Faust 326*4b9c6d91SCole Faust def test_parse_missing_operator(self): 327*4b9c6d91SCole Faust """Reject missing operator.""" 328*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 'missing operator'): 329*4b9c6d91SCole Faust self.parser.parse_argument_expression(self._tokenize('arg0')) 330*4b9c6d91SCole Faust 331*4b9c6d91SCole Faust def test_parse_missing_operand(self): 332*4b9c6d91SCole Faust """Reject missing operand.""" 333*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 'empty constant'): 334*4b9c6d91SCole Faust self.parser.parse_argument_expression(self._tokenize('arg0 ==')) 335*4b9c6d91SCole Faust 336*4b9c6d91SCole Faust 337*4b9c6d91SCole Faustclass ParseFilterTests(unittest.TestCase): 338*4b9c6d91SCole Faust """Tests for PolicyParser.parse_filter.""" 339*4b9c6d91SCole Faust 340*4b9c6d91SCole Faust def setUp(self): 341*4b9c6d91SCole Faust self.arch = ARCH_64 342*4b9c6d91SCole Faust self.parser = parser.PolicyParser( 343*4b9c6d91SCole Faust self.arch, kill_action=bpf.KillProcess()) 344*4b9c6d91SCole Faust 345*4b9c6d91SCole Faust def _tokenize(self, line): 346*4b9c6d91SCole Faust # pylint: disable=protected-access 347*4b9c6d91SCole Faust return list(self.parser._parser_state.tokenize([line]))[0] 348*4b9c6d91SCole Faust 349*4b9c6d91SCole Faust def test_parse_filter(self): 350*4b9c6d91SCole Faust """Accept valid filters.""" 351*4b9c6d91SCole Faust self.assertEqual( 352*4b9c6d91SCole Faust self.parser.parse_filter(self._tokenize('arg0 == 0')), [ 353*4b9c6d91SCole Faust parser.Filter([[parser.Atom(0, '==', 0)]], bpf.Allow()), 354*4b9c6d91SCole Faust ]) 355*4b9c6d91SCole Faust self.assertEqual( 356*4b9c6d91SCole Faust self.parser.parse_filter(self._tokenize('kill-process')), [ 357*4b9c6d91SCole Faust parser.Filter(None, bpf.KillProcess()), 358*4b9c6d91SCole Faust ]) 359*4b9c6d91SCole Faust self.assertEqual( 360*4b9c6d91SCole Faust self.parser.parse_filter(self._tokenize('kill-thread')), [ 361*4b9c6d91SCole Faust parser.Filter(None, bpf.KillThread()), 362*4b9c6d91SCole Faust ]) 363*4b9c6d91SCole Faust self.assertEqual( 364*4b9c6d91SCole Faust self.parser.parse_filter(self._tokenize('trap')), [ 365*4b9c6d91SCole Faust parser.Filter(None, bpf.Trap()), 366*4b9c6d91SCole Faust ]) 367*4b9c6d91SCole Faust self.assertEqual( 368*4b9c6d91SCole Faust self.parser.parse_filter(self._tokenize('return ENOSYS')), [ 369*4b9c6d91SCole Faust parser.Filter(None, 370*4b9c6d91SCole Faust bpf.ReturnErrno(self.arch.constants['ENOSYS'])), 371*4b9c6d91SCole Faust ]) 372*4b9c6d91SCole Faust self.assertEqual( 373*4b9c6d91SCole Faust self.parser.parse_filter(self._tokenize('trace')), [ 374*4b9c6d91SCole Faust parser.Filter(None, bpf.Trace()), 375*4b9c6d91SCole Faust ]) 376*4b9c6d91SCole Faust self.assertEqual( 377*4b9c6d91SCole Faust self.parser.parse_filter(self._tokenize('user-notify')), [ 378*4b9c6d91SCole Faust parser.Filter(None, bpf.UserNotify()), 379*4b9c6d91SCole Faust ]) 380*4b9c6d91SCole Faust self.assertEqual( 381*4b9c6d91SCole Faust self.parser.parse_filter(self._tokenize('log')), [ 382*4b9c6d91SCole Faust parser.Filter(None, bpf.Log()), 383*4b9c6d91SCole Faust ]) 384*4b9c6d91SCole Faust self.assertEqual( 385*4b9c6d91SCole Faust self.parser.parse_filter(self._tokenize('allow')), [ 386*4b9c6d91SCole Faust parser.Filter(None, bpf.Allow()), 387*4b9c6d91SCole Faust ]) 388*4b9c6d91SCole Faust self.assertEqual( 389*4b9c6d91SCole Faust self.parser.parse_filter(self._tokenize('1')), [ 390*4b9c6d91SCole Faust parser.Filter(None, bpf.Allow()), 391*4b9c6d91SCole Faust ]) 392*4b9c6d91SCole Faust self.assertEqual( 393*4b9c6d91SCole Faust self.parser.parse_filter( 394*4b9c6d91SCole Faust self._tokenize( 395*4b9c6d91SCole Faust '{ arg0 == 0, arg0 == 1; return ENOSYS, trap }')), 396*4b9c6d91SCole Faust [ 397*4b9c6d91SCole Faust parser.Filter([[parser.Atom(0, '==', 0)]], bpf.Allow()), 398*4b9c6d91SCole Faust parser.Filter([[parser.Atom(0, '==', 1)]], 399*4b9c6d91SCole Faust bpf.ReturnErrno(self.arch.constants['ENOSYS'])), 400*4b9c6d91SCole Faust parser.Filter(None, bpf.Trap()), 401*4b9c6d91SCole Faust ]) 402*4b9c6d91SCole Faust 403*4b9c6d91SCole Faust def test_parse_missing_return_value(self): 404*4b9c6d91SCole Faust """Reject missing return value.""" 405*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 406*4b9c6d91SCole Faust 'missing return value'): 407*4b9c6d91SCole Faust self.parser.parse_filter(self._tokenize('return')) 408*4b9c6d91SCole Faust 409*4b9c6d91SCole Faust def test_parse_invalid_return_value(self): 410*4b9c6d91SCole Faust """Reject invalid return value.""" 411*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 'invalid constant'): 412*4b9c6d91SCole Faust self.parser.parse_filter(self._tokenize('return arg0')) 413*4b9c6d91SCole Faust 414*4b9c6d91SCole Faust def test_parse_unclosed_brace(self): 415*4b9c6d91SCole Faust """Reject unclosed brace.""" 416*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 'unclosed brace'): 417*4b9c6d91SCole Faust self.parser.parse_filter(self._tokenize('{ allow')) 418*4b9c6d91SCole Faust 419*4b9c6d91SCole Faust 420*4b9c6d91SCole Faustclass ParseFilterDenylistTests(unittest.TestCase): 421*4b9c6d91SCole Faust """Tests for PolicyParser.parse_filter with a denylist policy.""" 422*4b9c6d91SCole Faust 423*4b9c6d91SCole Faust def setUp(self): 424*4b9c6d91SCole Faust self.arch = ARCH_64 425*4b9c6d91SCole Faust self.kill_action = bpf.KillProcess() 426*4b9c6d91SCole Faust self.parser = parser.PolicyParser( 427*4b9c6d91SCole Faust self.arch, kill_action=self.kill_action, denylist=True) 428*4b9c6d91SCole Faust 429*4b9c6d91SCole Faust def _tokenize(self, line): 430*4b9c6d91SCole Faust # pylint: disable=protected-access 431*4b9c6d91SCole Faust return list(self.parser._parser_state.tokenize([line]))[0] 432*4b9c6d91SCole Faust 433*4b9c6d91SCole Faust def test_parse_filter(self): 434*4b9c6d91SCole Faust """Accept only filters that return an errno.""" 435*4b9c6d91SCole Faust self.assertEqual( 436*4b9c6d91SCole Faust self.parser.parse_filter(self._tokenize('arg0 == 0; return ENOSYS')), 437*4b9c6d91SCole Faust [ 438*4b9c6d91SCole Faust parser.Filter([[parser.Atom(0, '==', 0)]], 439*4b9c6d91SCole Faust bpf.ReturnErrno(self.arch.constants['ENOSYS'])), 440*4b9c6d91SCole Faust ]) 441*4b9c6d91SCole Faust 442*4b9c6d91SCole Faust 443*4b9c6d91SCole Faustclass ParseFilterStatementTests(unittest.TestCase): 444*4b9c6d91SCole Faust """Tests for PolicyParser.parse_filter_statement.""" 445*4b9c6d91SCole Faust 446*4b9c6d91SCole Faust def setUp(self): 447*4b9c6d91SCole Faust self.arch = ARCH_64 448*4b9c6d91SCole Faust self.parser = parser.PolicyParser( 449*4b9c6d91SCole Faust self.arch, kill_action=bpf.KillProcess()) 450*4b9c6d91SCole Faust 451*4b9c6d91SCole Faust def _tokenize(self, line): 452*4b9c6d91SCole Faust # pylint: disable=protected-access 453*4b9c6d91SCole Faust return list(self.parser._parser_state.tokenize([line]))[0] 454*4b9c6d91SCole Faust 455*4b9c6d91SCole Faust def assertEqualIgnoringToken(self, actual, expected, msg=None): 456*4b9c6d91SCole Faust """Similar to assertEqual, but ignores the token field.""" 457*4b9c6d91SCole Faust if (actual.syscalls != expected.syscalls or 458*4b9c6d91SCole Faust actual.filters != expected.filters): 459*4b9c6d91SCole Faust self.fail('%r != %r' % (actual, expected), msg) 460*4b9c6d91SCole Faust 461*4b9c6d91SCole Faust def test_parse_filter_statement(self): 462*4b9c6d91SCole Faust """Accept valid filter statements.""" 463*4b9c6d91SCole Faust self.assertEqualIgnoringToken( 464*4b9c6d91SCole Faust self.parser.parse_filter_statement( 465*4b9c6d91SCole Faust self._tokenize('read: arg0 == 0')), 466*4b9c6d91SCole Faust parser.ParsedFilterStatement( 467*4b9c6d91SCole Faust syscalls=(parser.Syscall('read', 0), ), 468*4b9c6d91SCole Faust filters=[ 469*4b9c6d91SCole Faust parser.Filter([[parser.Atom(0, '==', 0)]], bpf.Allow()), 470*4b9c6d91SCole Faust ], 471*4b9c6d91SCole Faust token=None)) 472*4b9c6d91SCole Faust self.assertEqualIgnoringToken( 473*4b9c6d91SCole Faust self.parser.parse_filter_statement( 474*4b9c6d91SCole Faust self._tokenize('{read, write}: arg0 == 0')), 475*4b9c6d91SCole Faust parser.ParsedFilterStatement( 476*4b9c6d91SCole Faust syscalls=( 477*4b9c6d91SCole Faust parser.Syscall('read', 0), 478*4b9c6d91SCole Faust parser.Syscall('write', 1), 479*4b9c6d91SCole Faust ), 480*4b9c6d91SCole Faust filters=[ 481*4b9c6d91SCole Faust parser.Filter([[parser.Atom(0, '==', 0)]], bpf.Allow()), 482*4b9c6d91SCole Faust ], 483*4b9c6d91SCole Faust token=None)) 484*4b9c6d91SCole Faust self.assertEqualIgnoringToken( 485*4b9c6d91SCole Faust self.parser.parse_filter_statement( 486*4b9c6d91SCole Faust self._tokenize('io@libc: arg0 == 0')), 487*4b9c6d91SCole Faust parser.ParsedFilterStatement( 488*4b9c6d91SCole Faust syscalls=( 489*4b9c6d91SCole Faust parser.Syscall('read', 0), 490*4b9c6d91SCole Faust parser.Syscall('write', 1), 491*4b9c6d91SCole Faust ), 492*4b9c6d91SCole Faust filters=[ 493*4b9c6d91SCole Faust parser.Filter([[parser.Atom(0, '==', 0)]], bpf.Allow()), 494*4b9c6d91SCole Faust ], 495*4b9c6d91SCole Faust token=None)) 496*4b9c6d91SCole Faust self.assertEqualIgnoringToken( 497*4b9c6d91SCole Faust self.parser.parse_filter_statement( 498*4b9c6d91SCole Faust self._tokenize('file-io@systemd: arg0 == 0')), 499*4b9c6d91SCole Faust parser.ParsedFilterStatement( 500*4b9c6d91SCole Faust syscalls=( 501*4b9c6d91SCole Faust parser.Syscall('read', 0), 502*4b9c6d91SCole Faust parser.Syscall('write', 1), 503*4b9c6d91SCole Faust ), 504*4b9c6d91SCole Faust filters=[ 505*4b9c6d91SCole Faust parser.Filter([[parser.Atom(0, '==', 0)]], bpf.Allow()), 506*4b9c6d91SCole Faust ], 507*4b9c6d91SCole Faust token=None)) 508*4b9c6d91SCole Faust self.assertEqualIgnoringToken( 509*4b9c6d91SCole Faust self.parser.parse_filter_statement( 510*4b9c6d91SCole Faust self._tokenize('kill: arg0 == 0')), 511*4b9c6d91SCole Faust parser.ParsedFilterStatement( 512*4b9c6d91SCole Faust syscalls=( 513*4b9c6d91SCole Faust parser.Syscall('kill', 62), 514*4b9c6d91SCole Faust ), 515*4b9c6d91SCole Faust filters=[ 516*4b9c6d91SCole Faust parser.Filter([[parser.Atom(0, '==', 0)]], bpf.Allow()), 517*4b9c6d91SCole Faust ], 518*4b9c6d91SCole Faust token=None)) 519*4b9c6d91SCole Faust 520*4b9c6d91SCole Faust def test_parse_metadata(self): 521*4b9c6d91SCole Faust """Accept valid filter statements with metadata.""" 522*4b9c6d91SCole Faust self.assertEqualIgnoringToken( 523*4b9c6d91SCole Faust self.parser.parse_filter_statement( 524*4b9c6d91SCole Faust self._tokenize('read[arch=test]: arg0 == 0')), 525*4b9c6d91SCole Faust parser.ParsedFilterStatement( 526*4b9c6d91SCole Faust syscalls=( 527*4b9c6d91SCole Faust parser.Syscall('read', 0), 528*4b9c6d91SCole Faust ), 529*4b9c6d91SCole Faust filters=[ 530*4b9c6d91SCole Faust parser.Filter([[parser.Atom(0, '==', 0)]], bpf.Allow()), 531*4b9c6d91SCole Faust ], 532*4b9c6d91SCole Faust token=None)) 533*4b9c6d91SCole Faust self.assertEqualIgnoringToken( 534*4b9c6d91SCole Faust self.parser.parse_filter_statement( 535*4b9c6d91SCole Faust self._tokenize( 536*4b9c6d91SCole Faust '{read, nonexistent[arch=nonexistent]}: arg0 == 0')), 537*4b9c6d91SCole Faust parser.ParsedFilterStatement( 538*4b9c6d91SCole Faust syscalls=( 539*4b9c6d91SCole Faust parser.Syscall('read', 0), 540*4b9c6d91SCole Faust ), 541*4b9c6d91SCole Faust filters=[ 542*4b9c6d91SCole Faust parser.Filter([[parser.Atom(0, '==', 0)]], bpf.Allow()), 543*4b9c6d91SCole Faust ], 544*4b9c6d91SCole Faust token=None)) 545*4b9c6d91SCole Faust 546*4b9c6d91SCole Faust def test_parse_unclosed_brace(self): 547*4b9c6d91SCole Faust """Reject unclosed brace.""" 548*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 'unclosed brace'): 549*4b9c6d91SCole Faust self.parser.parse_filter(self._tokenize('{ allow')) 550*4b9c6d91SCole Faust 551*4b9c6d91SCole Faust def test_parse_invalid_syscall_group(self): 552*4b9c6d91SCole Faust """Reject invalid syscall groups.""" 553*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 'unclosed brace'): 554*4b9c6d91SCole Faust self.parser.parse_filter_statement( 555*4b9c6d91SCole Faust self._tokenize('{ read, write: arg0 == 0')) 556*4b9c6d91SCole Faust 557*4b9c6d91SCole Faust def test_parse_missing_colon(self): 558*4b9c6d91SCole Faust """Reject missing colon.""" 559*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 'missing colon'): 560*4b9c6d91SCole Faust self.parser.parse_filter_statement(self._tokenize('read')) 561*4b9c6d91SCole Faust 562*4b9c6d91SCole Faust def test_parse_invalid_colon(self): 563*4b9c6d91SCole Faust """Reject invalid colon.""" 564*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 'invalid colon'): 565*4b9c6d91SCole Faust self.parser.parse_filter_statement(self._tokenize('read arg0')) 566*4b9c6d91SCole Faust 567*4b9c6d91SCole Faust def test_parse_missing_filter(self): 568*4b9c6d91SCole Faust """Reject missing filter.""" 569*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 'missing filter'): 570*4b9c6d91SCole Faust self.parser.parse_filter_statement(self._tokenize('read:')) 571*4b9c6d91SCole Faust 572*4b9c6d91SCole Faust 573*4b9c6d91SCole Faustclass ParseFileTests(unittest.TestCase): 574*4b9c6d91SCole Faust """Tests for PolicyParser.parse_file.""" 575*4b9c6d91SCole Faust 576*4b9c6d91SCole Faust def setUp(self): 577*4b9c6d91SCole Faust self.arch = ARCH_64 578*4b9c6d91SCole Faust self.parser = parser.PolicyParser( 579*4b9c6d91SCole Faust self.arch, kill_action=bpf.KillProcess()) 580*4b9c6d91SCole Faust self.tempdir = tempfile.mkdtemp() 581*4b9c6d91SCole Faust 582*4b9c6d91SCole Faust def tearDown(self): 583*4b9c6d91SCole Faust shutil.rmtree(self.tempdir) 584*4b9c6d91SCole Faust 585*4b9c6d91SCole Faust def _write_file(self, filename, contents): 586*4b9c6d91SCole Faust """Helper to write out a file for testing.""" 587*4b9c6d91SCole Faust path = os.path.join(self.tempdir, filename) 588*4b9c6d91SCole Faust with open(path, 'w') as outf: 589*4b9c6d91SCole Faust outf.write(contents) 590*4b9c6d91SCole Faust return path 591*4b9c6d91SCole Faust 592*4b9c6d91SCole Faust def test_parse_simple(self): 593*4b9c6d91SCole Faust """Allow simple policy files.""" 594*4b9c6d91SCole Faust path = self._write_file( 595*4b9c6d91SCole Faust 'test.policy', """ 596*4b9c6d91SCole Faust # Comment. 597*4b9c6d91SCole Faust read: allow 598*4b9c6d91SCole Faust write: allow 599*4b9c6d91SCole Faust """) 600*4b9c6d91SCole Faust 601*4b9c6d91SCole Faust self.assertEqual( 602*4b9c6d91SCole Faust self.parser.parse_file(path), 603*4b9c6d91SCole Faust parser.ParsedPolicy( 604*4b9c6d91SCole Faust default_action=bpf.KillProcess(), 605*4b9c6d91SCole Faust filter_statements=[ 606*4b9c6d91SCole Faust parser.FilterStatement( 607*4b9c6d91SCole Faust syscall=parser.Syscall('read', 0), 608*4b9c6d91SCole Faust frequency=1, 609*4b9c6d91SCole Faust filters=[ 610*4b9c6d91SCole Faust parser.Filter(None, bpf.Allow()), 611*4b9c6d91SCole Faust ]), 612*4b9c6d91SCole Faust parser.FilterStatement( 613*4b9c6d91SCole Faust syscall=parser.Syscall('write', 1), 614*4b9c6d91SCole Faust frequency=1, 615*4b9c6d91SCole Faust filters=[ 616*4b9c6d91SCole Faust parser.Filter(None, bpf.Allow()), 617*4b9c6d91SCole Faust ]), 618*4b9c6d91SCole Faust ])) 619*4b9c6d91SCole Faust 620*4b9c6d91SCole Faust def test_parse_multiline(self): 621*4b9c6d91SCole Faust """Allow simple multi-line policy files.""" 622*4b9c6d91SCole Faust path = self._write_file( 623*4b9c6d91SCole Faust 'test.policy', """ 624*4b9c6d91SCole Faust # Comment. 625*4b9c6d91SCole Faust read: \ 626*4b9c6d91SCole Faust allow 627*4b9c6d91SCole Faust write: allow 628*4b9c6d91SCole Faust """) 629*4b9c6d91SCole Faust 630*4b9c6d91SCole Faust self.assertEqual( 631*4b9c6d91SCole Faust self.parser.parse_file(path), 632*4b9c6d91SCole Faust parser.ParsedPolicy( 633*4b9c6d91SCole Faust default_action=bpf.KillProcess(), 634*4b9c6d91SCole Faust filter_statements=[ 635*4b9c6d91SCole Faust parser.FilterStatement( 636*4b9c6d91SCole Faust syscall=parser.Syscall('read', 0), 637*4b9c6d91SCole Faust frequency=1, 638*4b9c6d91SCole Faust filters=[ 639*4b9c6d91SCole Faust parser.Filter(None, bpf.Allow()), 640*4b9c6d91SCole Faust ]), 641*4b9c6d91SCole Faust parser.FilterStatement( 642*4b9c6d91SCole Faust syscall=parser.Syscall('write', 1), 643*4b9c6d91SCole Faust frequency=1, 644*4b9c6d91SCole Faust filters=[ 645*4b9c6d91SCole Faust parser.Filter(None, bpf.Allow()), 646*4b9c6d91SCole Faust ]), 647*4b9c6d91SCole Faust ])) 648*4b9c6d91SCole Faust 649*4b9c6d91SCole Faust def test_parse_default(self): 650*4b9c6d91SCole Faust """Allow defining a default action.""" 651*4b9c6d91SCole Faust path = self._write_file( 652*4b9c6d91SCole Faust 'test.policy', """ 653*4b9c6d91SCole Faust @default kill-thread 654*4b9c6d91SCole Faust read: allow 655*4b9c6d91SCole Faust """) 656*4b9c6d91SCole Faust 657*4b9c6d91SCole Faust self.assertEqual( 658*4b9c6d91SCole Faust self.parser.parse_file(path), 659*4b9c6d91SCole Faust parser.ParsedPolicy( 660*4b9c6d91SCole Faust default_action=bpf.KillThread(), 661*4b9c6d91SCole Faust filter_statements=[ 662*4b9c6d91SCole Faust parser.FilterStatement( 663*4b9c6d91SCole Faust syscall=parser.Syscall('read', 0), 664*4b9c6d91SCole Faust frequency=1, 665*4b9c6d91SCole Faust filters=[ 666*4b9c6d91SCole Faust parser.Filter(None, bpf.Allow()), 667*4b9c6d91SCole Faust ]), 668*4b9c6d91SCole Faust ])) 669*4b9c6d91SCole Faust 670*4b9c6d91SCole Faust def test_parse_default_permissive(self): 671*4b9c6d91SCole Faust """Reject defining a permissive default action.""" 672*4b9c6d91SCole Faust path = self._write_file( 673*4b9c6d91SCole Faust 'test.policy', """ 674*4b9c6d91SCole Faust @default log 675*4b9c6d91SCole Faust read: allow 676*4b9c6d91SCole Faust """) 677*4b9c6d91SCole Faust 678*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 679*4b9c6d91SCole Faust r'invalid permissive default action'): 680*4b9c6d91SCole Faust self.parser.parse_file(path) 681*4b9c6d91SCole Faust 682*4b9c6d91SCole Faust def test_parse_simple_grouped(self): 683*4b9c6d91SCole Faust """Allow simple policy files.""" 684*4b9c6d91SCole Faust path = self._write_file( 685*4b9c6d91SCole Faust 'test.policy', """ 686*4b9c6d91SCole Faust # Comment. 687*4b9c6d91SCole Faust {read, write}: allow 688*4b9c6d91SCole Faust """) 689*4b9c6d91SCole Faust 690*4b9c6d91SCole Faust self.assertEqual( 691*4b9c6d91SCole Faust self.parser.parse_file(path), 692*4b9c6d91SCole Faust parser.ParsedPolicy( 693*4b9c6d91SCole Faust default_action=bpf.KillProcess(), 694*4b9c6d91SCole Faust filter_statements=[ 695*4b9c6d91SCole Faust parser.FilterStatement( 696*4b9c6d91SCole Faust syscall=parser.Syscall('read', 0), 697*4b9c6d91SCole Faust frequency=1, 698*4b9c6d91SCole Faust filters=[ 699*4b9c6d91SCole Faust parser.Filter(None, bpf.Allow()), 700*4b9c6d91SCole Faust ]), 701*4b9c6d91SCole Faust parser.FilterStatement( 702*4b9c6d91SCole Faust syscall=parser.Syscall('write', 1), 703*4b9c6d91SCole Faust frequency=1, 704*4b9c6d91SCole Faust filters=[ 705*4b9c6d91SCole Faust parser.Filter(None, bpf.Allow()), 706*4b9c6d91SCole Faust ]), 707*4b9c6d91SCole Faust ])) 708*4b9c6d91SCole Faust 709*4b9c6d91SCole Faust def test_parse_other_arch(self): 710*4b9c6d91SCole Faust """Allow entries that only target another architecture.""" 711*4b9c6d91SCole Faust path = self._write_file( 712*4b9c6d91SCole Faust 'test.policy', """ 713*4b9c6d91SCole Faust # Comment. 714*4b9c6d91SCole Faust read[arch=nonexistent]: allow 715*4b9c6d91SCole Faust write: allow 716*4b9c6d91SCole Faust """) 717*4b9c6d91SCole Faust 718*4b9c6d91SCole Faust self.assertEqual( 719*4b9c6d91SCole Faust self.parser.parse_file(path), 720*4b9c6d91SCole Faust parser.ParsedPolicy( 721*4b9c6d91SCole Faust default_action=bpf.KillProcess(), 722*4b9c6d91SCole Faust filter_statements=[ 723*4b9c6d91SCole Faust parser.FilterStatement( 724*4b9c6d91SCole Faust syscall=parser.Syscall('write', 1), 725*4b9c6d91SCole Faust frequency=1, 726*4b9c6d91SCole Faust filters=[ 727*4b9c6d91SCole Faust parser.Filter(None, bpf.Allow()), 728*4b9c6d91SCole Faust ]), 729*4b9c6d91SCole Faust ])) 730*4b9c6d91SCole Faust 731*4b9c6d91SCole Faust def test_parse_include(self): 732*4b9c6d91SCole Faust """Allow including policy files.""" 733*4b9c6d91SCole Faust path = self._write_file( 734*4b9c6d91SCole Faust 'test.include.policy', """ 735*4b9c6d91SCole Faust {read, write}: arg0 == 0; allow 736*4b9c6d91SCole Faust """) 737*4b9c6d91SCole Faust path = self._write_file( 738*4b9c6d91SCole Faust 'test.policy', """ 739*4b9c6d91SCole Faust @include ./test.include.policy 740*4b9c6d91SCole Faust read: return ENOSYS 741*4b9c6d91SCole Faust """) 742*4b9c6d91SCole Faust 743*4b9c6d91SCole Faust self.assertEqual( 744*4b9c6d91SCole Faust self.parser.parse_file(path), 745*4b9c6d91SCole Faust parser.ParsedPolicy( 746*4b9c6d91SCole Faust default_action=bpf.KillProcess(), 747*4b9c6d91SCole Faust filter_statements=[ 748*4b9c6d91SCole Faust parser.FilterStatement( 749*4b9c6d91SCole Faust syscall=parser.Syscall('read', 0), 750*4b9c6d91SCole Faust frequency=1, 751*4b9c6d91SCole Faust filters=[ 752*4b9c6d91SCole Faust parser.Filter([[parser.Atom(0, '==', 0)]], 753*4b9c6d91SCole Faust bpf.Allow()), 754*4b9c6d91SCole Faust parser.Filter( 755*4b9c6d91SCole Faust None, 756*4b9c6d91SCole Faust bpf.ReturnErrno( 757*4b9c6d91SCole Faust self.arch.constants['ENOSYS'])), 758*4b9c6d91SCole Faust ]), 759*4b9c6d91SCole Faust parser.FilterStatement( 760*4b9c6d91SCole Faust syscall=parser.Syscall('write', 1), 761*4b9c6d91SCole Faust frequency=1, 762*4b9c6d91SCole Faust filters=[ 763*4b9c6d91SCole Faust parser.Filter([[parser.Atom(0, '==', 0)]], 764*4b9c6d91SCole Faust bpf.Allow()), 765*4b9c6d91SCole Faust parser.Filter(None, bpf.KillProcess()), 766*4b9c6d91SCole Faust ]), 767*4b9c6d91SCole Faust ])) 768*4b9c6d91SCole Faust 769*4b9c6d91SCole Faust def test_parse_invalid_include(self): 770*4b9c6d91SCole Faust """Reject including invalid policy files.""" 771*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 772*4b9c6d91SCole Faust r'empty include path'): 773*4b9c6d91SCole Faust path = self._write_file( 774*4b9c6d91SCole Faust 'test.policy', """ 775*4b9c6d91SCole Faust @include 776*4b9c6d91SCole Faust """) 777*4b9c6d91SCole Faust self.parser.parse_file(path) 778*4b9c6d91SCole Faust 779*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 780*4b9c6d91SCole Faust r'invalid include path'): 781*4b9c6d91SCole Faust path = self._write_file( 782*4b9c6d91SCole Faust 'test.policy', """ 783*4b9c6d91SCole Faust @include arg0 784*4b9c6d91SCole Faust """) 785*4b9c6d91SCole Faust self.parser.parse_file(path) 786*4b9c6d91SCole Faust 787*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 788*4b9c6d91SCole Faust r'@include statement nested too deep'): 789*4b9c6d91SCole Faust path = self._write_file( 790*4b9c6d91SCole Faust 'test.policy', """ 791*4b9c6d91SCole Faust @include ./test.policy 792*4b9c6d91SCole Faust """) 793*4b9c6d91SCole Faust self.parser.parse_file(path) 794*4b9c6d91SCole Faust 795*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 796*4b9c6d91SCole Faust r'Could not @include .*'): 797*4b9c6d91SCole Faust path = self._write_file( 798*4b9c6d91SCole Faust 'test.policy', """ 799*4b9c6d91SCole Faust @include ./nonexistent.policy 800*4b9c6d91SCole Faust """) 801*4b9c6d91SCole Faust self.parser.parse_file(path) 802*4b9c6d91SCole Faust 803*4b9c6d91SCole Faust def test_parse_frequency(self): 804*4b9c6d91SCole Faust """Allow including frequency files.""" 805*4b9c6d91SCole Faust self._write_file( 806*4b9c6d91SCole Faust 'test.frequency', """ 807*4b9c6d91SCole Faust read: 2 808*4b9c6d91SCole Faust write: 3 809*4b9c6d91SCole Faust """) 810*4b9c6d91SCole Faust path = self._write_file( 811*4b9c6d91SCole Faust 'test.policy', """ 812*4b9c6d91SCole Faust @frequency ./test.frequency 813*4b9c6d91SCole Faust read: allow 814*4b9c6d91SCole Faust """) 815*4b9c6d91SCole Faust 816*4b9c6d91SCole Faust self.assertEqual( 817*4b9c6d91SCole Faust self.parser.parse_file(path), 818*4b9c6d91SCole Faust parser.ParsedPolicy( 819*4b9c6d91SCole Faust default_action=bpf.KillProcess(), 820*4b9c6d91SCole Faust filter_statements=[ 821*4b9c6d91SCole Faust parser.FilterStatement( 822*4b9c6d91SCole Faust syscall=parser.Syscall('read', 0), 823*4b9c6d91SCole Faust frequency=2, 824*4b9c6d91SCole Faust filters=[ 825*4b9c6d91SCole Faust parser.Filter(None, bpf.Allow()), 826*4b9c6d91SCole Faust ]), 827*4b9c6d91SCole Faust ])) 828*4b9c6d91SCole Faust 829*4b9c6d91SCole Faust def test_parse_invalid_frequency(self): 830*4b9c6d91SCole Faust """Reject including invalid frequency files.""" 831*4b9c6d91SCole Faust path = self._write_file('test.policy', 832*4b9c6d91SCole Faust """@frequency ./test.frequency""") 833*4b9c6d91SCole Faust 834*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, r'missing colon'): 835*4b9c6d91SCole Faust self._write_file('test.frequency', """ 836*4b9c6d91SCole Faust read 837*4b9c6d91SCole Faust """) 838*4b9c6d91SCole Faust self.parser.parse_file(path) 839*4b9c6d91SCole Faust 840*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, r'invalid colon'): 841*4b9c6d91SCole Faust self._write_file('test.frequency', """ 842*4b9c6d91SCole Faust read foo 843*4b9c6d91SCole Faust """) 844*4b9c6d91SCole Faust self.parser.parse_file(path) 845*4b9c6d91SCole Faust 846*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, r'missing number'): 847*4b9c6d91SCole Faust self._write_file('test.frequency', """ 848*4b9c6d91SCole Faust read: 849*4b9c6d91SCole Faust """) 850*4b9c6d91SCole Faust self.parser.parse_file(path) 851*4b9c6d91SCole Faust 852*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, r'invalid number'): 853*4b9c6d91SCole Faust self._write_file('test.frequency', """ 854*4b9c6d91SCole Faust read: foo 855*4b9c6d91SCole Faust """) 856*4b9c6d91SCole Faust self.parser.parse_file(path) 857*4b9c6d91SCole Faust 858*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, r'invalid number'): 859*4b9c6d91SCole Faust self._write_file('test.frequency', """ 860*4b9c6d91SCole Faust read: -1 861*4b9c6d91SCole Faust """) 862*4b9c6d91SCole Faust self.parser.parse_file(path) 863*4b9c6d91SCole Faust 864*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 865*4b9c6d91SCole Faust r'empty frequency path'): 866*4b9c6d91SCole Faust path = self._write_file( 867*4b9c6d91SCole Faust 'test.policy', """ 868*4b9c6d91SCole Faust @frequency 869*4b9c6d91SCole Faust """) 870*4b9c6d91SCole Faust self.parser.parse_file(path) 871*4b9c6d91SCole Faust 872*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 873*4b9c6d91SCole Faust r'invalid frequency path'): 874*4b9c6d91SCole Faust path = self._write_file( 875*4b9c6d91SCole Faust 'test.policy', """ 876*4b9c6d91SCole Faust @frequency arg0 877*4b9c6d91SCole Faust """) 878*4b9c6d91SCole Faust self.parser.parse_file(path) 879*4b9c6d91SCole Faust 880*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 881*4b9c6d91SCole Faust r'Could not open frequency file.*'): 882*4b9c6d91SCole Faust path = self._write_file( 883*4b9c6d91SCole Faust 'test.policy', """ 884*4b9c6d91SCole Faust @frequency ./nonexistent.frequency 885*4b9c6d91SCole Faust """) 886*4b9c6d91SCole Faust self.parser.parse_file(path) 887*4b9c6d91SCole Faust 888*4b9c6d91SCole Faust def test_parse_multiple_unconditional(self): 889*4b9c6d91SCole Faust """Reject actions after an unconditional action.""" 890*4b9c6d91SCole Faust path = self._write_file( 891*4b9c6d91SCole Faust 'test.policy', """ 892*4b9c6d91SCole Faust read: allow 893*4b9c6d91SCole Faust read: allow 894*4b9c6d91SCole Faust """) 895*4b9c6d91SCole Faust 896*4b9c6d91SCole Faust with self.assertRaisesRegex( 897*4b9c6d91SCole Faust parser.ParseException, 898*4b9c6d91SCole Faust (r'test.policy\(3:17\): ' 899*4b9c6d91SCole Faust r'Syscall read.*already had an unconditional action ' 900*4b9c6d91SCole Faust r'applied')): 901*4b9c6d91SCole Faust self.parser.parse_file(path) 902*4b9c6d91SCole Faust 903*4b9c6d91SCole Faust path = self._write_file( 904*4b9c6d91SCole Faust 'test.policy', """ 905*4b9c6d91SCole Faust read: log 906*4b9c6d91SCole Faust read: arg0 == 0; log 907*4b9c6d91SCole Faust """) 908*4b9c6d91SCole Faust 909*4b9c6d91SCole Faust with self.assertRaisesRegex( 910*4b9c6d91SCole Faust parser.ParseException, 911*4b9c6d91SCole Faust (r'test.policy\(3:17\): ' 912*4b9c6d91SCole Faust r'Syscall read.*already had an unconditional action ' 913*4b9c6d91SCole Faust r'applied')): 914*4b9c6d91SCole Faust self.parser.parse_file(path) 915*4b9c6d91SCole Faust 916*4b9c6d91SCole Faust def test_parse_allowlist_denylist_header(self): 917*4b9c6d91SCole Faust """Reject trying to compile denylist policy file as allowlist.""" 918*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 919*4b9c6d91SCole Faust r'policy is denylist, but flag --denylist ' 920*4b9c6d91SCole Faust 'not passed in'): 921*4b9c6d91SCole Faust path = self._write_file( 922*4b9c6d91SCole Faust 'test.policy', """ 923*4b9c6d91SCole Faust @denylist 924*4b9c6d91SCole Faust """) 925*4b9c6d91SCole Faust self.parser.parse_file(path) 926*4b9c6d91SCole Faust 927*4b9c6d91SCole Faust 928*4b9c6d91SCole Faustclass ParseFileDenylistTests(unittest.TestCase): 929*4b9c6d91SCole Faust """Tests for PolicyParser.parse_file.""" 930*4b9c6d91SCole Faust 931*4b9c6d91SCole Faust def setUp(self): 932*4b9c6d91SCole Faust self.arch = ARCH_64 933*4b9c6d91SCole Faust self.kill_action = bpf.KillProcess() 934*4b9c6d91SCole Faust self.parser = parser.PolicyParser( 935*4b9c6d91SCole Faust self.arch, kill_action=self.kill_action, denylist=True) 936*4b9c6d91SCole Faust self.tempdir = tempfile.mkdtemp() 937*4b9c6d91SCole Faust 938*4b9c6d91SCole Faust def tearDown(self): 939*4b9c6d91SCole Faust shutil.rmtree(self.tempdir) 940*4b9c6d91SCole Faust 941*4b9c6d91SCole Faust def _write_file(self, filename, contents): 942*4b9c6d91SCole Faust """Helper to write out a file for testing.""" 943*4b9c6d91SCole Faust path = os.path.join(self.tempdir, filename) 944*4b9c6d91SCole Faust with open(path, 'w') as outf: 945*4b9c6d91SCole Faust outf.write(contents) 946*4b9c6d91SCole Faust return path 947*4b9c6d91SCole Faust 948*4b9c6d91SCole Faust def test_parse_simple(self): 949*4b9c6d91SCole Faust """Allow simple denylist policy files.""" 950*4b9c6d91SCole Faust path = self._write_file( 951*4b9c6d91SCole Faust 'test.policy', """ 952*4b9c6d91SCole Faust # Comment. 953*4b9c6d91SCole Faust @denylist 954*4b9c6d91SCole Faust read: return ENOSYS 955*4b9c6d91SCole Faust write: return ENOSYS 956*4b9c6d91SCole Faust """) 957*4b9c6d91SCole Faust 958*4b9c6d91SCole Faust self.assertEqual( 959*4b9c6d91SCole Faust self.parser.parse_file(path), 960*4b9c6d91SCole Faust parser.ParsedPolicy( 961*4b9c6d91SCole Faust default_action=bpf.Allow(), 962*4b9c6d91SCole Faust filter_statements=[ 963*4b9c6d91SCole Faust parser.FilterStatement( 964*4b9c6d91SCole Faust syscall=parser.Syscall('read', 0), 965*4b9c6d91SCole Faust frequency=1, 966*4b9c6d91SCole Faust filters=[ 967*4b9c6d91SCole Faust parser.Filter(None, bpf.ReturnErrno( 968*4b9c6d91SCole Faust self.arch.constants['ENOSYS'])), 969*4b9c6d91SCole Faust ]), 970*4b9c6d91SCole Faust parser.FilterStatement( 971*4b9c6d91SCole Faust syscall=parser.Syscall('write', 1), 972*4b9c6d91SCole Faust frequency=1, 973*4b9c6d91SCole Faust filters=[ 974*4b9c6d91SCole Faust parser.Filter(None, bpf.ReturnErrno( 975*4b9c6d91SCole Faust self.arch.constants['ENOSYS'])), 976*4b9c6d91SCole Faust ]), 977*4b9c6d91SCole Faust ])) 978*4b9c6d91SCole Faust 979*4b9c6d91SCole Faust def test_parse_simple_with_arg(self): 980*4b9c6d91SCole Faust """Allow simple denylist policy files.""" 981*4b9c6d91SCole Faust path = self._write_file( 982*4b9c6d91SCole Faust 'test.policy', """ 983*4b9c6d91SCole Faust # Comment. 984*4b9c6d91SCole Faust @denylist 985*4b9c6d91SCole Faust read: return ENOSYS 986*4b9c6d91SCole Faust write: arg0 == 0 ; return ENOSYS 987*4b9c6d91SCole Faust """) 988*4b9c6d91SCole Faust 989*4b9c6d91SCole Faust self.assertEqual( 990*4b9c6d91SCole Faust self.parser.parse_file(path), 991*4b9c6d91SCole Faust parser.ParsedPolicy( 992*4b9c6d91SCole Faust default_action=bpf.Allow(), 993*4b9c6d91SCole Faust filter_statements=[ 994*4b9c6d91SCole Faust parser.FilterStatement( 995*4b9c6d91SCole Faust syscall=parser.Syscall('read', 0), 996*4b9c6d91SCole Faust frequency=1, 997*4b9c6d91SCole Faust filters=[ 998*4b9c6d91SCole Faust parser.Filter(None, bpf.ReturnErrno( 999*4b9c6d91SCole Faust self.arch.constants['ENOSYS'])), 1000*4b9c6d91SCole Faust ]), 1001*4b9c6d91SCole Faust parser.FilterStatement( 1002*4b9c6d91SCole Faust syscall=parser.Syscall('write', 1), 1003*4b9c6d91SCole Faust frequency=1, 1004*4b9c6d91SCole Faust filters=[ 1005*4b9c6d91SCole Faust parser.Filter([[parser.Atom(0, '==', 0)]], 1006*4b9c6d91SCole Faust bpf.ReturnErrno(self.arch.constants['ENOSYS'])), 1007*4b9c6d91SCole Faust parser.Filter(None, bpf.Allow()), 1008*4b9c6d91SCole Faust ]), 1009*4b9c6d91SCole Faust ])) 1010*4b9c6d91SCole Faust 1011*4b9c6d91SCole Faust 1012*4b9c6d91SCole Faust def test_parse_denylist_no_header(self): 1013*4b9c6d91SCole Faust """Reject trying to compile denylist policy file as allowlist.""" 1014*4b9c6d91SCole Faust with self.assertRaisesRegex(parser.ParseException, 1015*4b9c6d91SCole Faust r'policy must contain @denylist flag to be ' 1016*4b9c6d91SCole Faust 'compiled with --denylist flag'): 1017*4b9c6d91SCole Faust path = self._write_file( 1018*4b9c6d91SCole Faust 'test.policy', """ 1019*4b9c6d91SCole Faust read: return ENOSYS 1020*4b9c6d91SCole Faust """) 1021*4b9c6d91SCole Faust self.parser.parse_file(path) 1022*4b9c6d91SCole Faust 1023*4b9c6d91SCole Faustif __name__ == '__main__': 1024*4b9c6d91SCole Faust unittest.main() 1025