apparmor/utils/test/test-mount.py
Georgia Garcia cec9ae6dff utils: fix coding style to match PEP8
Annotate exceptions with '  # noqa: ERROR'

Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2024-05-20 13:56:37 -03:00

274 lines
18 KiB
Python

#!/usr/bin/python3
# ----------------------------------------------------------------------
# Copyright (C) 2024 Canonical, Ltd.
#
# 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 collections import namedtuple
from common_test import AATest, setup_all_loops
from apparmor.common import AppArmorException, AppArmorBug
from apparmor.translations import init_translation
from apparmor.rule.mount import MountRule
_ = init_translation()
class MountTestParse(AATest):
tests = (
# Rule Operation Filesystem Options Source Destination Audit Deny Allow Comment
('mount -> **,', MountRule('mount', MountRule.ALL, MountRule.ALL, MountRule.ALL, '**', False, False, False, '')),
('mount options=(rw, shared) -> **,', MountRule('mount', MountRule.ALL, ('=', ('rw', 'shared')), MountRule.ALL, '**', False, False, False, '')),
('mount fstype=bpf options=rw bpf -> /sys/fs/bpf/,', MountRule('mount', ('=', ['bpf']), ('=', ('rw')), 'bpf', '/sys/fs/bpf/', False, False, False, '')),
('mount fstype=fuse.obex* options=rw bpf -> /sys/fs/bpf/,', MountRule('mount', ('=', ['fuse.obex*']), ('=', ('rw')), 'bpf', '/sys/fs/bpf/', False, False, False, '')),
('mount fstype=fuse.* options=rw bpf -> /sys/fs/bpf/,', MountRule('mount', ('=', ['fuse.*']), ('=', ('rw')), 'bpf', '/sys/fs/bpf/', False, False, False, '')),
('mount fstype=bpf options=(rw) random_label -> /sys/fs/bpf/,', MountRule('mount', ('=', ['bpf']), ('=', ('rw')), 'random_label', '/sys/fs/bpf/', False, False, False, '')),
('mount,', MountRule('mount', MountRule.ALL, MountRule.ALL, MountRule.ALL, MountRule.ALL, False, False, False, '')),
('mount fstype=(ext3, ext4),', MountRule('mount', ('=', ['ext3', 'ext4']), MountRule.ALL, MountRule.ALL, MountRule.ALL, False, False, False, '')),
('mount bpf,', MountRule('mount', MountRule.ALL, MountRule.ALL, 'bpf', MountRule.ALL, False, False, False, '')),
('mount none,', MountRule('mount', MountRule.ALL, MountRule.ALL, 'none', MountRule.ALL, False, False, False, '')),
('mount fstype=(ext3, ext4) options=(ro),', MountRule('mount', ('=', ['ext3', 'ext4']), ('=', ('ro')), MountRule.ALL, MountRule.ALL, False, False, False, '')),
('mount @{mntpnt},', MountRule('mount', MountRule.ALL, MountRule.ALL, '@{mntpnt}', MountRule.ALL, False, False, False, '')),
('mount /a,', MountRule('mount', MountRule.ALL, MountRule.ALL, '/a', MountRule.ALL, False, False, False, '')),
('mount fstype=(ext3, ext4) /a -> /b,', MountRule('mount', ('=', ['ext3', 'ext4']), MountRule.ALL, '/a', '/b', False, False, False, '')),
('mount fstype=(ext3, ext4) options=(ro, rbind) /a -> /b,', MountRule('mount', ('=', ['ext3', 'ext4']), ('=', ('ro', 'rbind')), '/a', '/b', False, False, False, '')),
('mount fstype=(ext3, ext4) options=(ro, rbind) /a -> /b, #cmt', MountRule('mount', ('=', ['ext3', 'ext4']), ('=', ('ro', 'rbind')), '/a', '/b', False, False, False, ' #cmt')),
('mount fstype=({ext3,ext4}) options in (ro, rbind) /a -> /b,', MountRule('mount', ('=', ['{ext3,ext4}']), ('in', ('ro', 'rbind')), '/a', '/b', False, False, False, '')),
('mount fstype in (ext3, ext4) options=(ro, rbind) /a -> /b, #cmt', MountRule('mount', ('in', ['ext3', 'ext4']), ('=', ('ro', 'rbind')), '/a', '/b', False, False, False, ' #cmt')),
('mount fstype in (ext3, ext4) option in (ro, rbind) /a, #cmt', MountRule('mount', ('in', ['ext3', 'ext4']), ('in', ('ro', 'rbind')), '/a', MountRule.ALL, False, False, False, ' #cmt')),
('mount fstype=(ext3, ext4) option=(ro, rbind) /a -> /b, #cmt', MountRule('mount', ('=', ['ext3', 'ext4']), ('=', ('ro', 'rbind')), '/a', '/b', False, False, False, ' #cmt')),
('mount options=(rw, rbind) {,/usr}/lib{,32,64,x32}/modules/ -> /tmp/snap.rootfs_*{,/usr}/lib/modules/,',
MountRule('mount', MountRule.ALL, ('=', ('rw', 'rbind')), '{,/usr}/lib{,32,64,x32}/modules/', # noqa: E127
'/tmp/snap.rootfs_*{,/usr}/lib/modules/', # noqa: E127
False, False, False, '')), # noqa: E127
('umount,', MountRule('umount', MountRule.ALL, MountRule.ALL, MountRule.ALL, MountRule.ALL, False, False, False, '')),
('umount fstype=ext3,', MountRule('umount', ('=', ['ext3']), MountRule.ALL, MountRule.ALL, MountRule.ALL, False, False, False, '')),
('umount /a,', MountRule('umount', MountRule.ALL, MountRule.ALL, MountRule.ALL, '/a', False, False, False, '')),
('remount,', MountRule('remount', MountRule.ALL, MountRule.ALL, MountRule.ALL, MountRule.ALL, False, False, False, '')),
('remount fstype=ext4,', MountRule('remount', ('=', ['ext4']), MountRule.ALL, MountRule.ALL, MountRule.ALL, False, False, False, '')),
('remount /b,', MountRule('remount', MountRule.ALL, MountRule.ALL, MountRule.ALL, '/b', False, False, False, '')),
)
def _run_test(self, rawrule, expected):
self.assertTrue(MountRule.match(rawrule))
obj = MountRule.create_instance(rawrule)
expected.raw_rule = rawrule.strip()
self.assertTrue(obj.is_equal(expected, True))
class MountTestParseInvalid(AATest):
tests = (
('mount fstype=,', AppArmorException),
('mount fstype=(),', AppArmorException),
('mount options=(),', AppArmorException),
('mount option=(invalid),', AppArmorException),
('mount option=(ext3ext4),', AppArmorException),
)
def _run_test(self, rawrule, expected):
self.assertTrue(MountRule.match(rawrule)) # the above invalid rules still match the main regex!
with self.assertRaises(expected):
MountRule.create_instance(rawrule)
def test_parse_fail(self):
with self.assertRaises(AppArmorException):
MountRule.create_instance('foo,')
def test_diff_non_mountrule(self):
exp = namedtuple('exp', ('audit', 'deny'))
obj = MountRule('mount', ('=', ['ext4']), MountRule.ALL, MountRule.ALL, MountRule.ALL)
with self.assertRaises(AppArmorBug):
obj.is_equal(exp(False, False), False)
def test_diff_invalid_fstype_equals_or_in(self):
with self.assertRaises(AppArmorBug):
MountRule('mount', ('ext3', 'ext4'), MountRule.ALL, MountRule.ALL, MountRule.ALL) # fstype[0] should be '=' or 'in'
def test_diff_invalid_fstype_aare(self):
tests = [
'mount fstype=({unclosed_regex),',
'mount fstype=({closed}twice}),',
]
for t in tests:
with self.assertRaises(AppArmorException):
MountRule.create_instance(t)
def test_diff_invalid_fstype_aare_2(self):
fslists = [
['invalid_{_regex'],
['ext4', 'invalid_}_regex'],
['ext4', '{invalid} {regex}']
]
for fslist in fslists:
with self.assertRaises(AppArmorException):
MountRule('mount', ('=', fslist), MountRule.ALL, MountRule.ALL, MountRule.ALL)
def test_diff_invalid_options_equals_or_in(self):
with self.assertRaises(AppArmorBug):
MountRule('mount', MountRule.ALL, ('rbind', 'rw'), MountRule.ALL, MountRule.ALL) # fstype[0] should be '=' or 'in'
def test_diff_invalid_options_keyword(self):
with self.assertRaises(AppArmorException):
MountRule('mount', MountRule.ALL, ('=', 'invalid'), MountRule.ALL, MountRule.ALL) # fstype[0] should be '=' or 'in'
def test_diff_fstype(self):
obj1 = MountRule('mount', ('=', ['ext4']), MountRule.ALL, MountRule.ALL, MountRule.ALL)
obj2 = MountRule('mount', MountRule.ALL, MountRule.ALL, MountRule.ALL, MountRule.ALL)
self.assertFalse(obj1.is_equal(obj2, False))
def test_diff_source(self):
obj1 = MountRule('mount', MountRule.ALL, MountRule.ALL, '/foo', MountRule.ALL)
obj2 = MountRule('mount', MountRule.ALL, MountRule.ALL, '/bar', MountRule.ALL)
self.assertFalse(obj1.is_equal(obj2, False))
def test_invalid_umount_with_source(self):
with self.assertRaises(AppArmorException):
MountRule('umount', MountRule.ALL, MountRule.ALL, '/foo', MountRule.ALL) # Umount and remount shall not have a source
def test_invalid_remount_with_source(self):
with self.assertRaises(AppArmorException):
MountRule('remount', MountRule.ALL, MountRule.ALL, '/foo', MountRule.ALL)
class MountTestGlob(AATest):
def test_glob(self):
globList = [(
'mount options=(bind, rw) /home/user/Downloads/ -> /mnt/a/,',
'mount options=(bind, rw) /home/user/Downloads/,',
'mount options=(bind, rw) /home/user/*/,',
'mount options=(bind, rw) /home/**/,',
'mount options=(bind, rw),',
'mount,',
'mount,',
)]
for globs in globList:
for i in range(len(globs) - 1):
rule = MountRule.create_instance(globs[i])
rule.glob()
self.assertEqual(rule.get_clean(), globs[i + 1])
class MountTestClean(AATest):
tests = (
# raw rule clean rule
(' mount , # foo ', 'mount, # foo'),
(' mount fstype = ( sysfs ) , ', 'mount fstype=(sysfs),'),
(' mount fstype = ( sysfs , procfs ) , ', 'mount fstype=(procfs, sysfs),'),
(' mount options = ( rw ) , ', 'mount options=(rw),'),
(' mount options = ( rw , noatime ) , ', 'mount options=(noatime, rw),'),
(' mount fstype in ( sysfs ) , ', 'mount fstype in (sysfs),'),
(' mount fstype in ( sysfs , procfs ) , ', 'mount fstype in (procfs, sysfs),'),
(' mount options in ( rw ) , ', 'mount options in (rw),'),
(' mount options in ( rw , noatime ) , ', 'mount options in (noatime, rw),'),
(' umount , ', 'umount,'),
(' umount /foo , ', 'umount /foo,'),
(' remount , ', 'remount,'),
(' remount /foo , ', 'remount /foo,'),
)
def _run_test(self, rawrule, expected):
self.assertTrue(MountRule.match(rawrule))
obj = MountRule.create_instance(rawrule)
clean = obj.get_clean()
raw = obj.get_raw()
self.assertEqual(expected, clean, 'unexpected clean rule')
self.assertEqual(rawrule.strip(), raw, 'unexpected raw rule')
class MountLogprofHeaderTest(AATest):
tests = (
('mount,', [_('Operation'), _('mount'), _('Fstype'), _('ALL'), _('Options'), _('ALL'), _('Source'), _('ALL'), _('Destination'), _('ALL')]),
('mount options=(ro, nosuid) /a,', [_('Operation'), _('mount'), _('Fstype'), _('ALL'), _('Options'), ('=', _('nosuid ro')), _('Source'), _('/a'), _('Destination'), _('ALL')]),
('mount fstype=(ext3, ext4) options=(ro, nosuid) /a -> /b,', [_('Operation'), _('mount'), _('Fstype'), ('=', _('ext3 ext4')), _('Options'), ('=', _('nosuid ro')), _('Source'), _('/a'), _('Destination'), _('/b')])
)
def _run_test(self, params, expected):
obj = MountRule.create_instance(params)
self.assertEqual(obj.logprof_header(), expected)
class MountIsCoveredTest(AATest):
def test_is_covered(self):
obj = MountRule('mount', ('=', ('ext3', 'ext4')), ('=', ('ro')), '/foo/b*', '/b*')
tests = [
('mount', ('=', ['ext3', 'ext4']), ('=', ('ro')), '/foo/b', '/bar'),
('mount', ('=', ['ext3', 'ext4']), ('=', ('ro')), '/foo/bar', '/b')
]
for test in tests:
self.assertTrue(obj.is_covered(MountRule(*test)))
self.assertFalse(obj.is_equal(MountRule(*test)))
def test_is_covered_fs_source(self):
obj = MountRule('mount', ('=', ['ext3', 'ext4']), ('=', ('ro')), 'tmpfs', MountRule.ALL)
self.assertTrue(obj.is_covered(MountRule('mount', ('=', ['ext3']), ('=', ('ro')), 'tmpfs', MountRule.ALL)))
self.assertFalse(obj.is_equal(MountRule('mount', ('=', ['ext3']), ('=', ('ro')), 'tmpfs', MountRule.ALL)))
def test_is_covered_aare_1(self):
obj = MountRule('mount', ('=', ['sys*', 'fuse.*']), ('=', ('ro')), 'tmpfs', MountRule.ALL)
tests = [
('mount', ('=', ['sysfs', 'fuse.s3fs']), ('=', ('ro')), 'tmpfs', MountRule.ALL),
('mount', ('=', ['sysfs', 'fuse.jmtpfs', 'fuse.s3fs', 'fuse.obexfs', 'fuse.obexautofs', 'fuse.fuseiso']), ('=', ('ro')), 'tmpfs', MountRule.ALL)
]
for test in tests:
self.assertTrue(obj.is_covered(MountRule(*test)))
self.assertFalse(obj.is_equal(MountRule(*test)))
def test_is_covered_aare_2(self):
obj = MountRule('mount', ('=', ['ext{3,4}', '{cgroup*,fuse.*}']), ('=', ('ro')), 'tmpfs', MountRule.ALL)
tests = [
('mount', ('=', ['ext3']), ('=', ('ro')), 'tmpfs', MountRule.ALL),
('mount', ('=', ['ext3', 'ext4', 'cgroup', 'cgroup2', 'fuse.jmtpfs', 'fuse.s3fs', 'fuse.obexfs', 'fuse.obexautofs', 'fuse.fuseiso']), ('=', ('ro')), 'tmpfs', MountRule.ALL)
]
for test in tests:
self.assertTrue(obj.is_covered(MountRule(*test)))
self.assertFalse(obj.is_equal(MountRule(*test)))
def test_is_notcovered(self):
obj = MountRule('mount', ('=', ['ext3', 'ext4']), ('=', ('ro')), '/foo/b*', '/b*')
tests = [
('mount', ('in', ['ext3', 'ext4']), ('=', ('ro')), '/foo/bar', '/bar'),
('mount', ('=', ['procfs', 'ext4']), ('=', ('ro')), '/foo/bar', '/bar'),
('mount', ('=', ['ext3']), ('=', ('rw')), '/foo/bar', '/bar'),
('mount', ('=', ['ext3', 'ext4']), MountRule.ALL, '/foo/b*', '/bar'),
('mount', MountRule.ALL, ('=', ('ro')), '/foo/b*', '/bar'),
('mount', ('=', ['ext3', 'ext4']), ('=', ('ro')), '/invalid/bar', '/bar'),
('umount', MountRule.ALL, MountRule.ALL, MountRule.ALL, '/bar'),
('remount', MountRule.ALL, MountRule.ALL, MountRule.ALL, '/bar'),
('mount', ('=', ['ext3', 'ext4']), ('=', ('ro')), 'tmpfs', '/bar'),
('mount', ('=', ['ext3', 'ext4']), ('=', ('ro')), '/foo/b*', '/invalid'),
]
for test in tests:
self.assertFalse(obj.is_covered(MountRule(*test)))
self.assertFalse(obj.is_equal(MountRule(*test)))
def test_is_not_covered_fs_source(self):
obj = MountRule('mount', ('=', ['ext3', 'ext4']), ('=', ('ro')), 'tmpfs', MountRule.ALL)
test = ('mount', ('=', ['ext3', 'ext4']), ('=', ('ro')), 'procfs', MountRule.ALL)
self.assertFalse(obj.is_covered(MountRule(*test)))
self.assertFalse(obj.is_equal(MountRule(*test)))
def test_is_not_covered_fs_options(self):
obj = MountRule('mount', MountRule.ALL, ('=', ('ro')), 'tmpfs', MountRule.ALL)
test = ('mount', MountRule.ALL, ('=', ('rw')), 'procfs', MountRule.ALL)
self.assertFalse(obj.is_covered(MountRule(*test)))
self.assertFalse(obj.is_equal(MountRule(*test)))
setup_all_loops(__name__)
if __name__ == '__main__':
unittest.main(verbosity=1)