1*4b9c6d91SCole Faust#!/usr/bin/env python3 2*4b9c6d91SCole Faust# 3*4b9c6d91SCole Faust# Copyright (C) 2021 The Android Open Source Project 4*4b9c6d91SCole Faust# 5*4b9c6d91SCole Faust# Licensed under the Apache License, Version 2.0 (the "License"); 6*4b9c6d91SCole Faust# you may not use this file except in compliance with the License. 7*4b9c6d91SCole Faust# You may obtain a copy of the License at 8*4b9c6d91SCole Faust# 9*4b9c6d91SCole Faust# http://www.apache.org/licenses/LICENSE-2.0 10*4b9c6d91SCole Faust# 11*4b9c6d91SCole Faust# Unless required by applicable law or agreed to in writing, software 12*4b9c6d91SCole Faust# distributed under the License is distributed on an "AS IS" BASIS, 13*4b9c6d91SCole Faust# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*4b9c6d91SCole Faust# See the License for the specific language governing permissions and 15*4b9c6d91SCole Faust# limitations under the License. 16*4b9c6d91SCole Faust"""Unittests for the seccomp policy linter module.""" 17*4b9c6d91SCole Faust 18*4b9c6d91SCole Faustfrom pathlib import Path 19*4b9c6d91SCole Faustimport tempfile 20*4b9c6d91SCole Faustimport unittest 21*4b9c6d91SCole Faust 22*4b9c6d91SCole Faustimport seccomp_policy_lint 23*4b9c6d91SCole Faust 24*4b9c6d91SCole Faustclass CheckSeccompPolicyTests(unittest.TestCase): 25*4b9c6d91SCole Faust """Tests for check_seccomp_policy.""" 26*4b9c6d91SCole Faust 27*4b9c6d91SCole Faust def setUp(self): 28*4b9c6d91SCole Faust self.tempdir = Path(tempfile.mkdtemp()) 29*4b9c6d91SCole Faust 30*4b9c6d91SCole Faust def _write_file(self, filename, contents): 31*4b9c6d91SCole Faust """Helper to write out a file for testing.""" 32*4b9c6d91SCole Faust path = self.tempdir / filename 33*4b9c6d91SCole Faust path.write_text(contents) 34*4b9c6d91SCole Faust return path 35*4b9c6d91SCole Faust 36*4b9c6d91SCole Faust def test_check_simple(self): 37*4b9c6d91SCole Faust """Allow simple policy files.""" 38*4b9c6d91SCole Faust path = self._write_file( 39*4b9c6d91SCole Faust 'test.policy', """ 40*4b9c6d91SCole Faust # Comment.\n 41*4b9c6d91SCole Faust read: 1\n 42*4b9c6d91SCole Faust write: 1\n 43*4b9c6d91SCole Faust """) 44*4b9c6d91SCole Faust 45*4b9c6d91SCole Faust exp_out = seccomp_policy_lint.CheckPolicyReturn( 46*4b9c6d91SCole Faust f'seccomp: {path.resolve()} does not contain any dangerous' 47*4b9c6d91SCole Faust ' syscalls, so does not require review from' 48*4b9c6d91SCole Faust ' chromeos-security@', 49*4b9c6d91SCole Faust []) 50*4b9c6d91SCole Faust 51*4b9c6d91SCole Faust with path.open('r', encoding='utf-8') as check_file: 52*4b9c6d91SCole Faust self.assertEqual(seccomp_policy_lint.check_seccomp_policy( 53*4b9c6d91SCole Faust check_file, seccomp_policy_lint.DANGEROUS_SYSCALLS), 54*4b9c6d91SCole Faust exp_out) 55*4b9c6d91SCole Faust 56*4b9c6d91SCole Faust def test_check_dangerous_comment(self): 57*4b9c6d91SCole Faust """Dangerous syscalls must have a comment and need to be reviewed.""" 58*4b9c6d91SCole Faust path = self._write_file( 59*4b9c6d91SCole Faust 'test.policy', """ 60*4b9c6d91SCole Faust # Comment.\n\n\n 61*4b9c6d91SCole Faust clone: 1\n 62*4b9c6d91SCole Faust write: 1\n 63*4b9c6d91SCole Faust """) 64*4b9c6d91SCole Faust 65*4b9c6d91SCole Faust exp_out = seccomp_policy_lint.CheckPolicyReturn( 66*4b9c6d91SCole Faust f'seccomp: {path.resolve()} contains dangerous syscalls,' 67*4b9c6d91SCole Faust ' so requires review from chromeos-security@', 68*4b9c6d91SCole Faust []) 69*4b9c6d91SCole Faust 70*4b9c6d91SCole Faust with path.open('r', encoding='utf-8') as check_file: 71*4b9c6d91SCole Faust self.assertEqual(seccomp_policy_lint.check_seccomp_policy( 72*4b9c6d91SCole Faust check_file, seccomp_policy_lint.DANGEROUS_SYSCALLS), 73*4b9c6d91SCole Faust exp_out) 74*4b9c6d91SCole Faust 75*4b9c6d91SCole Faust def test_check_dangerous_no_comment(self): 76*4b9c6d91SCole Faust """Dangerous syscalls without a comment should cause an error.""" 77*4b9c6d91SCole Faust path = self._write_file( 78*4b9c6d91SCole Faust 'test.policy', """ 79*4b9c6d91SCole Faust # Comment.\n 80*4b9c6d91SCole Faust mount: 1\n 81*4b9c6d91SCole Faust clone: 1\n 82*4b9c6d91SCole Faust """) 83*4b9c6d91SCole Faust 84*4b9c6d91SCole Faust exp_out = seccomp_policy_lint.CheckPolicyReturn( 85*4b9c6d91SCole Faust f'seccomp: {path.resolve()} contains dangerous syscalls,' 86*4b9c6d91SCole Faust ' so requires review from chromeos-security@', 87*4b9c6d91SCole Faust [(f'{path.resolve()}, line 5: clone syscall is a dangerous ' 88*4b9c6d91SCole Faust 'syscall so requires a comment on the preceding line')]) 89*4b9c6d91SCole Faust 90*4b9c6d91SCole Faust with path.open('r', encoding='utf-8') as check_file: 91*4b9c6d91SCole Faust self.assertEqual(seccomp_policy_lint.check_seccomp_policy( 92*4b9c6d91SCole Faust check_file, seccomp_policy_lint.DANGEROUS_SYSCALLS), 93*4b9c6d91SCole Faust exp_out) 94*4b9c6d91SCole Faust 95*4b9c6d91SCole Faust def test_check_duplicate_syscall(self): 96*4b9c6d91SCole Faust """Policy files cannot have duplicate syscalls..""" 97*4b9c6d91SCole Faust path = self._write_file( 98*4b9c6d91SCole Faust 'test.policy', """ 99*4b9c6d91SCole Faust # Comment.\n 100*4b9c6d91SCole Faust clone: 1\n 101*4b9c6d91SCole Faust clone: arg0 == 3 102*4b9c6d91SCole Faust """) 103*4b9c6d91SCole Faust 104*4b9c6d91SCole Faust exp_out = seccomp_policy_lint.CheckPolicyReturn( 105*4b9c6d91SCole Faust f'seccomp: {path.resolve()} contains dangerous syscalls,' 106*4b9c6d91SCole Faust ' so requires review from chromeos-security@', 107*4b9c6d91SCole Faust [(f'{path.resolve()}, line 5: repeat syscall: clone')]) 108*4b9c6d91SCole Faust 109*4b9c6d91SCole Faust with path.open('r', encoding='utf-8') as check_file: 110*4b9c6d91SCole Faust self.assertEqual(seccomp_policy_lint.check_seccomp_policy( 111*4b9c6d91SCole Faust check_file, seccomp_policy_lint.DANGEROUS_SYSCALLS), 112*4b9c6d91SCole Faust exp_out) 113*4b9c6d91SCole Faust 114*4b9c6d91SCole Faust 115*4b9c6d91SCole Faustif __name__ == '__main__': 116*4b9c6d91SCole Faust unittest.main() 117