[apparmor] [PATCH] utils: Basic support for file prefix in path rules
Tyler Hicks
tyhicks at canonical.com
Thu Mar 27 02:02:47 UTC 2014
Bug: https://bugs.launchpad.net/bugs/1295346
Add the ability to read and write path rules containing the file prefix.
This also includes bare "file," rules.
The ALL global is updated to include a preceding NUL char to eliminate
possibilities of a real file path colliding with the ALL global.
Signed-off-by: Tyler Hicks <tyhicks at canonical.com>
---
utils/apparmor/aa.py | 109 ++++++++++++++++++++++++++++-----------
utils/test/test-regex_matches.py | 61 ++++++++++++++++++++++
2 files changed, 141 insertions(+), 29 deletions(-)
diff --git a/utils/apparmor/aa.py b/utils/apparmor/aa.py
index 03e28b1..f2bcac2 100644
--- a/utils/apparmor/aa.py
+++ b/utils/apparmor/aa.py
@@ -79,7 +79,7 @@ seen_events = 0 # was our
user_globs = []
# The key for representing bare rules such as "capability," or "file,"
-ALL = '_ALL'
+ALL = '\0ALL'
## Variables used under logprof
### Were our
@@ -2615,7 +2615,7 @@ RE_PROFILE_VARIABLE = re.compile('^\s*(@\{?\w+\}?)\s*(\+?=)\s*(@*.+?)\s*,?\s*(#.
RE_PROFILE_CONDITIONAL = re.compile('^\s*if\s+(not\s+)?(\$\{?\w*\}?)\s*\{\s*(#.*)?$')
RE_PROFILE_CONDITIONAL_VARIABLE = re.compile('^\s*if\s+(not\s+)?defined\s+(@\{?\w+\}?)\s*\{\s*(#.*)?$')
RE_PROFILE_CONDITIONAL_BOOLEAN = re.compile('^\s*if\s+(not\s+)?defined\s+(\$\{?\w+\}?)\s*\{\s*(#.*)?$')
-RE_PROFILE_PATH_ENTRY = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?(owner\s+)?([\"@/].*?)\s+(\S+)(\s+->\s*(.*?))?\s*,\s*(#.*)?$')
+RE_PROFILE_PATH_ENTRY = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?(owner\s+)?(file\s+|file(?=,))?([\"@/].*?\s+)?(\S+)?(\s+->\s*(.*?))?\s*,\s*(#.*)?$')
RE_PROFILE_NETWORK = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?network(.*)\s*(#.*)?$')
RE_PROFILE_CHANGE_HAT = re.compile('^\s*\^(\"??.+?\"??)\s*,\s*(#.*)?$')
RE_PROFILE_HAT_DEF = re.compile('^\s*\^(\"??.+?\"??)\s+((flags=)?\((.+)\)\s+)*\{\s*(#.*)?$')
@@ -2865,11 +2865,27 @@ def parse_profile_data(data, file, do_include):
if matches[2]:
user = True
- path = matches[3].strip()
- mode = matches[4]
- nt_name = matches[6]
- if nt_name:
- nt_name = nt_name.strip()
+ file_prefix = False
+ if matches[3]:
+ file_prefix = True
+
+ path = None
+ if matches[4]:
+ path = matches[4].strip()
+
+ mode = None
+ if matches[5]:
+ mode = matches[5]
+
+ nt_name = None
+ if matches[7]:
+ nt_name = matches[7].strip()
+
+ if file_prefix and not path and not mode and not nt_name:
+ path = ALL
+ elif (file_prefix and path and not mode) or \
+ (not file_prefix and (not path or not mode)):
+ raise AppArmorException(_('Syntax Error: Invalid path entry found in file: %s line: %s') % (file, lineno + 1))
p_re = convert_regexp(path)
try:
@@ -2877,17 +2893,21 @@ def parse_profile_data(data, file, do_include):
except:
raise AppArmorException(_('Syntax Error: Invalid Regex %s in file: %s line: %s') % (path, file, lineno + 1))
- if not validate_profile_mode(mode, allow, nt_name):
- raise AppArmorException(_('Invalid mode %s in file: %s line: %s') % (mode, file, lineno + 1))
-
tmpmode = set()
- if user:
- tmpmode = str_to_mode('%s::' % mode)
- else:
- tmpmode = str_to_mode(mode)
+ if mode:
+ if not validate_profile_mode(mode, allow, nt_name):
+ raise AppArmorException(_('Invalid mode %s in file: %s line: %s') % (mode, file, lineno + 1))
+
+ if user:
+ tmpmode = str_to_mode('%s::' % mode)
+ else:
+ tmpmode = str_to_mode(mode)
profile_data[profile][hat][allow]['path'][path]['mode'] = profile_data[profile][hat][allow]['path'][path].get('mode', set()) | tmpmode
+ if file_prefix:
+ profile_data[profile][hat][allow]['path'][path]['file_prefix'] = True
+
if nt_name:
profile_data[profile][hat][allow]['path'][path]['to'] = nt_name
@@ -3359,13 +3379,19 @@ def write_path_rules(prof_data, depth, allow):
if prof_data[allow].get('path', False):
for path in sorted(prof_data[allow]['path'].keys()):
+ filestr = ''
+ if prof_data[allow]['path'][path].get('file_prefix', False):
+ filestr = 'file '
mode = prof_data[allow]['path'][path]['mode']
audit = prof_data[allow]['path'][path]['audit']
tail = ''
if prof_data[allow]['path'][path].get('to', False):
tail = ' -> %s' % prof_data[allow]['path'][path]['to']
- user, other = split_mode(mode)
- user_audit, other_audit = split_mode(audit)
+ user = None
+ other = None
+ if mode or audit:
+ user, other = split_mode(mode)
+ user_audit, other_audit = split_mode(audit)
while user or other:
ownerstr = ''
@@ -3393,13 +3419,19 @@ def write_path_rules(prof_data, depth, allow):
if tmpmode & tmpaudit:
modestr = mode_to_str(tmpmode & tmpaudit)
path = quote_if_needed(path)
- data.append('%saudit %s%s%s %s%s,' % (pre, allowstr, ownerstr, path, modestr, tail))
+ data.append('%saudit %s%s%s%s %s%s,' % (pre, allowstr, ownerstr, filestr, path, modestr, tail))
tmpmode = tmpmode - tmpaudit
if tmpmode:
modestr = mode_to_str(tmpmode)
path = quote_if_needed(path)
- data.append('%s%s%s%s %s%s,' % (pre, allowstr, ownerstr, path, modestr, tail))
+ data.append('%s%s%s%s%s %s%s,' % (pre, allowstr, ownerstr, filestr, path, modestr, tail))
+
+ if filestr and path == ALL:
+ auditstr = ''
+ if audit == 0:
+ auditstr = 'audit '
+ data.append('%s%s%s%s%s,' % (pre, auditstr, allowstr, filestr, tail))
data.append('')
return data
@@ -3931,26 +3963,45 @@ def serialize_profile_from_old_profile(profile_data, name, options):
if matches[2]:
user = True
- path = matches[3].strip()
- mode = matches[4]
- nt_name = matches[6]
- if nt_name:
- nt_name = nt_name.strip()
+ file_prefix = False
+ if matches[3]:
+ file_prefix = True
+
+ path = None
+ if matches[4]:
+ path = matches[4].strip()
+
+ mode = None
+ if matches[5]:
+ mode = matches[5].strip()
+
+ nt_name = None
+ if matches[7]:
+ nt_name = matches[7].strip()
+
+ if file_prefix and not path and not mode and not nt_name:
+ path = ALL
+ elif (file_prefix and path and not mode) or \
+ (not file_prefix and (not path or not mode)):
+ correct = False
tmpmode = set()
- if user:
- tmpmode = str_to_mode('%s::' % mode)
- else:
- tmpmode = str_to_mode(mode)
+ if mode:
+ if user:
+ tmpmode = str_to_mode('%s::' % mode)
+ else:
+ tmpmode = str_to_mode(mode)
if not write_prof_data[hat][allow]['path'][path].get('mode', set()) & tmpmode:
- correct = False
+ if path != ALL:
+ correct = False
if nt_name and not write_prof_data[hat][allow]['path'][path].get('to', False) == nt_name:
correct = False
if audit and not write_prof_data[hat][allow]['path'][path].get('audit', set()) & tmpmode:
- correct = False
+ if path != ALL:
+ correct = False
if correct:
if not segments['path'] and True in segments.values():
diff --git a/utils/test/test-regex_matches.py b/utils/test/test-regex_matches.py
index 167196f..50ac69e 100644
--- a/utils/test/test-regex_matches.py
+++ b/utils/test/test-regex_matches.py
@@ -96,6 +96,9 @@ regex_split_comment_testcases = [
('dbus send member=no_comment, ', False),
('audit "/tmp/foo, # bar" rw', False),
('audit "/tmp/foo, # bar" rw # comment', ('audit "/tmp/foo, # bar" rw ', '# comment')),
+ ('file,', False),
+ ('file, # bare', ('file, ', '# bare')),
+ ('file /tmp/foo rw, # read-write', ('file /tmp/foo rw, ', '# read-write')),
]
def setup_split_comment_testcases():
@@ -154,6 +157,64 @@ class AARegexCapability(unittest.TestCase):
result = aa.RE_PROFILE_CAP.search(line)
self.assertFalse(result, 'Found unexpected capability rule in "%s"' % line)
+class AARegexPath(unittest.TestCase):
+ '''Tests for RE_PROFILE_PATH_ENTRY'''
+
+ def test_simple_path_01(self):
+ '''test ' /tmp/foo r,' '''
+
+ line = ' /tmp/foo r,'
+ result = aa.RE_PROFILE_CAP.search(line)
+ self.assertTrue(result, 'Couldn\'t find file rule in "%s"' % line)
+ mode = result.groups()[5].strip()
+ self.assertEqual(mode, 'r', 'Expected mode "r", got "%s"' % (mode))
+
+ def test_simple_path_02(self):
+ '''test ' audit /tmp/foo rw,' '''
+
+ line = ' audit /tmp/foo rw,'
+ result = aa.RE_PROFILE_CAP.search(line)
+ self.assertTrue(result, 'Couldn\'t find file rule in "%s"' % line)
+ audit = result.groups()[0].strip()
+ self.assertEqual(audit, 'audit', 'Couldn\t find audit modifier')
+ mode = result.groups()[5].strip()
+ self.assertEqual(mode, 'rw', 'Expected mode "rw", got "%s"' % (mode))
+
+ def test_simple_path_03(self):
+ '''test ' audit deny /tmp/foo rw,' '''
+
+ line = ' audit deny /tmp/foo rw,'
+ result = aa.RE_PROFILE_CAP.search(line)
+ self.assertTrue(result, 'Couldn\'t find file rule in "%s"' % line)
+ audit = result.groups()[0].strip()
+ self.assertEqual(audit, 'audit', 'Couldn\t find audit modifier')
+ deny = result.groups()[1].strip()
+ self.assertEqual(deny, 'deny', 'Couldn\t find deny modifier')
+ mode = result.groups()[5].strip()
+ self.assertEqual(mode, 'rw', 'Expected mode "rw", got "%s"' % (mode))
+
+ def test_simple_file_01(self):
+ '''test ' file /tmp/foo rw,' '''
+
+ line = ' file /tmp/foo rw,'
+ result = aa.RE_PROFILE_CAP.search(line)
+ self.assertTrue(result, 'Couldn\'t find file rule in "%s"' % line)
+ file_prefix = result.groups()[3].strip()
+ self.assertEqual(file_prefix, 'file', 'Couldn\t find file prefix')
+ mode = result.groups()[5].strip()
+ self.assertEqual(mode, 'rw', 'Expected mode "rw", got "%s"' % (mode))
+
+ def test_simple_file_02(self):
+ '''test ' file,' '''
+
+ line = ' file /tmp/foo rw,'
+ result = aa.RE_PROFILE_CAP.search(line)
+ self.assertTrue(result, 'Couldn\'t find file rule in "%s"' % line)
+ file_prefix = result.groups()[3].strip()
+ self.assertEqual(file_prefix, 'file', 'Couldn\t find file prefix')
+ mode = result.groups()[5]
+ self.assertEqual(mode, None, 'Unexpected mode, got "%s"' % (mode))
+
if __name__ == '__main__':
verbosity = 2
--
1.9.1
More information about the AppArmor
mailing list