xref: /aosp_15_r20/external/minijail/tools/parser_unittest.py (revision 4b9c6d91573e8b3a96609339b46361b5476dd0f9)
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