[apparmor] [PATCH 2/3] utils: Basic support for ptrace rules
Tyler Hicks
tyhicks at canonical.com
Thu Apr 3 19:56:00 UTC 2014
Bug: https://bugs.launchpad.net/bugs/1300317
This patch does bare bones parsing of ptrace rules and stores the raw
strings for writing them out later. It is meant to be a simple change to
prevent aa.py from emitting a traceback when encountering ptrace rules.
Signed-off-by: Tyler Hicks <tyhicks at canonical.com>
---
utils/apparmor/aa.py | 49 +++++++++++++++++++++++++
utils/apparmor/rules.py | 12 ++++++
utils/test/test-ptrace_parse.py | 54 +++++++++++++++++++++++++++
utils/test/test-regex_matches.py | 79 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 194 insertions(+)
create mode 100644 utils/test/test-ptrace_parse.py
diff --git a/utils/apparmor/aa.py b/utils/apparmor/aa.py
index 87eb7b0..8c1e374 100644
--- a/utils/apparmor/aa.py
+++ b/utils/apparmor/aa.py
@@ -2625,6 +2625,7 @@ RE_NETWORK_FAMILY = re.compile('\s+(\S+)\s*,$')
RE_PROFILE_DBUS = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?(dbus[^#]*\s*,)\s*(#.*)?$')
RE_PROFILE_MOUNT = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?((mount|remount|umount)[^#]*\s*,)\s*(#.*)?$')
RE_PROFILE_SIGNAL = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?((signal)[^#]*\s*,)\s*(#.*)?$')
+RE_PROFILE_PTRACE = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?((ptrace)[^#]*\s*,)\s*(#.*)?$')
# match anything that's not " or #, or matching quotes with anything except quotes inside
__re_no_or_quoted_hash = '([^#"]|"[^"]*")*'
@@ -2704,6 +2705,7 @@ def parse_profile_data(data, file, do_include):
profile_data[profile][hat]['allow']['dbus'] = list()
profile_data[profile][hat]['allow']['mount'] = list()
profile_data[profile][hat]['allow']['signal'] = list()
+ profile_data[profile][hat]['allow']['ptrace'] = list()
# Save the initial comment
if initial_comment:
profile_data[profile][hat]['initial_comment'] = initial_comment
@@ -3086,6 +3088,28 @@ def parse_profile_data(data, file, do_include):
signal_rules.append(signal_rule)
profile_data[profile][hat][allow]['signal'] = signal_rules
+ elif RE_PROFILE_PTRACE.search(line):
+ matches = RE_PROFILE_PTRACE.search(line).groups()
+
+ if not profile:
+ raise AppArmorException(_('Syntax Error: Unexpected ptrace entry found in file: %s line: %s') % (file, lineno + 1))
+
+ audit = False
+ if matches[0]:
+ audit = True
+ allow = 'allow'
+ if matches[1] and matches[1].strip() == 'deny':
+ allow = 'deny'
+ ptrace = matches[2].strip()
+
+ ptrace_rule = parse_ptrace_rule(ptrace)
+ ptrace_rule.audit = audit
+ ptrace_rule.deny = (allow == 'deny')
+
+ ptrace_rules = profile_data[profile][hat][allow].get('ptrace', list())
+ ptrace_rules.append(ptrace_rule)
+ profile_data[profile][hat][allow]['ptrace'] = ptrace_rules
+
elif RE_PROFILE_CHANGE_HAT.search(line):
matches = RE_PROFILE_CHANGE_HAT.search(line).groups()
@@ -3188,6 +3212,10 @@ def parse_signal_rule(line):
# XXX Do real parsing here
return aarules.Raw_Signal_Rule(line)
+def parse_ptrace_rule(line):
+ # XXX Do real parsing here
+ return aarules.Raw_Ptrace_Rule(line)
+
def separate_vars(vs):
"""Returns a list of all the values for a variable"""
data = []
@@ -3435,6 +3463,24 @@ def write_signal(prof_data, depth):
data += write_signal_rules(prof_data, depth, 'allow')
return data
+def write_ptrace_rules(prof_data, depth, allow):
+ pre = ' ' * depth
+ data = []
+
+ # no ptrace rules, so return
+ if not prof_data[allow].get('ptrace', False):
+ return data
+
+ for ptrace_rule in prof_data[allow]['ptrace']:
+ data.append('%s%s' % (pre, ptrace_rule.serialize()))
+ data.append('')
+ return data
+
+def write_ptrace(prof_data, depth):
+ data = write_ptrace_rules(prof_data, depth, 'deny')
+ data += write_ptrace_rules(prof_data, depth, 'allow')
+ return data
+
def write_link_rules(prof_data, depth, allow):
pre = ' ' * depth
data = []
@@ -3542,6 +3588,7 @@ def write_rules(prof_data, depth):
data += write_dbus(prof_data, depth)
data += write_mount(prof_data, depth)
data += write_signal(prof_data, depth)
+ data += write_ptrace(prof_data, depth)
data += write_links(prof_data, depth)
data += write_paths(prof_data, depth)
data += write_change_profile(prof_data, depth)
@@ -3692,6 +3739,7 @@ def serialize_profile_from_old_profile(profile_data, name, options):
'dbus': write_dbus,
'mount': write_mount,
'signal': write_signal,
+ 'ptrace': write_ptrace,
'link': write_links,
'path': write_paths,
'change_profile': write_change_profile,
@@ -3785,6 +3833,7 @@ def serialize_profile_from_old_profile(profile_data, name, options):
data += write_dbus(write_prof_data[name], depth)
data += write_mount(write_prof_data[name], depth)
data += write_signal(write_prof_data[name], depth)
+ data += write_ptrace(write_prof_data[name], depth)
data += write_links(write_prof_data[name], depth)
data += write_paths(write_prof_data[name], depth)
data += write_change_profile(write_prof_data[name], depth)
diff --git a/utils/apparmor/rules.py b/utils/apparmor/rules.py
index 64f8e54..5b93e1e 100644
--- a/utils/apparmor/rules.py
+++ b/utils/apparmor/rules.py
@@ -79,3 +79,15 @@ class Raw_Signal_Rule(object):
return "%s%s%s" % ('audit ' if self.audit else '',
'deny ' if self.deny else '',
self.rule)
+
+class Raw_Ptrace_Rule(object):
+ audit = False
+ deny = False
+
+ def __init__(self, rule):
+ self.rule = rule
+
+ def serialize(self):
+ return "%s%s%s" % ('audit ' if self.audit else '',
+ 'deny ' if self.deny else '',
+ self.rule)
diff --git a/utils/test/test-ptrace_parse.py b/utils/test/test-ptrace_parse.py
new file mode 100644
index 0000000..adf4b3c
--- /dev/null
+++ b/utils/test/test-ptrace_parse.py
@@ -0,0 +1,54 @@
+#! /usr/bin/env python
+# ------------------------------------------------------------------
+#
+# Copyright (C) 2014 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 published by the Free Software Foundation.
+#
+# ------------------------------------------------------------------
+
+import apparmor.aa as aa
+import unittest
+
+class AAParsePtraceTest(unittest.TestCase):
+
+ def _test_parse_ptrace_rule(self, rule):
+ ptrace = aa.parse_ptrace_rule(rule)
+ print(ptrace.serialize())
+ self.assertEqual(rule, ptrace.serialize(),
+ 'ptrace object returned "%s", expected "%s"' % (ptrace.serialize(), rule))
+
+ def test_parse_plain_ptrace_rule(self):
+ self._test_parse_ptrace_rule('ptrace,')
+
+ def test_parse_readby_ptrace_rule(self):
+ self._test_parse_ptrace_rule('ptrace (readby),')
+
+ def test_parse_trace_ptrace_rule(self):
+ self._test_parse_ptrace_rule('ptrace (trace),')
+
+ def test_parse_trace_read_ptrace_rule(self):
+ self._test_parse_ptrace_rule('ptrace (trace read),')
+
+ def test_parse_r_ptrace_rule(self):
+ self._test_parse_ptrace_rule('ptrace r,')
+
+ def test_parse_w_ptrace_rule(self):
+ self._test_parse_ptrace_rule('ptrace w,')
+
+ def test_parse_rw_ptrace_rule(self):
+ self._test_parse_ptrace_rule('ptrace rw,')
+
+ def test_parse_peer_1_ptrace_rule(self):
+ self._test_parse_ptrace_rule('ptrace read peer=foo,')
+
+ def test_parse_peer_2_ptrace_rule(self):
+ self._test_parse_ptrace_rule('ptrace (trace read) peer=/usr/bin/bar,')
+
+ def test_parse_peer_3_ptrace_rule(self):
+ self._test_parse_ptrace_rule('ptrace wr peer=/sbin/baz,')
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/utils/test/test-regex_matches.py b/utils/test/test-regex_matches.py
index 7096a50..3118c35 100644
--- a/utils/test/test-regex_matches.py
+++ b/utils/test/test-regex_matches.py
@@ -48,6 +48,11 @@ regex_has_comma_testcases = [
('signal (send, receive)%s', 'embedded parens signal 01'),
('signal (send, receive) set=(hup, quit)%s', 'embedded parens signal 02'),
+ ('ptrace%s', 'bare ptrace'),
+ ('ptrace trace%s', 'simple ptrace'),
+ ('ptrace (tracedby, readby)%s', 'embedded parens ptrace 01'),
+ ('ptrace (trace) peer=/usr/bin/foo%s', 'embedded parens ptrace 02'),
+
# the following fail due to inadequacies in the regex
# ('dbus (r, w, %s', 'incomplete dbus action'),
# ('member="{Hello,AddMatch,RemoveMatch, %s', 'incomplete {} regex'), # also invalid policy
@@ -106,6 +111,8 @@ regex_split_comment_testcases = [
('file /tmp/foo rw, # read-write', ('file /tmp/foo rw, ', '# read-write')),
('signal, # comment', ('signal, ', '# comment')),
('signal receive set=(usr1 usr2) peer=foo,', False),
+ ('ptrace, # comment', ('ptrace, ', '# comment')),
+ ('ptrace (trace read) peer=/usr/bin/foo,', False),
]
def setup_split_comment_testcases():
@@ -365,6 +372,77 @@ class AARegexSignal(unittest.TestCase):
self.assertEqual(parsed, rule, 'Expected signal rule "%s", got "%s"'
% (rule, parsed))
+class AARegexPtrace(unittest.TestCase):
+ '''Tests for RE_PROFILE_PTRACE'''
+
+ def test_bare_ptrace_01(self):
+ '''test ' ptrace,' '''
+
+ rule = 'ptrace,'
+ line = ' %s' % rule
+ result = aa.RE_PROFILE_PTRACE.search(line)
+ self.assertTrue(result, 'Couldn\'t find ptrace rule in "%s"' % line)
+ parsed = result.groups()[2].strip()
+ self.assertEqual(parsed, rule, 'Expected ptrace rule "%s", got "%s"'
+ % (rule, parsed))
+
+ def test_bare_ptrace_02(self):
+ '''test ' audit ptrace,' '''
+
+ rule = 'ptrace,'
+ line = ' audit %s' % rule
+ result = aa.RE_PROFILE_PTRACE.search(line)
+ self.assertTrue(result, 'Couldn\'t find ptrace rule in "%s"' % line)
+ self.assertTrue(result.groups()[0], 'Couldn\'t find audit modifier in "%s"' % line)
+ parsed = result.groups()[2].strip()
+ self.assertEqual(parsed, rule, 'Expected ptrace rule "%s", got "%s"'
+ % (rule, parsed))
+
+ def test_simple_ptrace_01(self):
+ '''test ' ptrace trace,' '''
+
+ rule = 'ptrace trace,'
+ line = ' %s' % rule
+ result = aa.RE_PROFILE_PTRACE.search(line)
+ self.assertTrue(result, 'Couldn\'t find ptrace rule in "%s"' % line)
+ parsed = result.groups()[2].strip()
+ self.assertEqual(parsed, rule, 'Expected ptrace rule "%s", got "%s"'
+ % (rule, parsed))
+
+ def test_simple_ptrace_02(self):
+ '''test ' ptrace (tracedby, readby),' '''
+
+ rule = 'ptrace (tracedby, readby),'
+ line = ' %s' % rule
+ result = aa.RE_PROFILE_PTRACE.search(line)
+ self.assertTrue(result, 'Couldn\'t find ptrace rule in "%s"' % line)
+ parsed = result.groups()[2].strip()
+ self.assertEqual(parsed, rule, 'Expected ptrace rule "%s", got "%s"'
+ % (rule, parsed))
+
+ def test_simple_ptrace_03(self):
+ '''test ' audit ptrace (read),' '''
+
+ rule = 'ptrace (read),'
+ line = ' audit %s' % rule
+ result = aa.RE_PROFILE_PTRACE.search(line)
+ self.assertTrue(result, 'Couldn\'t find ptrace rule in "%s"' % line)
+ self.assertTrue(result.groups()[0], 'Couldn\'t find audit modifier in "%s"' % line)
+ parsed = result.groups()[2].strip()
+ self.assertEqual(parsed, rule, 'Expected ptrace rule "%s", got "%s"'
+ % (rule, parsed))
+
+ def test_peer_ptrace_01(self):
+ '''test ' ptrace trace peer=/usr/sbin/daemon,' '''
+
+ rule = 'ptrace trace peer=/usr/sbin/daemon,'
+ line = ' %s' % rule
+ result = aa.RE_PROFILE_PTRACE.search(line)
+ self.assertTrue(result, 'Couldn\'t find ptrace rule in "%s"' % line)
+ parsed = result.groups()[2].strip()
+ self.assertEqual(parsed, rule, 'Expected ptrace rule "%s", got "%s"'
+ % (rule, parsed))
+
if __name__ == '__main__':
verbosity = 2
@@ -378,6 +456,7 @@ if __name__ == '__main__':
test_suite.addTest(unittest.TestLoader().loadTestsFromTestCase(AARegexPath))
test_suite.addTest(unittest.TestLoader().loadTestsFromTestCase(AARegexFile))
test_suite.addTest(unittest.TestLoader().loadTestsFromTestCase(AARegexSignal))
+ test_suite.addTest(unittest.TestLoader().loadTestsFromTestCase(AARegexPtrace))
result = unittest.TextTestRunner(verbosity=verbosity).run(test_suite)
if not result.wasSuccessful():
exit(1)
--
1.9.1
More information about the AppArmor
mailing list