mirror of
https://gitlab.com/apparmor/apparmor
synced 2025-09-06 01:05:11 +00:00
utils: add support for multiple options in mount rules
The tools don't support having multiple options specified in mount rules as it is allowed in the parser. Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
This commit is contained in:
@@ -32,11 +32,12 @@ flags_change_propagation = {
|
|||||||
'make-rslave'
|
'make-rslave'
|
||||||
}
|
}
|
||||||
# keep in sync with parser/mount.cc mnt_opts_table!
|
# 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) + [
|
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',
|
'nomand', 'dirsync', 'symfollow', 'nosymfollow', 'atime', 'noatime', 'diratime', 'nodiratime', 'move', 'M',
|
||||||
'verbose', 'silent', 'loud', 'acl', 'noacl', 'relatime', 'norelatime', 'iversion', 'noiversion', 'strictatime',
|
'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)
|
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*'\
|
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'(?P<options>\(\s*(' + join_valid_flags + r')(' + sep + r'(' + join_valid_flags + r'))*\s*\)|' \
|
||||||
r'(\s*' + join_valid_flags + r')'\
|
r'(\s*' + join_valid_flags + r')'\
|
||||||
r'))?'
|
r'))'
|
||||||
|
|
||||||
# allow any order of fstype and options
|
# allow any order of fstype and options
|
||||||
# Note: also matches if multiple fstype= or options= are given to keep the regex simpler
|
# 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
|
# Source can either be
|
||||||
# - A path : /foo
|
# - 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_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'$')
|
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'
|
# check if a rule contains multiple '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)
|
# (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)'
|
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'))
|
RE_MOUNT_MULTIPLE_FS_TYPE = re.compile(multi_param_template.replace('PARAM', 'v?fstype'))
|
||||||
|
|
||||||
|
|
||||||
@@ -137,29 +137,36 @@ class MountRule(BaseRule):
|
|||||||
self.fstype = fstype[1]
|
self.fstype = fstype[1]
|
||||||
self.is_fstype_equal = fstype[0]
|
self.is_fstype_equal = fstype[0]
|
||||||
|
|
||||||
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 not isinstance(options, list):
|
||||||
if unknown_items:
|
options = [options]
|
||||||
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_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))
|
||||||
|
|
||||||
self.source, self.all_source = self._aare_or_all(source, 'source', is_path=False, log_event=log_event, empty_ok=True)
|
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)
|
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'):
|
if not self.all_fstype and self.is_fstype_equal not in ('=', 'in'):
|
||||||
raise AppArmorBug(f'Invalid is_fstype_equal : {self.is_fstype_equal}')
|
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:
|
if self.operation != 'mount' and not self.all_source:
|
||||||
raise AppArmorException(f'Operation {self.operation} cannot have a 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():
|
for opt_cond in self.options:
|
||||||
if not (self.all_source or self.all_dest):
|
if self.operation == 'mount' and not self.all_options and flags_change_propagation & opt_cond.values != set():
|
||||||
raise AppArmorException(f'Operation {flags_change_propagation & self.options} cannot specify a source. Source = {self.source}')
|
if not (self.all_source or self.all_dest):
|
||||||
elif not self.all_fstype:
|
raise AppArmorException(f'Operation {flags_change_propagation & opt_cond.values} cannot specify a source. Source = {self.source}')
|
||||||
raise AppArmorException(f'Operation {flags_change_propagation & self.options} cannot specify a fstype. Fstype = {self.fstype}')
|
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:
|
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}')
|
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
|
self.can_glob = not self.all_source and not self.all_dest and not self.all_options
|
||||||
|
|
||||||
@@ -196,17 +203,12 @@ class MountRule(BaseRule):
|
|||||||
is_fstype_equal = None
|
is_fstype_equal = None
|
||||||
fstype = cls.ALL
|
fstype = cls.ALL
|
||||||
|
|
||||||
|
opts = (None, cls.ALL)
|
||||||
if r['options'] is not None:
|
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.
|
opts = []
|
||||||
# Therefore raise an exception when parsing such a rule to prevent breaking the rule.
|
for m in re.finditer(option_pattern, rule_details):
|
||||||
if RE_MOUNT_MULTIPLE_OPTIONS.search(raw_rule):
|
options = strip_parenthesis(m.group('options')).replace(',', ' ').split()
|
||||||
raise AppArmorException("mount rules with multiple 'options' are not supported by the tools")
|
opts.append((m.group('options_equals_or_in'), options))
|
||||||
|
|
||||||
is_options_equal = r['options_equals_or_in']
|
|
||||||
options = strip_parenthesis(r['options']).replace(',', ' ').split()
|
|
||||||
else:
|
|
||||||
is_options_equal = None
|
|
||||||
options = cls.ALL
|
|
||||||
|
|
||||||
if operation == 'mount' and r['source_file'] is not None: # Umount cannot have a source
|
if operation == 'mount' and r['source_file'] is not None: # Umount cannot have a source
|
||||||
source = strip_quotes(r['source_file'])
|
source = strip_quotes(r['source_file'])
|
||||||
@@ -219,14 +221,13 @@ class MountRule(BaseRule):
|
|||||||
dest = cls.ALL
|
dest = cls.ALL
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
opts = (None, cls.ALL)
|
||||||
is_fstype_equal = None
|
is_fstype_equal = None
|
||||||
is_options_equal = None
|
|
||||||
fstype = cls.ALL
|
fstype = cls.ALL
|
||||||
options = cls.ALL
|
|
||||||
source = cls.ALL
|
source = cls.ALL
|
||||||
dest = cls.ALL
|
dest = cls.ALL
|
||||||
|
|
||||||
return cls(operation=operation, fstype=(is_fstype_equal, fstype), options=(is_options_equal, options),
|
return cls(operation=operation, fstype=(is_fstype_equal, fstype), options=opts,
|
||||||
source=source, dest=dest, audit=audit, deny=deny, allow_keyword=allow_keyword, comment=comment,
|
source=source, dest=dest, audit=audit, deny=deny, allow_keyword=allow_keyword, comment=comment,
|
||||||
priority=priority)
|
priority=priority)
|
||||||
|
|
||||||
@@ -234,7 +235,9 @@ class MountRule(BaseRule):
|
|||||||
space = ' ' * depth
|
space = ' ' * depth
|
||||||
|
|
||||||
fstype = ' fstype%s(%s)' % (wrap_in_with_spaces(self.is_fstype_equal), ', '.join(sorted(self.fstype))) if not self.all_fstype else ''
|
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 ''
|
options = ''
|
||||||
|
for opt in self.options:
|
||||||
|
options += opt.get_clean()
|
||||||
|
|
||||||
source = ''
|
source = ''
|
||||||
dest = ''
|
dest = ''
|
||||||
@@ -263,13 +266,19 @@ class MountRule(BaseRule):
|
|||||||
self.comment,
|
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):
|
def _is_covered_localvars(self, other_rule):
|
||||||
if self.operation != other_rule.operation:
|
if self.operation != other_rule.operation:
|
||||||
return False
|
return False
|
||||||
if self.is_fstype_equal != other_rule.is_fstype_equal:
|
if self.is_fstype_equal != other_rule.is_fstype_equal:
|
||||||
return False
|
return False
|
||||||
if self.is_options_equal != other_rule.is_options_equal:
|
|
||||||
return False
|
|
||||||
|
|
||||||
for o_it in other_rule.fstype or []:
|
for o_it in other_rule.fstype or []:
|
||||||
found = False
|
found = False
|
||||||
@@ -279,7 +288,7 @@ class MountRule(BaseRule):
|
|||||||
|
|
||||||
if not found:
|
if not found:
|
||||||
return False
|
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
|
return False
|
||||||
if not self._is_covered_aare(self.source, self.all_source, other_rule.source, other_rule.all_source, 'source'):
|
if not self._is_covered_aare(self.source, self.all_source, other_rule.source, other_rule.all_source, 'source'):
|
||||||
return False
|
return False
|
||||||
@@ -293,8 +302,6 @@ class MountRule(BaseRule):
|
|||||||
return False
|
return False
|
||||||
if self.is_fstype_equal != rule_obj.is_fstype_equal:
|
if self.is_fstype_equal != rule_obj.is_fstype_equal:
|
||||||
return False
|
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:
|
if self.fstype != rule_obj.fstype or self.options != rule_obj.options:
|
||||||
return False
|
return False
|
||||||
if not self._is_equal_aare(self.source, self.all_source, rule_obj.source, rule_obj.all_source, 'source'):
|
if not self._is_equal_aare(self.source, self.all_source, rule_obj.source, rule_obj.all_source, 'source'):
|
||||||
@@ -339,21 +346,24 @@ class MountRule(BaseRule):
|
|||||||
self.source = self.ALL
|
self.source = self.ALL
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.options = self.ALL
|
self.options = [MountConditional('options', self.ALL, True, None)]
|
||||||
self.all_options = True
|
self.all_options = True
|
||||||
self.raw_rule = None
|
self.raw_rule = None
|
||||||
|
|
||||||
def _logprof_header_localvars(self):
|
def _logprof_header_localvars(self):
|
||||||
operation = self.operation
|
operation = self.operation
|
||||||
fstype = logprof_value_or_all(self.fstype, self.all_fstype)
|
fstype = logprof_value_or_all(self.fstype, self.all_fstype)
|
||||||
options = logprof_value_or_all(self.options, self.all_options)
|
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)
|
source = logprof_value_or_all(self.source, self.all_source)
|
||||||
dest = logprof_value_or_all(self.dest, self.all_dest)
|
dest = logprof_value_or_all(self.dest, self.all_dest)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
_('Operation'), operation,
|
_('Operation'), operation,
|
||||||
_('Fstype'), (self.is_fstype_equal, fstype) if fstype != 'ALL' else fstype,
|
_('Fstype'), (self.is_fstype_equal, fstype) if fstype != 'ALL' else fstype,
|
||||||
_('Options'), (self.is_options_equal, options) if options != 'ALL' else options,
|
*opts_output,
|
||||||
_('Source'), source,
|
_('Source'), source,
|
||||||
_('Destination'), dest,
|
_('Destination'), dest,
|
||||||
|
|
||||||
@@ -402,3 +412,53 @@ def wrap_in_with_spaces(value):
|
|||||||
value = ' in '
|
value = ' in '
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
class MountConditional(MountRule):
|
||||||
|
'''Class to handle and store mount conditionals'''
|
||||||
|
def __init__(self, name, values, all_values, operator):
|
||||||
|
self.name = name
|
||||||
|
self.values = values
|
||||||
|
self.all_values = all_values
|
||||||
|
self.operator = operator
|
||||||
|
|
||||||
|
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.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 self._is_covered_list(self.values, self.all_values, other.values, other.all_values, self.name):
|
||||||
|
if self.operator == other.operator:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_clean(self) -> 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
|
||||||
|
@@ -20,7 +20,7 @@ from common_test import AATest, setup_all_loops
|
|||||||
from apparmor.common import AppArmorException, AppArmorBug
|
from apparmor.common import AppArmorException, AppArmorBug
|
||||||
from apparmor.translations import init_translation
|
from apparmor.translations import init_translation
|
||||||
|
|
||||||
from apparmor.rule.mount import MountRule
|
from apparmor.rule.mount import MountRule, MountConditional
|
||||||
|
|
||||||
_ = init_translation()
|
_ = init_translation()
|
||||||
|
|
||||||
@@ -61,6 +61,20 @@ class MountTestParse(AATest):
|
|||||||
False, False, False, '')), # noqa: E127
|
False, False, False, '')), # noqa: E127
|
||||||
('mount options=(runbindable, rw) -> /,', MountRule('mount', MountRule.ALL, ('=', ['runbindable', 'rw']), MountRule.ALL, '/', False, False, False, '')),
|
('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 "" -> /,', 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
|
||||||
|
|
||||||
('umount,', MountRule('umount', MountRule.ALL, MountRule.ALL, MountRule.ALL, MountRule.ALL, False, False, False, '')),
|
('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 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, '')),
|
('umount /a,', MountRule('umount', MountRule.ALL, MountRule.ALL, MountRule.ALL, '/a', False, False, False, '')),
|
||||||
@@ -96,14 +110,6 @@ class MountTestParseInvalid(AATest):
|
|||||||
('mount option=(invalid),', AppArmorException),
|
('mount option=(invalid),', AppArmorException),
|
||||||
('mount option=(ext3ext4),', 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 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 options=(ro) fstype=ext3 fstype=ext4 -> /destination,', AppArmorException),
|
||||||
('mount fstype=ext3 options=(ro) fstype=ext4 -> /destination,', AppArmorException),
|
('mount fstype=ext3 options=(ro) fstype=ext4 -> /destination,', AppArmorException),
|
||||||
@@ -264,8 +270,9 @@ class MountIsCoveredTest(AATest):
|
|||||||
def test_is_covered(self):
|
def test_is_covered(self):
|
||||||
obj = MountRule('mount', ('=', ('ext3', 'ext4')), ('=', ('ro')), '/foo/b*', '/b*')
|
obj = MountRule('mount', ('=', ('ext3', 'ext4')), ('=', ('ro')), '/foo/b*', '/b*')
|
||||||
tests = [
|
tests = [
|
||||||
('mount', ('=', ['ext3', 'ext4']), ('=', ('ro')), '/foo/b', '/bar'),
|
('mount', ('=', ['ext3', 'ext4']), ('=', ('ro')), '/foo/b', '/bar'),
|
||||||
('mount', ('=', ['ext3', 'ext4']), ('=', ('ro')), '/foo/bar', '/b')
|
('mount', ('=', ['ext3', 'ext4']), ('=', ('ro')), '/foo/bar', '/b'),
|
||||||
|
('mount', ('=', ['ext3', 'ext4']), [('=', ('ro'))], '/foo/bar', '/b')
|
||||||
]
|
]
|
||||||
for test in tests:
|
for test in tests:
|
||||||
self.assertTrue(obj.is_covered(MountRule(*test)))
|
self.assertTrue(obj.is_covered(MountRule(*test)))
|
||||||
@@ -296,19 +303,68 @@ class MountIsCoveredTest(AATest):
|
|||||||
self.assertTrue(obj.is_covered(MountRule(*test)))
|
self.assertTrue(obj.is_covered(MountRule(*test)))
|
||||||
self.assertFalse(obj.is_equal(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_instance(self):
|
||||||
|
obj = MountRule('mount', ('=', ('ext3', 'ext4')), [('in', ('ro')), ('=', ('nosuid', 'noexec'))], '/foo/b*', '/b*')
|
||||||
|
test_obj = MountConditional('options', ['ro'], False, 'in')
|
||||||
|
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')
|
||||||
|
self.assertFalse(test_obj.is_covered(obj), 'Incompatible comparison')
|
||||||
|
|
||||||
|
def test_covered_cond_diff_name(self):
|
||||||
|
obj = MountConditional('foo', ['ro'], False, '=')
|
||||||
|
test_obj = MountConditional('options', ['ro'], False, '=')
|
||||||
|
self.assertFalse(test_obj.is_covered(obj), 'Incompatible comparison')
|
||||||
|
|
||||||
def test_is_notcovered(self):
|
def test_is_notcovered(self):
|
||||||
obj = MountRule('mount', ('=', ['ext3', 'ext4']), ('=', ('ro')), '/foo/b*', '/b*')
|
obj = MountRule('mount', ('=', ['ext3', 'ext4']), ('=', ('ro')), '/foo/b*', '/b*')
|
||||||
tests = [
|
tests = [
|
||||||
('mount', ('in', ['ext3', 'ext4']), ('=', ('ro')), '/foo/bar', '/bar'),
|
('mount', ('in', ['ext3', 'ext4']), ('=', ('ro')), '/foo/bar', '/bar'),
|
||||||
('mount', ('=', ['procfs', 'ext4']), ('=', ('ro')), '/foo/bar', '/bar'),
|
('mount', ('=', ['procfs', 'ext4']), ('=', ('ro')), '/foo/bar', '/bar'),
|
||||||
('mount', ('=', ['ext3']), ('=', ('rw')), '/foo/bar', '/bar'),
|
('mount', ('=', ['ext3']), ('=', ('rw')), '/foo/bar', '/bar'),
|
||||||
('mount', ('=', ['ext3', 'ext4']), MountRule.ALL, '/foo/b*', '/bar'),
|
('mount', ('=', ['ext3', 'ext4']), MountRule.ALL, '/foo/b*', '/bar'),
|
||||||
('mount', MountRule.ALL, ('=', ('ro')), '/foo/b*', '/bar'),
|
('mount', MountRule.ALL, ('=', ('ro')), '/foo/b*', '/bar'),
|
||||||
('mount', ('=', ['ext3', 'ext4']), ('=', ('ro')), '/invalid/bar', '/bar'),
|
('mount', ('=', ['ext3', 'ext4']), ('=', ('ro')), '/invalid/bar', '/bar'),
|
||||||
('umount', MountRule.ALL, MountRule.ALL, MountRule.ALL, '/bar'),
|
('umount', MountRule.ALL, MountRule.ALL, MountRule.ALL, '/bar'),
|
||||||
('remount', 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')), 'tmpfs', '/bar'),
|
||||||
('mount', ('=', ['ext3', 'ext4']), ('=', ('ro')), '/foo/b*', '/invalid'),
|
('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:
|
for test in tests:
|
||||||
self.assertFalse(obj.is_covered(MountRule(*test)))
|
self.assertFalse(obj.is_covered(MountRule(*test)))
|
||||||
|
@@ -440,9 +440,8 @@ syntax_failure = (
|
|||||||
'file/priority/ok_quoted_4.sd', # quoted string including \"
|
'file/priority/ok_quoted_4.sd', # quoted string including \"
|
||||||
'file/priority/ok_embedded_spaces_4.sd', # \-escaped space
|
'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.
|
# 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.
|
# 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
|
'mount/ok_opt_88.sd', # multiple fstype
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
|
Reference in New Issue
Block a user