#!/usr/bin/env python # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # Copyright (C) 2014 Canonical, Ltd. # Copyright (C) 2015 Christian Boltz # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public # License as published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # ---------------------------------------------------------------------- import unittest from common_test import AATest, setup_all_loops import apparmor.severity as severity from apparmor.common import AppArmorException class SeverityBaseTest(AATest): def AASetup(self): self.sev_db = severity.Severity('severity.db', 'unknown') def _simple_severity_test(self, path, expected_rank): rank = self.sev_db.rank(path) self.assertEqual(rank, expected_rank, 'expected rank %s, got %s' % (expected_rank, rank)) def _simple_severity_w_perm(self, path, perm, expected_rank): rank = self.sev_db.rank(path, perm) self.assertEqual(rank, expected_rank, 'expected rank %s, got %s' % (expected_rank, rank)) class SeverityTest(SeverityBaseTest): tests = [ (['/usr/bin/whatis', 'x' ], 5), (['/etc', 'x' ], 'unknown'), (['/dev/doublehit', 'x' ], 0), (['/dev/doublehit', 'rx' ], 4), (['/dev/doublehit', 'rwx' ], 8), (['/dev/tty10', 'rwx' ], 9), (['/var/adm/foo/**', 'rx' ], 3), (['/etc/apparmor/**', 'r' ], 6), (['/etc/**', 'r' ], 'unknown'), (['/usr/foo@bar', 'r' ], 'unknown'), ## filename containing @ (['/home/foo@bar', 'rw' ], 6), ## filename containing @ ] def _run_test(self, params, expected): self._simple_severity_w_perm(params[0], params[1], expected) ## filename containing @ def test_invalid_rank(self): with self.assertRaises(AppArmorException): self._simple_severity_w_perm('unexpected_unput', 'rw', 6) class SeverityTestCap(SeverityBaseTest): tests = [ ('KILL', 8), ('SETPCAP', 9), ('setpcap', 9), ('UNKNOWN', 'unknown'), ('K*', 'unknown'), ('__ALL__', 10), ] def _run_test(self, params, expected): cap_with_prefix = 'CAP_%s' % params self._simple_severity_test(cap_with_prefix, expected) rank = self.sev_db.rank_capability(params) self.assertEqual(rank, expected, 'expected rank %s, got %s' % (expected, rank)) class SeverityVarsTest(SeverityBaseTest): VARIABLE_DEFINITIONS = ''' @{HOME}=@{HOMEDIRS}/*/ /root/ @{HOMEDIRS}=/home/ # add another path to @{HOMEDIRS} @{HOMEDIRS}+=/storage/ @{multiarch}=*-linux-gnu* @{TFTP_DIR}=/var/tftp /srv/tftpboot @{PROC}=/proc/ @{pid}={[1-9],[1-9][0-9],[1-9][0-9][0-9],[1-9][0-9][0-9][0-9],[1-9][0-9][0-9][0-9][0-9],[1-9][0-9][0-9][0-9][0-9][0-9]} @{tid}=@{pid} @{pids}=@{pid} @{somepaths}=/home/foo/downloads @{HOMEDIRS}/foo/.ssh/ ''' def _init_tunables(self, content=''): if not content: content = self.VARIABLE_DEFINITIONS self.rules_file = self.writeTmpfile('tunables', content) self.sev_db.load_variables(self.rules_file) def AATeardown(self): self.sev_db.unload_variables() tests = [ (['@{PROC}/sys/vm/overcommit_memory', 'r'], 6), (['@{HOME}/sys/@{PROC}/overcommit_memory', 'r'], 4), (['/overco@{multiarch}mmit_memory', 'r'], 'unknown'), (['@{PROC}/sys/@{TFTP_DIR}/overcommit_memory', 'r'], 6), (['@{somepaths}/somefile', 'r'], 7), ] def _run_test(self, params, expected): self._init_tunables() self._simple_severity_w_perm(params[0], params[1], expected) def test_include(self): self._init_tunables('#include ') # including non-existing files doesn't raise an exception self.assertTrue(True) # this test only makes sure that loading the tunables file works def test_invalid_variable_add(self): with self.assertRaises(AppArmorException): self._init_tunables('@{invalid} += /home/') def test_invalid_variable_double_definition(self): with self.assertRaises(AppArmorException): self._init_tunables('@{foo} = /home/\n@{foo} = /root/') class SeverityDBTest(AATest): def _test_db(self, contents): self.db_file = self.writeTmpfile('severity.db', contents) self.sev_db = severity.Severity(self.db_file) return self.sev_db tests = [ ("CAP_LEASE 18\n" , AppArmorException), # out of range ("CAP_LEASE -1\n" , AppArmorException), # out of range ("/etc/passwd* 0 4\n" , AppArmorException), # insufficient vals ("/etc/passwd* 0 4 5 6\n" , AppArmorException), # too many vals ("/etc/passwd* -2 4 6\n" , AppArmorException), # out of range ("/etc/passwd* 12 4 6\n" , AppArmorException), # out of range ("/etc/passwd* 2 -4 6\n" , AppArmorException), # out of range ("/etc/passwd 2 14 6\n" , AppArmorException), # out of range ("/etc/passwd 2 4 -12\n" , AppArmorException), # out of range ("/etc/passwd 2 4 4294967297\n" , AppArmorException), # out of range ("garbage line\n" , AppArmorException), ] def _run_test(self, params, expected): with self.assertRaises(expected): self._test_db(params) def test_simple_db(self): self._test_db(''' CAP_LEASE 8 /etc/passwd* 4 8 0 ''') def test_cap_val_max_range(self): self._test_db("CAP_LEASE 10\n") def test_cap_val_min_range(self): self._test_db("CAP_LEASE 0\n") def test_invalid_db(self): self.assertRaises(AppArmorException, severity.Severity, 'severity_broken.db') def test_nonexistent_db(self): self.assertRaises(IOError, severity.Severity, 'severity.db.does.not.exist') def test_no_arg_to_severity(self): with self.assertRaises(AppArmorException): severity.Severity() setup_all_loops(__name__) if __name__ == '__main__': unittest.main(verbosity=2)