From ba5e00728789f71e92acfb5275625921f4082dce Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Wed, 4 Jun 2025 22:42:34 +0200 Subject: [PATCH] Fix parsing of mount options to honor full words Parsing mount options (also) accepted partial matches as long as the option started with the right characters. For example, 'options=syncfoo' was parsed as 'sync'. This is also the reason why the list of mount options was re-ordered so that 'r' and 'w' came last to give longer options a chance to match (otherwise, 'rw' would be interpreted as 'r'). Fix parsing by adding a lookahead match so that the regex enforces that the mount option is followed by whitespace, or is at the end of rule_details. Note that this issue only affected the options=foo syntax. options=(foo) worked correctly even without this fix. Now that this is fixed, move 'r' and 'w' back to their original position in the list of mount options. Also add a test where a mount rule ends with 'options=rw,' to ensure that the '$' lookahead works. --- utils/apparmor/rule/mount.py | 7 +++---- utils/test/test-mount.py | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/utils/apparmor/rule/mount.py b/utils/apparmor/rule/mount.py index 12dd09052..337cda6c8 100644 --- a/utils/apparmor/rule/mount.py +++ b/utils/apparmor/rule/mount.py @@ -32,12 +32,11 @@ 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', 'read-only', 'rw', 'suid', 'nosuid', 'dev', 'nodev', 'exec', 'noexec', 'sync', 'async', 'mand', + 'ro', 'r', 'read-only', 'rw', 'w', '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', 'r', 'w', + 'nostrictatime', 'lazytime', 'nolazytime', 'user', 'nouser', '[A-Za-z0-9-]+', # as long as the parser uses a hardcoded options list, this only helps to print a better error message on unknown mount options ] join_valid_flags = '|'.join(flags_keywords) @@ -54,7 +53,7 @@ fs_type_pattern = r'\b(?Pfstype|vfstype)\b\s*(?P=|in)\s*'\ r'(?P\(\s*(' + join_valid_flags + r')(' + sep + r'(' + join_valid_flags + r'))*\s*\)|' \ - r'(\s*' + join_valid_flags + r')'\ + r'(\s*' + join_valid_flags + r')(?=(\s|$))'\ r'))' # allow any order of fstype and options diff --git a/utils/test/test-mount.py b/utils/test/test-mount.py index e5d6d1ca3..f6fd324da 100644 --- a/utils/test/test-mount.py +++ b/utils/test/test-mount.py @@ -31,6 +31,7 @@ class MountTestParse(AATest): # 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 options=rw,', MountRule('mount', MountRule.ALL, ('=', ('rw')), MountRule.ALL, 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, '')),