2
0
mirror of https://gitlab.com/apparmor/apparmor synced 2025-08-22 01:57:43 +00:00

Merge utils: add support for multiple options and fstypes in mount rules

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1693
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
This commit is contained in:
John Johansen 2025-06-03 23:43:40 +00:00
commit 23deb55149
3 changed files with 257 additions and 114 deletions

View File

@ -32,11 +32,12 @@ flags_change_propagation = {
'make-rslave'
}
# keep in sync with parser/mount.cc mnt_opts_table!
# ordering is relevant here due to re.finditer - if r is present in the list before rw, then options will match r, not rw
flags_keywords = list(flags_bind_mount) + list(flags_change_propagation) + [
'ro', 'r', 'read-only', 'rw', 'w', 'suid', 'nosuid', 'dev', 'nodev', 'exec', 'noexec', 'sync', 'async', 'mand',
'ro', 'read-only', 'rw', 'suid', 'nosuid', 'dev', 'nodev', 'exec', 'noexec', 'sync', 'async', 'mand',
'nomand', 'dirsync', 'symfollow', 'nosymfollow', 'atime', 'noatime', 'diratime', 'nodiratime', 'move', 'M',
'verbose', 'silent', 'loud', 'acl', 'noacl', 'relatime', 'norelatime', 'iversion', 'noiversion', 'strictatime',
'nostrictatime', 'lazytime', 'nolazytime', 'user', 'nouser', '([A-Za-z0-9])',
'nostrictatime', 'lazytime', 'nolazytime', 'user', 'nouser', 'r', 'w', '([A-Za-z0-9])',
]
join_valid_flags = '|'.join(flags_keywords)
@ -53,11 +54,11 @@ fs_type_pattern = r'\b(?P<fstype_or_vfstype>fstype|vfstype)\b\s*(?P<fstype_equal
option_pattern = r'\s*(\boption(s?)\b\s*(?P<options_equals_or_in>=|in)\s*'\
r'(?P<options>\(\s*(' + join_valid_flags + r')(' + sep + r'(' + join_valid_flags + r'))*\s*\)|' \
r'(\s*' + join_valid_flags + r')'\
r'))?'
r'))'
# allow any order of fstype and options
# Note: also matches if multiple fstype= or options= are given to keep the regex simpler
mount_condition_pattern = rf'({fs_type_pattern}\s*|{option_pattern}\s*)*'
mount_condition_pattern = rf'({fs_type_pattern}\s*|{option_pattern}?\s*)*'
# Source can either be
# - A path : /foo
@ -92,10 +93,9 @@ dest_fileglob_pattern = (
RE_MOUNT_DETAILS = re.compile(r'^\s*' + mount_condition_pattern + rf'(\s+{source_fileglob_pattern})?' + rf'(\s+->\s+{dest_fileglob_pattern})?\s*' + r'$')
RE_UMOUNT_DETAILS = re.compile(r'^\s*' + mount_condition_pattern + rf'(\s+{dest_fileglob_pattern})?\s*' + r'$')
# check if a rule contains multiple 'options' or 'fstype'
# (not using option_pattern or fs_type_pattern here because a) it also matches an empty string, and b) using it twice would cause name conflicts)
# check if a rule contains multiple 'fstype'
# (not using fs_type_pattern here because a) it also matches an empty string, and b) using it twice would cause name conflicts)
multi_param_template = r'\sPARAM\s*(=|\sin).*\sPARAM\s*(=|\sin)'
RE_MOUNT_MULTIPLE_OPTIONS = re.compile(multi_param_template.replace('PARAM', 'options'))
RE_MOUNT_MULTIPLE_FS_TYPE = re.compile(multi_param_template.replace('PARAM', 'v?fstype'))
@ -124,42 +124,55 @@ class MountRule(BaseRule):
self.operation = operation
if fstype == self.ALL or fstype[1] == self.ALL:
self.all_fstype = True
self.fstype = None
self.is_fstype_equal = None
else:
self.all_fstype = False
for it in fstype[1]:
aare_len, unused = parse_aare(it, 0, 'fstype')
if aare_len != len(it):
raise AppArmorException(f'Invalid aare : {it}')
self.fstype = fstype[1]
self.is_fstype_equal = fstype[0]
if not isinstance(fstype, list):
fstype = [fstype]
self.options, self.all_options, unknown_items = check_and_split_list(options[1] if options != self.ALL else options, flags_keywords, self.ALL, type(self).__name__, 'options')
if unknown_items:
raise AppArmorException(_('Passed unknown options keyword to %s: %s') % (type(self).__name__, ' '.join(unknown_items)))
self.is_options_equal = options[0] if not self.all_options else None
# self.all_fstype will only be true if no fstypes are
# specified, so it's fine to set it inside the loop
self.fstype = []
for fst in fstype:
if fst == self.ALL or fst[1] == self.ALL:
self.all_fstype = True
fstype_values = None
is_fstype_equal = None
else:
self.all_fstype = False
for it in fst[1]:
aare_len, unused = parse_aare(it, 0, 'fstype')
if aare_len != len(it):
raise AppArmorException(f'Invalid aare : {it}')
fstype_values = fst[1]
is_fstype_equal = fst[0]
self.fstype.append(MountConditional('fstype', fstype_values, self.all_fstype, is_fstype_equal, 'aare'))
if not isinstance(options, list):
options = [options]
# self.all_options will only be true if no options are
# specified, so it's fine to set it inside the loop
self.options = []
for opts in options:
opt_values, self.all_options, unknown_items = check_and_split_list(opts[1] if opts != self.ALL else opts, flags_keywords, self.ALL, type(self).__name__, 'options')
if unknown_items:
raise AppArmorException(_('Passed unknown options keyword to %s: %s') % (type(self).__name__, ' '.join(unknown_items)))
is_options_equal = opts[0] if not self.all_options else None
self.options.append(MountConditional('options', opt_values, self.all_options, is_options_equal, 'list'))
self.source, self.all_source = self._aare_or_all(source, 'source', is_path=False, log_event=log_event, empty_ok=True)
self.dest, self.all_dest = self._aare_or_all(dest, 'dest', is_path=False, log_event=log_event)
if not self.all_fstype and self.is_fstype_equal not in ('=', 'in'):
raise AppArmorBug(f'Invalid is_fstype_equal : {self.is_fstype_equal}')
if not self.all_options and self.is_options_equal not in ('=', 'in'):
raise AppArmorBug(f'Invalid is_options_equal : {self.is_options_equal}')
if self.operation != 'mount' and not self.all_source:
raise AppArmorException(f'Operation {self.operation} cannot have a source')
if self.operation == 'mount' and not self.all_options and flags_change_propagation & self.options != set():
if not (self.all_source or self.all_dest):
raise AppArmorException(f'Operation {flags_change_propagation & self.options} cannot specify a source. Source = {self.source}')
elif not self.all_fstype:
raise AppArmorException(f'Operation {flags_change_propagation & self.options} cannot specify a fstype. Fstype = {self.fstype}')
for opt_cond in self.options:
if self.operation == 'mount' and not self.all_options and flags_change_propagation & opt_cond.values != set():
if not (self.all_source or self.all_dest):
raise AppArmorException(f'Operation {flags_change_propagation & opt_cond.values} cannot specify a source. Source = {self.source}')
elif not self.all_fstype:
raise AppArmorException(f'Operation {flags_change_propagation & opt_cond.values} cannot specify a fstype. Fstype = {self.fstype}')
if self.operation == 'mount' and not self.all_options and flags_bind_mount & self.options != set() and not self.all_fstype:
raise AppArmorException(f'Bind mount rules cannot specify a fstype. Fstype = {self.fstype}')
if self.operation == 'mount' and not self.all_options and flags_bind_mount & opt_cond.values != set() and not self.all_fstype:
raise AppArmorException(f'Bind mount rules cannot specify a fstype. Fstype = {self.fstype}')
self.can_glob = not self.all_source and not self.all_dest and not self.all_options
@ -184,29 +197,19 @@ class MountRule(BaseRule):
if not r:
raise AppArmorException('Can\'t parse mount rule ' + raw_rule)
fstype = (None, cls.ALL)
if r['fstype'] is not None:
# mount rules with multiple 'fstype' are not supported by the tools yet, and when writing them, only the last 'fstype' would survive.
# Therefore raise an exception when parsing such a rule to prevent breaking the rule.
if RE_MOUNT_MULTIPLE_FS_TYPE.search(raw_rule):
raise AppArmorException("mount rules with multiple 'fstype' are not supported by the tools")
is_fstype_equal = r['fstype_equals_or_in']
fstype = parse_aare_list(strip_parenthesis(r['fstype']), 'fstype')
else:
is_fstype_equal = None
fstype = cls.ALL
fstype = []
for m in re.finditer(fs_type_pattern, rule_details):
fst = parse_aare_list(strip_parenthesis(m.group('fstype')), 'fstype')
fstype.append((m.group('fstype_equals_or_in'), fst))
opts = (None, cls.ALL)
if r['options'] is not None:
# mount rules with multiple 'options' are not supported by the tools yet, and when writing them, only the last 'options' would survive.
# Therefore raise an exception when parsing such a rule to prevent breaking the rule.
if RE_MOUNT_MULTIPLE_OPTIONS.search(raw_rule):
raise AppArmorException("mount rules with multiple 'options' are not supported by the tools")
is_options_equal = r['options_equals_or_in']
options = strip_parenthesis(r['options']).replace(',', ' ').split()
else:
is_options_equal = None
options = cls.ALL
opts = []
for m in re.finditer(option_pattern, rule_details):
options = strip_parenthesis(m.group('options')).replace(',', ' ').split()
opts.append((m.group('options_equals_or_in'), options))
if operation == 'mount' and r['source_file'] is not None: # Umount cannot have a source
source = strip_quotes(r['source_file'])
@ -219,22 +222,25 @@ class MountRule(BaseRule):
dest = cls.ALL
else:
is_fstype_equal = None
is_options_equal = None
fstype = cls.ALL
options = cls.ALL
opts = (None, cls.ALL)
fstype = (None, cls.ALL)
source = cls.ALL
dest = cls.ALL
return cls(operation=operation, fstype=(is_fstype_equal, fstype), options=(is_options_equal, options),
return cls(operation=operation, fstype=fstype, options=opts,
source=source, dest=dest, audit=audit, deny=deny, allow_keyword=allow_keyword, comment=comment,
priority=priority)
def get_clean(self, depth=0):
space = ' ' * depth
fstype = ' fstype%s(%s)' % (wrap_in_with_spaces(self.is_fstype_equal), ', '.join(sorted(self.fstype))) if not self.all_fstype else ''
options = ' options%s(%s)' % (wrap_in_with_spaces(self.is_options_equal), ', '.join(sorted(self.options))) if not self.all_options else ''
fstype = ''
for fst in self.fstype:
fstype += fst.get_clean()
options = ''
for opt in self.options:
options += opt.get_clean()
source = ''
dest = ''
@ -263,23 +269,20 @@ class MountRule(BaseRule):
self.comment,
))
def _is_cond_list_covered(self, conds, other_conds):
'''Checks if all conds in 'other_conds' are covered by at
least one cond in 'conds'.'''
return all(
any(cond.is_covered(other_cond) for cond in conds)
for other_cond in other_conds
)
def _is_covered_localvars(self, other_rule):
if self.operation != other_rule.operation:
return False
if self.is_fstype_equal != other_rule.is_fstype_equal:
if not self._is_cond_list_covered(self.fstype, other_rule.fstype):
return False
if self.is_options_equal != other_rule.is_options_equal:
return False
for o_it in other_rule.fstype or []:
found = False
for s_it in self.fstype or []:
if self._is_covered_aare(AARE(s_it, False), self.all_fstype, AARE(o_it, False), other_rule.all_fstype, 'fstype'):
found = True
if not found:
return False
if not self._is_covered_list(self.options, self.all_options, other_rule.options, other_rule.all_options, 'options'):
if not self._is_cond_list_covered(self.options, other_rule.options):
return False
if not self._is_covered_aare(self.source, self.all_source, other_rule.source, other_rule.all_source, 'source'):
return False
@ -291,10 +294,6 @@ class MountRule(BaseRule):
def _is_equal_localvars(self, rule_obj, strict):
if self.operation != rule_obj.operation:
return False
if self.is_fstype_equal != rule_obj.is_fstype_equal:
return False
if self.is_options_equal != rule_obj.is_options_equal:
return False
if self.fstype != rule_obj.fstype or self.options != rule_obj.options:
return False
if not self._is_equal_aare(self.source, self.all_source, rule_obj.source, rule_obj.all_source, 'source'):
@ -339,21 +338,29 @@ class MountRule(BaseRule):
self.source = self.ALL
else:
self.options = self.ALL
self.options = [MountConditional('options', self.ALL, True, None)]
self.all_options = True
self.raw_rule = None
def _logprof_header_localvars(self):
operation = self.operation
fstype = logprof_value_or_all(self.fstype, self.all_fstype)
options = logprof_value_or_all(self.options, self.all_options)
fstype_output = ()
for fst in self.fstype:
fstype = logprof_value_or_all(fst.values, fst.all_values)
fstype_output = fstype_output + (_('Fstype'), (fst.operator, fstype) if fstype != 'ALL' else fstype)
opts_output = ()
for opt in self.options:
options = logprof_value_or_all(opt.values, opt.all_values)
opts_output = opts_output + (_('Options'), (opt.operator, options) if options != 'ALL' else options)
source = logprof_value_or_all(self.source, self.all_source)
dest = logprof_value_or_all(self.dest, self.all_dest)
return (
_('Operation'), operation,
_('Fstype'), (self.is_fstype_equal, fstype) if fstype != 'ALL' else fstype,
_('Options'), (self.is_options_equal, options) if options != 'ALL' else options,
*fstype_output,
*opts_output,
_('Source'), source,
_('Destination'), dest,
@ -402,3 +409,72 @@ def wrap_in_with_spaces(value):
value = ' in '
return value
class MountConditional(MountRule):
'''Class to handle and store mount conditionals'''
def __init__(self, name, values, all_values, operator, cond_type=None):
self.name = name
self.values = values
self.all_values = all_values
self.operator = operator
self.cond_type = cond_type
self.raw_rule = '' # needed so __repr__ calls get_clean
if not self.all_values and self.operator not in ('=', 'in'):
raise AppArmorBug(f'Invalid operator for {self.name}: {self.operator}')
def __eq__(self, other: object) -> bool:
if not isinstance(other, MountConditional):
return False
if self.all_values != other.all_values:
return False
if self.cond_type != other.cond_type:
return False
if self.values != other.values:
return False
if self.operator != other.operator:
return False
return True
def is_covered(self, other: object) -> bool:
if not isinstance(other, MountConditional):
return False
if self.name != other.name:
return False
if self.all_values:
return True
if other.all_values:
return False
if self.cond_type == 'list':
if not self._is_covered_list(self.values, self.all_values, other.values, other.all_values, self.name):
return False
elif self.cond_type == 'aare': # list of aares - all values in other must be at least once in self.values
if not all(any(self._is_covered_aare(AARE(value, False), self.all_values,
AARE(other_value, False), other.all_values, self.name)
for value in self.values)
for other_value in other.values):
return False
else:
raise AppArmorBug('Type should only be empty if ALL is true')
if self.operator == other.operator:
return True
return False
def get_clean(self, depth=0) -> str:
conditional = ''
if not self.all_values:
conditional += ' %s%s(%s)' % (self.name, wrap_in_with_spaces(self.operator), ', '.join(sorted(self.values)))
return conditional

View File

@ -20,7 +20,7 @@ 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
from apparmor.rule.mount import MountRule, MountConditional
_ = init_translation()
@ -61,6 +61,33 @@ class MountTestParse(AATest):
False, False, False, '')), # noqa: E127
('mount options=(runbindable, rw) -> /,', MountRule('mount', MountRule.ALL, ('=', ['runbindable', 'rw']), MountRule.ALL, '/', False, False, False, '')),
('mount "" -> /,', MountRule('mount', MountRule.ALL, MountRule.ALL, '', '/', False, False, False, '')),
('mount options=(ro) options=(rw) fstype=ext4 -> /dest,', MountRule('mount', ('=', ['ext4']), [('=', ('ro')), ('=', ('rw'))], # noqa: E127
MountRule.ALL, '/dest', False, False, False, '')), # noqa: E127
('mount options=(ro) fstype=ext4 options=(rw) /src -> /dest,', MountRule('mount', ('=', ['ext4']), [('=', ('ro')), ('=', ('rw'))], # noqa: E127
'/src', '/dest', False, False, False, '')), # noqa: E127
('mount options in (ro) options in (rw) fstype=ext4 -> /dest,', MountRule('mount', ('=', ['ext4']), [('in', ('ro')), ('in', ('rw'))], # noqa: E127
MountRule.ALL, '/dest', False, False, False, '')), # noqa: E127
('mount options in (ro) fstype=ext4 options in (rw) -> /dest,', MountRule('mount', ('=', ['ext4']), [('in', ('ro')), ('in', ('rw'))], # noqa: E127
MountRule.ALL, '/dest', False, False, False, '')), # noqa: E127
('mount options = (ro) options in (rw) fstype=ext4 -> /dest,', MountRule('mount', ('=', ['ext4']), [('=', ('ro')), ('in', ('rw'))], # noqa: E127
MountRule.ALL, '/dest', False, False, False, '')), # noqa: E127
('mount options = (ro) fstype=ext4 options in (rw) -> /dest,', MountRule('mount', ('=', ['ext4']), [('=', ('ro')), ('in', ('rw'))], # noqa: E127
MountRule.ALL, '/dest', False, False, False, '')), # noqa: E127
('mount options=(ro) fstype=ext3 fstype=ext4 -> /dest,', MountRule('mount', [('=', ['ext3']), ('=', ['ext4'])],
('=', ('ro')), MountRule.ALL, '/dest', False, False, False, '')), # noqa: E127
('mount fstype=ext3 options=(ro) fstype=ext4 src -> /dest,', MountRule('mount', [('=', ['ext3']), ('=', ['ext4'])],
('=', ('ro')), 'src', '/dest', False, False, False, '')), # noqa: E127
('mount options=(ro) fstype in (ext3) fstype in (ext4) -> /dest,', MountRule('mount', [('in', ['ext3']), ('in', ['ext4'])],
('=', ('ro')), MountRule.ALL, '/dest', False, False, False, '')), # noqa: E127
('mount fstype in (ext3) options=(ro) fstype in (ext4) -> /dest,', MountRule('mount', [('in', ['ext3']), ('in', ['ext4'])],
('=', ('ro')), MountRule.ALL, '/dest', False, False, False, '')), # noqa: E127
('mount options=(ro) fstype in (ext3) fstype=(ext4) -> /dest,', MountRule('mount', [('in', ['ext3']), ('=', ['ext4'])],
('=', ('ro')), MountRule.ALL, '/dest', False, False, False, '')), # noqa: E127
('mount fstype = (ext3) options=(ro) fstype in ext4 -> /dest,', MountRule('mount', [('=', ['ext3']), ('in', ['ext4'])],
('=', ('ro')), MountRule.ALL, '/dest', 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, '')),
@ -95,22 +122,6 @@ class MountTestParseInvalid(AATest):
('mount options=(),', AppArmorException),
('mount option=(invalid),', AppArmorException),
('mount option=(ext3ext4),', AppArmorException),
# mount rules with multiple 'options' are not supported by the tools yet, and when writing them, only the last 'options' would survive. Therefore MountRule intentionally raises an exception when parsing such a rule.
('mount options=(ro) options=(rw) fstype=ext4 -> /destination,', AppArmorException),
('mount options=(ro) fstype=ext4 options=(rw) -> /destination,', AppArmorException),
('mount options in (ro) options in (rw) fstype=ext4 -> /destination,', AppArmorException),
('mount options in (ro) fstype=ext4 options in (rw) -> /destination,', AppArmorException),
('mount options = (ro) options in (rw) fstype=ext4 -> /destination,', AppArmorException),
('mount options = (ro) fstype=ext4 options in (rw) -> /destination,', AppArmorException),
# mount rules with multiple 'fstype' are not supported by the tools yet, and when writing them, only the last 'fstype' would survive. Therefore MountRule intentionally raises an exception when parsing such a rule.
('mount options=(ro) fstype=ext3 fstype=ext4 -> /destination,', AppArmorException),
('mount fstype=ext3 options=(ro) fstype=ext4 -> /destination,', AppArmorException),
('mount options=(ro) fstype in (ext3) fstype in (ext4) -> /destination,', AppArmorException),
('mount fstype in (ext3) options=(ro) fstype in (ext4) -> /destination,', AppArmorException),
('mount options=(ro) fstype in (ext3) fstype=(ext4) -> /destination,', AppArmorException),
('mount fstype in (ext3) options=(ro) fstype=ext4 -> /destination,', AppArmorException),
)
def _run_test(self, rawrule, expected):
@ -192,6 +203,12 @@ class MountTestParseInvalid(AATest):
with self.assertRaises(AppArmorException):
MountRule('mount', ('=', ('ext4')), ('=', ('bind')), MountRule.ALL, '/foo')
def test_invalid_cond_type(self):
# cond_type cannot be None if all_values is False
with self.assertRaises(AppArmorBug):
obj = MountConditional('name', [], False, '=', cond_type=None)
obj.is_covered(obj)
class MountTestGlob(AATest):
def test_glob(self):
@ -264,8 +281,9 @@ 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')
('mount', ('=', ['ext3', 'ext4']), ('=', ('ro')), '/foo/b', '/bar'),
('mount', ('=', ['ext3', 'ext4']), ('=', ('ro')), '/foo/bar', '/b'),
('mount', ('=', ['ext3', 'ext4']), [('=', ('ro'))], '/foo/bar', '/b')
]
for test in tests:
self.assertTrue(obj.is_covered(MountRule(*test)))
@ -296,19 +314,73 @@ class MountIsCoveredTest(AATest):
self.assertTrue(obj.is_covered(MountRule(*test)))
self.assertFalse(obj.is_equal(MountRule(*test)))
def test_is_covered_options_diff_operator(self):
obj = MountRule('mount', ('=', ('ext3', 'ext4')), ('=', ('ro')), '/foo/b*', '/b*')
test_obj = MountRule('mount', ('=', ('ext3', 'ext4')), ('in', ('ro')), '/foo/b*', '/b*')
self.assertFalse(obj.is_covered(test_obj), '\n' + test_obj.get_clean() + '\n should not be covered by\n' + obj.get_clean())
def test_is_covered_options_all(self):
obj = MountRule('mount', ('=', ('ext3', 'ext4')), MountRule.ALL, '/foo/b*', '/b*')
test_obj = MountRule('mount', ('=', ('ext3', 'ext4')), ('in', ('ro')), '/foo/b*', '/b*')
self.assertTrue(obj.is_covered(test_obj), '\n' + test_obj.get_clean() + '\n should be covered by\n' + obj.get_clean())
def test_is_covered_options_true(self):
obj = MountRule('mount', ('=', ('ext3', 'ext4')), [('in', ('ro')), ('=', ('rw', 'noexec')), ('in', ('ro', 'nosuid'))], '/foo/b*', '/b*')
test_obj = MountRule('mount', ('=', ('ext3', 'ext4')), [('=', ('rw', 'noexec')), ('in', ('ro', 'nosuid'))], '/foo/b*', '/b*')
self.assertTrue(obj.is_covered(test_obj), '\n' + test_obj.get_clean() + '\n should be covered by\n' + obj.get_clean())
def test_is_equal_options_diff_operator(self):
obj = MountRule('mount', ('=', ('ext3', 'ext4')), ('=', ('ro')), '/foo/b*', '/b*')
test_obj = MountRule('mount', ('=', ('ext3', 'ext4')), ('in', ('ro')), '/foo/b*', '/b*')
self.assertFalse(obj.is_equal(test_obj, True), '\n' + test_obj.get_clean() + '\n should not be equal to\n' + obj.get_clean())
def test_is_equal_options_diff_options(self):
obj = MountRule('mount', ('=', ('ext3', 'ext4')), [('in', ('ro')), ('=', ('nosuid', 'noexec'))], '/foo/b*', '/b*')
test_obj = MountRule('mount', ('=', ('ext3', 'ext4')), ('in', ('ro')), '/foo/b*', '/b*')
self.assertFalse(obj.is_equal(test_obj, True), '\n' + test_obj.get_clean() + '\n should not be equal to\n' + obj.get_clean())
def test_is_equal_options_true(self):
obj = MountRule('mount', ('=', ('ext3', 'ext4')), [('in', ('ro')), ('=', ('nosuid', 'noexec'))], '/foo/b*', '/b*')
test_obj = MountRule('mount', ('=', ('ext3', 'ext4')), [('in', ('ro')), ('=', ('nosuid', 'noexec'))], '/foo/b*', '/b*')
self.assertTrue(obj.is_equal(test_obj, True), '\n' + test_obj.get_clean() + '\n should be equal to\n' + obj.get_clean())
def test_eq_cond_diff_type(self):
obj = MountConditional('options', ['ro'], False, 'in', 'aare')
test_obj = MountConditional('options', ['ro'], False, 'in', 'list')
self.assertFalse(test_obj == obj, 'cond_types should be different')
def test_eq_cond_diff_instance(self):
obj = MountRule('mount', ('=', ('ext3', 'ext4')), [('in', ('ro')), ('=', ('nosuid', 'noexec'))], '/foo/b*', '/b*')
test_obj = MountConditional('options', ['ro'], False, 'in', 'list')
self.assertFalse(test_obj == obj, 'Incompatible comparison')
def test_covered_cond_diff_instance(self):
obj = MountRule('mount', ('=', ('ext3', 'ext4')), [('in', ('ro')), ('=', ('nosuid', 'noexec'))], '/foo/b*', '/b*')
test_obj = MountConditional('options', ['ro'], False, 'in', 'list')
self.assertFalse(test_obj.is_covered(obj), 'Incompatible comparison')
def test_covered_cond_diff_name(self):
obj = MountConditional('foo', ['ro'], False, '=', 'list')
test_obj = MountConditional('options', ['ro'], False, '=', 'list')
self.assertFalse(test_obj.is_covered(obj), 'Incompatible comparison')
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', ('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'),
('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'),
('mount', ('=', ['ext3', 'ext4']), ('=', ('ro')), 'tmpfs', '/bar'),
('mount', ('=', ['ext3', 'ext4']), ('=', ('ro')), '/foo/b*', '/invalid'),
('mount', ('=', ['ext3']), ('in', ('ro')), '/foo/bar', '/bar'),
('mount', ('=', ['ext3']), [('in', ('ro')), ('=', ('rw'))], '/foo/bar', '/bar'),
('mount', ('=', ['ext3']), [('=', ('ro')), ('in', ('rw'))], '/foo/bar', '/bar'),
('mount', ('=', ['ext3']), [('=', ('ro', 'nosuid')), ('in', ('rw'))], '/foo/bar', '/bar'),
]
for test in tests:
self.assertFalse(obj.is_covered(MountRule(*test)))

View File

@ -440,11 +440,6 @@ syntax_failure = (
'file/priority/ok_quoted_4.sd', # quoted string including \"
'file/priority/ok_embedded_spaces_4.sd', # \-escaped space
# mount rules with multiple 'options' or 'fstype' are not supported by the tools yet, and when writing them, only the last 'options'/'fstype' would survive.
# Therefore MountRule intentionally raises an exception when parsing such a rule.
'mount/ok_opt_87.sd', # multiple options
'mount/ok_opt_88.sd', # multiple fstype
# misc
'vars/vars_dbus_12.sd', # AARE starting with {{ are not handled
'vars/vars_simple_assignment_12.sd', # Redefining existing variable @{BAR} ('\' not handled)