This patch adds a test script/driver for the aa-decode utility. The only change from the previous versions is to support overriding the location of the aa-decode to test via the APPARMOR_DECODE environment variable and documenting the utils/ tests in the top level README.
The aa-decode test can be run directly from the commandline in the utils directory like so: test/test-aa-decode.py -v --- README | 14 ++ utils/test/test-aa-decode.py | 203 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+) Index: b/utils/test/test-aa-decode.py =================================================================== --- /dev/null +++ b/utils/test/test-aa-decode.py @@ -0,0 +1,203 @@ +#! /usr/bin/env python +# ------------------------------------------------------------------ +# +# Copyright (C) 2011-2012 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 os +import signal +import subprocess +import tempfile +import unittest + +# The locationg of the aa-decode utility can be overridden by setting +# the APPARMOR_DECODE environment variable; this is useful for running +# these tests in an installed environment +aadecode_bin = "./aa-decode" + +# http://www.chiark.greenend.org.uk/ucgi/~cjwatson/blosxom/2009-07-02-python-sigpipe.html +# This is needed so that the subprocesses that produce endless output +# actually quit when the reader goes away. +def subprocess_setup(): + # Python installs a SIGPIPE handler by default. This is usually not what + # non-Python subprocesses expect. + signal.signal(signal.SIGPIPE, signal.SIG_DFL) + +def cmd(command, input = None, stderr = subprocess.STDOUT, stdout = subprocess.PIPE, stdin = None, timeout = None): + '''Try to execute given command (array) and return its stdout, or return + a textual error if it failed.''' + + try: + sp = subprocess.Popen(command, stdin=stdin, stdout=stdout, stderr=stderr, close_fds=True, preexec_fn=subprocess_setup) + except OSError as e: + return [127, str(e)] + + out, outerr = sp.communicate(input) + # Handle redirection of stdout + if out == None: + out = b'' + # Handle redirection of stderr + if outerr == None: + outerr = b'' + return [sp.returncode, out.decode('utf-8') + outerr.decode('utf-8')] + + +def mkstemp_fill(contents, suffix='', prefix='tst-aadecode-', dir=None): + '''As tempfile.mkstemp does, return a (file, name) pair, but with prefilled contents.''' + + handle, name = tempfile.mkstemp(suffix=suffix, prefix=prefix, dir=dir) + os.close(handle) + handle = open(name, "w+") + handle.write(contents) + handle.flush() + handle.seek(0) + + return handle, name + +class AADecodeTest(unittest.TestCase): + + def setUp(self): + self.tmpfile = None + + def tearDown(self): + if self.tmpfile and os.path.exists(self.tmpfile): + os.remove(self.tmpfile) + + def test_help(self): + '''Test --help argument''' + + expected = 0 + rc, report = cmd([aadecode_bin, "--help"]) + result = 'Got exit code %d, expected %d\n' % (rc, expected) + self.assertEqual(expected, rc, result + report) + + def _run_file_test(self, content, expected_list): + '''test case helper function; takes log content and a list of + expected strings as arguments''' + + expected = 0 + + (f, self.tmpfile) = mkstemp_fill(content) + + rc, report = cmd([aadecode_bin], stdin=f) + result = 'Got exit code %d, expected %d\n' % (rc, expected) + self.assertEqual(expected, rc, result + report) + for expected_string in expected_list: + result = 'could not find expected %s in output:\n' % (expected_string) + self.assertIn(expected_string, report, result + report) + f.close() + + def test_simple_decode(self): + '''Test simple decode on command line''' + + expected = 0 + expected_output = 'Decoded: /tmp/foo bar' + test_code = '2F746D702F666F6F20626172' + + rc, report = cmd([aadecode_bin, test_code]) + result = 'Got exit code %d, expected %d\n' % (rc, expected) + self.assertEqual(expected, rc, result + report) + result = 'Got output "%s", expected "%s"\n' % (report, expected_output) + self.assertIn(expected_output, report, result + report) + + def test_simple_filter(self): + '''test simple decoding of the name argument''' + + expected_string = 'name="/tmp/foo bar"' + content = \ +'''type=AVC msg=audit(1348982151.183:2934): apparmor="DENIED" operation="open" parent=30751 profile="/usr/lib/firefox/firefox{,*[^s] [^h]}" name=2F746D702F666F6F20626172 pid=30833 comm="plugin-containe" requested_mask="r" denied_mask="r" fsuid=1000 ouid=0 +''' + + self._run_file_test(content, [expected_string]) + + def test_simple_multiline(self): + '''test simple multiline decoding of the name argument''' + + expected_strings = ['ses=4294967295 new ses=2762', + 'name="/tmp/foo bar"', + 'name="/home/steve/tmp/my test file"'] + content = \ +''' type=LOGIN msg=audit(1348980001.155:2925): login pid=17875 uid=0 old auid=4294967295 new auid=0 old ses=4294967295 new ses=2762 +type=AVC msg=audit(1348982151.183:2934): apparmor="DENIED" operation="open" parent=30751 profile="/usr/lib/firefox/firefox{,*[^s] [^h]}" name=2F746D702F666F6F20626172 pid=30833 comm="plugin-containe" requested_mask="r" denied_mask="r" fsuid=1000 ouid=0 +type=AVC msg=audit(1348982148.195:2933): apparmor="DENIED" operation="file_lock" parent=5490 profile="/usr/lib/firefox/firefox{,*[^s][^h]}" name=2F686F6D652F73746576652F746D702F6D7920746573742066696C65 pid=30737 comm="firefox" requested_mask="k" denied_mask="k" fsuid=1000 ouid=1000 +''' + + self._run_file_test(content, expected_strings) + + def test_simple_profile(self): + '''test simple decoding of the profile argument''' + + '''Example take from LP: #897957''' + expected_strings = ['name="/lib/x86_64-linux-gnu/libdl-2.13.so"', + 'profile="/test space"'] + content = \ +'''[289763.843292] type=1400 audit(1322614912.304:857): apparmor="ALLOWED" operation="getattr" parent=16001 profile=2F74657374207370616365 name="/lib/x86_64-linux-gnu/libdl-2.13.so" pid=17011 comm="bash" requested_mask="r" denied_mask="r" fsuid=0 ouid=0 +''' + + self._run_file_test(content, expected_strings) + + def test_simple_profile2(self): + '''test simple decoding of name and profile argument''' + + '''Example take from LP: #897957''' + expected_strings = ['name="/home/steve/tmp/my test file"', + 'profile="/home/steve/tmp/my prog.sh"'] + content = \ +'''type=AVC msg=audit(1349805073.402:6857): apparmor="DENIED" operation="mknod" parent=5890 profile=2F686F6D652F73746576652F746D702F6D792070726F672E7368 name=2F686F6D652F73746576652F746D702F6D7920746573742066696C65 pid=5891 comm="touch" requested_mask="c" denied_mask="c" fsuid=1000 ouid=1000 +''' + + self._run_file_test(content, expected_strings) + + def test_simple_embedded_carat(self): + '''test simple decoding of embedded ^ in files''' + + expected_strings = ['name="/home/steve/tmp/my test ^file"'] + content = \ +'''type=AVC msg=audit(1349805073.402:6857): apparmor="DENIED" operation="mknod" parent=5890 profile="/usr/bin/test_profile" name=2F686F6D652F73746576652F746D702F6D792074657374205E66696C65 pid=5891 comm="touch" requested_mask="c" denied_mask="c" fsuid=1000 ouid=1000 +''' + + self._run_file_test(content, expected_strings) + + def test_simple_embedded_backslash_carat(self): + '''test simple decoding of embedded \^ in files''' + + expected_strings = ['name="/home/steve/tmp/my test \^file"'] + content = \ +'''type=AVC msg=audit(1349805073.402:6857): apparmor="DENIED" operation="mknod" parent=5890 profile="/usr/bin/test_profile" name=2F686F6D652F73746576652F746D702F6D792074657374205C5E66696C65 pid=5891 comm="touch" requested_mask="c" denied_mask="c" fsuid=1000 ouid=1000 +''' + + self._run_file_test(content, expected_strings) + + def test_simple_embedded_singlequote(self): + '''test simple decoding of embedded \' in files''' + + expected_strings = ['name="/home/steve/tmp/my test \'file"'] + content = \ +'''type=AVC msg=audit(1349805073.402:6857): apparmor="DENIED" operation="mknod" parent=5890 profile="/usr/bin/test_profile" name=2F686F6D652F73746576652F746D702F6D792074657374202766696C65 pid=5891 comm="touch" requested_mask="c" denied_mask="c" fsuid=1000 ouid=1000 +''' + + self._run_file_test(content, expected_strings) + + def test_simple_encoded_nonpath_profiles(self): + '''test simple decoding of nonpath profiles''' + + expected_strings = ['name="/lib/x86_64-linux-gnu/libdl-2.13.so"', + 'profile="test space"'] + content = \ +'''[289763.843292] type=1400 audit(1322614912.304:857): apparmor="ALLOWED" operation="getattr" parent=16001 profile=74657374207370616365 name="/lib/x86_64-linux-gnu/libdl-2.13.so" pid=17011 comm="bash" requested_mask="r" denied_mask="r" fsuid=0 ouid=0 +''' + + self._run_file_test(content, expected_strings) + +# +# Main +# +if __name__ == '__main__': + if 'APPARMOR_DECODE' in os.environ: + aadecode_bin = os.environ['APPARMOR_DECODE'] + unittest.main() Index: b/README =================================================================== --- a/README +++ b/README @@ -146,6 +146,20 @@ For details on structure and adding test $ cd libraries/libapparmor $ make check +Utils +----- +There are some simple tests available, including basic perl syntax +checks for the perl modules and executables. There are also minimal +checks on the python utilities and python-based tests in the test/ +subdirectory. +$ cd utils +$ make check + +The aa-decode utility to be tested can be overridden by +setting up environment variable APPARMOR_DECODE; e.g.: + +$ APPARMOR_DECODE=/usr/bin/aa-decode make check + Profile checks -------------- A basic consistency check to ensure that the parser and aa-logprof parse -- AppArmor mailing list [email protected] Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/apparmor
