def test_signal_from_log(self): parser = ReadLog('', '', '', '') event = 'type=AVC msg=audit(1409438250.564:201): apparmor="DENIED" operation="signal" profile="/usr/bin/pulseaudio" pid=2531 comm="pulseaudio" requested_mask="send" denied_mask="send" signal=term peer="/usr/bin/pulseaudio///usr/lib/pulseaudio/pulse/gconf-helper"' parsed_event = parser.parse_event(event) self.assertEqual( parsed_event, { 'request_mask': 'send', 'denied_mask': 'send', 'error_code': 0, 'magic_token': 0, 'parent': 0, 'profile': '/usr/bin/pulseaudio', 'signal': 'term', 'peer': '/usr/bin/pulseaudio///usr/lib/pulseaudio/pulse/gconf-helper', 'operation': 'signal', 'resource': None, 'info': None, 'aamode': 'REJECTING', 'time': 1409438250, 'active_hat': None, 'pid': 2531, 'task': 0, 'attr': None, 'name2': None, 'name': None, 'family': None, 'protocol': None, 'sock_type': None, }) obj = SignalRule(parsed_event['denied_mask'], parsed_event['signal'], parsed_event['peer'], log_event=parsed_event) # audit allow deny comment access all? signal all? peer all? expected = exp( False, False, False, '', {'send'}, False, {'term'}, False, '/usr/bin/pulseaudio///usr/lib/pulseaudio/pulse/gconf-helper', False) self._compare_obj(obj, expected) self.assertEqual( obj.get_raw(1), ' signal send set=term peer=/usr/bin/pulseaudio///usr/lib/pulseaudio/pulse/gconf-helper,' )
def test_ptrace_from_log(self): parser = ReadLog('', '', '', '') event = 'type=AVC msg=audit(1409700683.304:547661): apparmor="DENIED" operation="ptrace" profile="/home/ubuntu/bzr/apparmor/tests/regression/apparmor/ptrace" pid=22465 comm="ptrace" requested_mask="tracedby" denied_mask="tracedby" peer="/home/ubuntu/bzr/apparmor/tests/regression/apparmor/ptrace"' parsed_event = parser.parse_event(event) self.assertEqual( parsed_event, { 'request_mask': 'tracedby', 'denied_mask': 'tracedby', 'error_code': 0, 'magic_token': 0, 'parent': 0, 'profile': '/home/ubuntu/bzr/apparmor/tests/regression/apparmor/ptrace', 'peer': '/home/ubuntu/bzr/apparmor/tests/regression/apparmor/ptrace', 'operation': 'ptrace', 'resource': None, 'info': None, 'aamode': 'REJECTING', 'time': 1409700683, 'active_hat': None, 'pid': 22465, 'task': 0, 'attr': None, 'name2': None, 'name': None, 'family': None, 'protocol': None, 'sock_type': None, }) obj = PtraceRule(parsed_event['denied_mask'], parsed_event['peer'], log_event=parsed_event) # audit allow deny comment access all? peer all? expected = exp( False, False, False, '', {'tracedby'}, False, '/home/ubuntu/bzr/apparmor/tests/regression/apparmor/ptrace', False) self._compare_obj(obj, expected) self.assertEqual( obj.get_raw(1), ' ptrace tracedby peer=/home/ubuntu/bzr/apparmor/tests/regression/apparmor/ptrace,' )
class TestParseEventForTreeInvalid(AATest): tests = [ ('type=AVC msg=audit(1556742870.707:3614): apparmor="ALLOWED" operation="open" profile="/bin/hello" name="/dev/tty" pid=12856 comm="hello" requested_mask="wr" denied_mask="foo" fsuid=1000 ouid=0', AppArmorException), # invalid file permissions "foo" ('type=AVC msg=audit(1556742870.707:3614): apparmor="ALLOWED" operation="open" profile="/bin/hello" name="/dev/tty" pid=12856 comm="hello" requested_mask="wr" denied_mask="wr::w" fsuid=1000 ouid=0', AppArmorException), # "wr::w" mixes owner and other ] def _fake_profile_exists(self, program): return True def _run_test(self, params, expected): self.parser = ReadLog('', '', '') self.parser.profile_exists = self._fake_profile_exists # inject fake function that always returns True - much easier than handing over a ProfileList object to __init__ parsed_event = self.parser.parse_event(params) with self.assertRaises(expected): self.parser.parse_event_for_tree(parsed_event)
def test_change_profile_from_log(self): parser = ReadLog('', '', '', '') event = 'type=AVC msg=audit(1428699242.551:386): apparmor="DENIED" operation="change_profile" profile="/foo/changeprofile" pid=3459 comm="changeprofile" target="/foo/rename"' # libapparmor doesn't understand this log format (from JJ) # event = '[ 97.492562] audit: type=1400 audit(1431116353.523:77): apparmor="DENIED" operation="change_profile" profile="/foo/changeprofile" pid=3459 comm="changeprofile" target="/foo/rename"' parsed_event = parser.parse_event(event) self.assertEqual( parsed_event, { 'request_mask': None, 'denied_mask': None, 'error_code': 0, 'magic_token': 0, 'parent': 0, 'profile': '/foo/changeprofile', 'operation': 'change_profile', 'resource': None, 'info': None, 'aamode': 'REJECTING', 'time': 1428699242, 'active_hat': None, 'pid': 3459, 'task': 0, 'attr': None, 'name2': '/foo/rename', # target 'name': None, 'family': None, 'protocol': None, 'sock_type': None, }) obj = ChangeProfileRule(None, ChangeProfileRule.ALL, parsed_event['name2'], log_event=parsed_event) # audit allow deny comment execmode execcond all? targetprof all? expected = exp(False, False, False, '', None, None, True, '/foo/rename', False) self._compare_obj(obj, expected) self.assertEqual(obj.get_raw(1), ' change_profile -> /foo/rename,')
def test_cap_from_log(self): parser = ReadLog('', '', '', '') event = 'type=AVC msg=audit(1415403814.628:662): apparmor="ALLOWED" operation="capable" profile="/bin/ping" pid=15454 comm="ping" capability=13 capname="net_raw"' parsed_event = parser.parse_event(event) self.assertEqual( parsed_event, { 'request_mask': None, 'denied_mask': None, 'error_code': 0, 'magic_token': 0, 'parent': 0, 'profile': '/bin/ping', 'operation': 'capable', 'resource': None, 'info': None, 'aamode': 'PERMITTING', 'time': 1415403814, 'active_hat': None, 'pid': 15454, 'task': 0, 'attr': None, 'name2': None, 'name': 'net_raw', 'family': None, 'protocol': None, 'sock_type': None, }) obj = CapabilityRule(parsed_event['name'], log_event=parsed_event) self._compare_obj( obj, { 'allow_keyword': False, 'deny': False, 'audit': False, 'capability': {'net_raw'}, 'all_caps': False, 'comment': "", }) self.assertEqual(obj.get_raw(1), ' capability net_raw,')
def test_net_from_log(self): parser = ReadLog('', '', '', '') event = 'type=AVC msg=audit(1428699242.551:386): apparmor="DENIED" operation="create" profile="/bin/ping" pid=10589 comm="ping" family="inet" sock_type="raw" protocol=1' parsed_event = parser.parse_event(event) self.assertEqual( parsed_event, { 'request_mask': None, 'denied_mask': None, 'error_code': 0, 'family': 'inet', 'magic_token': 0, 'parent': 0, 'profile': '/bin/ping', 'protocol': 'icmp', 'sock_type': 'raw', 'operation': 'create', 'resource': None, 'info': None, 'aamode': 'REJECTING', 'time': 1428699242, 'active_hat': None, 'pid': 10589, 'task': 0, 'attr': None, 'name2': None, 'name': None, }) obj = NetworkRule(parsed_event['family'], parsed_event['sock_type'], log_event=parsed_event) # audit allow deny comment domain all? type/proto all? expected = exp(False, False, False, '', 'inet', False, 'raw', False) self._compare_obj(obj, expected) self.assertEqual(obj.get_raw(1), ' network inet raw,')
def test_net_from_log(self): parser = ReadLog('', '', '', '', '') event = 'type=AVC msg=audit(1428699242.551:386): apparmor="DENIED" operation="change_profile" profile="/foo/changeprofile" pid=3459 comm="changeprofile" target="/foo/rename"' # libapparmor doesn't understand this log format (from JJ) # event = '[ 97.492562] audit: type=1400 audit(1431116353.523:77): apparmor="DENIED" operation="change_profile" profile="/foo/changeprofile" pid=3459 comm="changeprofile" target="/foo/rename"' parsed_event = parser.parse_event(event) self.assertEqual(parsed_event, { 'request_mask': None, 'denied_mask': None, 'error_code': 0, #'family': 'inet', 'magic_token': 0, 'parent': 0, 'profile': '/foo/changeprofile', 'operation': 'change_profile', 'resource': None, 'info': None, 'aamode': 'REJECTING', 'time': 1428699242, 'active_hat': None, 'pid': 3459, 'task': 0, 'attr': None, 'name2': '/foo/rename', # target 'name': None, }) obj = ChangeProfileRule(ChangeProfileRule.ALL, parsed_event['name2'], log_event=parsed_event) # audit allow deny comment execcond all? targetprof all? expected = exp(False, False, False, '' , None, True, '/foo/rename', False) self._compare_obj(obj, expected) self.assertEqual(obj.get_raw(1), ' change_profile -> /foo/rename,')
def _run_test(self, params, expected): logfile = '%s.in' % params profile_dummy_file = 'AATest_does_exist' # we need to find out the profile name and aamode (complain vs. enforce mode) so that the test can access the correct place in storage parser = ReadLog('', '', '', '') parsed_event = parser.parse_event(read_file(logfile)) if not parsed_event: # AA_RECORD_INVALID return if params.split('/')[-1] in log_to_profile_skip: return aamode = parsed_event['aamode'] if aamode in [ 'AUDIT', 'STATUS', 'HINT' ]: # ignore some event types # XXX maybe we shouldn't ignore AUDIT events? return if aamode not in ['PERMITTING', 'REJECTING']: raise Exception('Unexpected aamode %s' % parsed_event['aamode']) # cleanup apparmor.aa storage apparmor.aa.log = dict() apparmor.aa.aa = apparmor.aa.hasher() apparmor.aa.prelog = apparmor.aa.hasher() profile = parsed_event['profile'] hat = profile if '//' in profile: profile, hat = profile.split('//') apparmor.aa.existing_profiles = {profile: profile_dummy_file} log_reader = ReadLog(dict(), logfile, apparmor.aa.existing_profiles, '') log = log_reader.read_log('') for root in log: apparmor.aa.handle_children('', '', root) # interactive for exec events! log_dict = apparmor.aa.collapse_log() apparmor.aa.filelist = apparmor.aa.hasher() apparmor.aa.filelist[profile_dummy_file]['profiles'][profile] = True new_profile = apparmor.aa.serialize_profile(log_dict[aamode][profile], profile, None) expected_profile = read_file('%s.profile' % params) if params.split('/')[-1] in log_to_profile_known_failures: self.assertNotEqual(new_profile, expected_profile) # known failure else: self.assertEqual(new_profile, expected_profile)
def test_cap_from_log(self): parser = ReadLog('', '', '', '', '') event = 'type=AVC msg=audit(1415403814.628:662): apparmor="ALLOWED" operation="capable" profile="/bin/ping" pid=15454 comm="ping" capability=13 capname="net_raw"' parsed_event = parser.parse_event(event) self.assertEqual(parsed_event, { 'request_mask': None, 'denied_mask': None, 'error_code': 0, 'magic_token': 0, 'parent': 0, 'profile': '/bin/ping', 'operation': 'capable', 'resource': None, 'info': None, 'aamode': 'PERMITTING', 'time': 1415403814, 'active_hat': None, 'pid': 15454, 'task': 0, 'attr': None, 'name2': None, 'name': 'net_raw' }) obj = CapabilityRule(parsed_event['name'], log_event=parsed_event) self._compare_obj(obj, { 'allow_keyword': False, 'deny': False, 'audit': False, 'capability': {'net_raw'}, 'all_caps': False, 'comment': "", }) self.assertEqual(obj.get_raw(1), ' capability net_raw,')
def test_net_from_log(self): parser = ReadLog('', '', '', '', '') event = 'type=AVC msg=audit(1428699242.551:386): apparmor="DENIED" operation="create" profile="/bin/ping" pid=10589 comm="ping" family="inet" sock_type="raw" protocol=1' parsed_event = parser.parse_event(event) self.assertEqual(parsed_event, { 'request_mask': None, 'denied_mask': None, 'error_code': 0, 'family': 'inet', 'magic_token': 0, 'parent': 0, 'profile': '/bin/ping', 'protocol': 'icmp', 'sock_type': 'raw', 'operation': 'create', 'resource': None, 'info': None, 'aamode': 'REJECTING', 'time': 1428699242, 'active_hat': None, 'pid': 10589, 'task': 0, 'attr': None, 'name2': None, 'name': None, }) obj = NetworkRule(parsed_event['family'], parsed_event['sock_type'], log_event=parsed_event) # audit allow deny comment domain all? type/proto all? expected = exp(False, False, False, '' , 'inet', False, 'raw' , False) self._compare_obj(obj, expected) self.assertEqual(obj.get_raw(1), ' network inet raw,')
def _run_test(self, params, expected): # tests[][expected] is a dummy, replace it with the real values if params.split('/')[-1] in log_to_skip: return expected = self._parse_libapparmor_test_multi(params) with open_file_read('%s.in' % params) as f_in: loglines = f_in.readlines() loglines2 = [] for line in loglines: if line.strip(): loglines2 += [line] self.assertEqual(len(loglines2), 1, '%s.in should only contain one line!' % params) parser = ReadLog('', '', '') parsed_event = parser.parse_event(loglines2[0]) if parsed_event and expected: parsed_items = dict(parsed_event.items()) # check if the line passes the regex in logparser.py if not parser.RE_LOG_ALL.search(loglines2[0]): raise Exception("Log event doesn't match RE_LOG_ALL") for label in expected: if label in [ 'file', # filename of the *.in file 'event_type', # mapped to aamode 'audit_id', 'audit_sub_id', # not set nor relevant 'comm', # not set, and not too useful # XXX most of the keywords listed below mean "TODO" 'fsuid', 'ouid', # file events 'flags', 'fs_type', # mount 'namespace', # file_lock only?? (at least the tests don't contain this in other event types with namespace) 'net_local_addr', 'net_foreign_addr', 'net_local_port', 'net_foreign_port', # detailed network events 'peer', 'signal', # signal 'src_name', # pivotroot 'dbus_bus', 'dbus_interface', 'dbus_member', 'dbus_path', # dbus 'peer_pid', 'peer_profile', # dbus ]: pass elif parsed_items['operation'] == 'exec' and label in [ 'sock_type', 'family', 'protocol' ]: pass # XXX 'exec' + network? really? elif parsed_items[ 'operation'] == 'ptrace' and label == 'name2' and params.endswith( '/ptrace_garbage_lp1689667_1'): pass # libapparmor would better qualify this case as invalid event elif not parsed_items.get(label, None): raise Exception('parsed_items[%s] not set' % label) elif not expected.get(label, None): raise Exception('expected[%s] not set' % label) else: self.assertEqual(str(parsed_items[label]), expected[label], '%s differs' % label) elif expected: self.assertIsNone(parsed_event) # that's why we end up here self.assertEqual(dict(), expected, 'parsed_event is none' ) # effectively print the content of expected elif parsed_event: self.assertIsNone(expected) # that's why we end up here self.assertEqual(parsed_event, dict(), 'expected is none' ) # effectively print the content of parsed_event else: self.assertIsNone(expected) # that's why we end up here self.assertIsNone(parsed_event) # that's why we end up here self.assertEqual(parsed_event, expected) # both are None
def logfile_to_profile(logfile): profile_dummy_file = 'AATest_does_exist' # we need to find out the profile name and aamode (complain vs. enforce mode) so that the test can access the correct place in storage parser = ReadLog('', '', '') parsed_event = parser.parse_event(read_file(logfile)) if not parsed_event: # AA_RECORD_INVALID return None, 'INVALID' aamode = parsed_event['aamode'] if aamode in [ 'AUDIT', 'STATUS', 'HINT' ]: # ignore some event types # XXX maybe we shouldn't ignore AUDIT events? return None, aamode if aamode not in ['PERMITTING', 'REJECTING']: raise Exception('Unexpected aamode %s' % parsed_event['aamode']) # cleanup apparmor.aa storage apparmor.aa.log = dict() apparmor.aa.aa = apparmor.aa.hasher() profile, hat = split_name(parsed_event['profile']) apparmor.aa.active_profiles = ProfileList() # optional for now, might be needed one day # if profile.startswith('/'): # apparmor.aa.active_profiles.add_profile(profile_dummy_file, profile, profile) # else: apparmor.aa.active_profiles.add_profile(profile_dummy_file, profile, '') log_reader = ReadLog(logfile, apparmor.aa.active_profiles, '') hashlog = log_reader.read_log('') apparmor.aa.ask_exec(hashlog) apparmor.aa.ask_addhat(hashlog) log_dict = apparmor.aa.collapse_log(hashlog, ignore_null_profiles=False) if profile != hat: # log event for a child profile means log_dict only contains the child profile # initialize parent profile in log_dict as ProfileStorage to ensure writing the profile doesn't fail # (in "normal" usage outside of this test, log_dict will not be handed over to serialize_profile()) if log_dict[aamode][profile][profile] != {}: raise Exception( 'event for child profile, but parent profile was initialized nevertheless. Logfile: %s' % logfile) log_dict[aamode][profile][profile] = apparmor.aa.ProfileStorage( 'TEST DUMMY for empty parent profile', profile_dummy_file, 'logfile_to_profile()') log_is_empty = True for tmpaamode in hashlog: for tmpprofile in hashlog[tmpaamode]: for tmpruletype in hashlog[tmpaamode][tmpprofile]: if tmpruletype == 'final_name' and hashlog[tmpaamode][ tmpprofile]['final_name'] == tmpprofile: continue # final_name is a copy of the profile name (may be changed by ask_exec(), but that won't happen in this test) if hashlog[tmpaamode][tmpprofile][tmpruletype]: log_is_empty = False if logfile.split('/')[-1][:-3] in log_to_profile_known_empty_log: # unfortunately this function might be called outside Unittest.TestCase, therefore we can't use assertEqual / assertNotEqual if log_is_empty == False: raise Exception( 'got non-empty log for logfile in log_to_profile_known_empty_log: %s %s' % (logfile, hashlog)) else: if log_is_empty == True: raise Exception( 'got empty log for logfile not in log_to_profile_known_empty_log: %s %s' % (logfile, hashlog)) new_profile = apparmor.aa.serialize_profile(log_dict[aamode][profile], profile, {}) return profile, new_profile
def setUp(self): self.parser = ReadLog('', '', '', '')
class TestParseEvent(unittest.TestCase): def setUp(self): self.parser = ReadLog('', '', '', '') def test_parse_event_audit_1(self): event = 'type=AVC msg=audit(1345027352.096:499): apparmor="ALLOWED" operation="rename_dest" parent=6974 profile="/usr/sbin/httpd2-prefork//vhost_foo" name=2F686F6D652F7777772F666F6F2E6261722E696E2F68747470646F63732F61707061726D6F722F696D616765732F746573742F696D61676520312E6A7067 pid=20143 comm="httpd2-prefork" requested_mask="wc" denied_mask="wc" fsuid=30 ouid=30' parsed_event = self.parser.parse_event(event) self.assertEqual( parsed_event['name'], '/home/www/foo.bar.in/httpdocs/apparmor/images/test/image 1.jpg') self.assertEqual(parsed_event['profile'], '/usr/sbin/httpd2-prefork//vhost_foo') self.assertEqual(parsed_event['aamode'], 'PERMITTING') self.assertEqual(parsed_event['request_mask'], 'wc') self.assertIsNotNone(ReadLog.RE_LOG_ALL.search(event)) def test_parse_event_audit_2(self): event = 'type=AVC msg=audit(1322614918.292:4376): apparmor="ALLOWED" operation="file_perm" parent=16001 profile=666F6F20626172 name="/home/foo/.bash_history" pid=17011 comm="bash" requested_mask="rw" denied_mask="rw" fsuid=0 ouid=1000' parsed_event = self.parser.parse_event(event) self.assertEqual(parsed_event['name'], '/home/foo/.bash_history') self.assertEqual(parsed_event['profile'], 'foo bar') self.assertEqual(parsed_event['aamode'], 'PERMITTING') self.assertEqual(parsed_event['request_mask'], 'rw') self.assertIsNotNone(ReadLog.RE_LOG_ALL.search(event)) def test_parse_event_syslog_1(self): # from https://bugs.launchpad.net/apparmor/+bug/1399027 event = '2014-06-09T20:37:28.975070+02:00 geeko kernel: [21028.143765] type=1400 audit(1402339048.973:1421): apparmor="ALLOWED" operation="open" profile="/home/cb/linuxtag/apparmor/scripts/hello" name="/dev/tty" pid=14335 comm="hello" requested_mask="rw" denied_mask="rw" fsuid=1000 ouid=0' parsed_event = self.parser.parse_event(event) self.assertEqual(parsed_event['name'], '/dev/tty') self.assertEqual(parsed_event['profile'], '/home/cb/linuxtag/apparmor/scripts/hello') self.assertEqual(parsed_event['aamode'], 'PERMITTING') self.assertEqual(parsed_event['request_mask'], 'rw') self.assertIsNotNone(ReadLog.RE_LOG_ALL.search(event)) def test_parse_event_syslog_2(self): # from https://bugs.launchpad.net/apparmor/+bug/1399027 event = 'Dec 7 13:18:59 rosa kernel: audit: type=1400 audit(1417954745.397:82): apparmor="ALLOWED" operation="open" profile="/home/simi/bin/aa-test" name="/usr/bin/" pid=3231 comm="ls" requested_mask="r" denied_mask="r" fsuid=1000 ouid=0' parsed_event = self.parser.parse_event(event) self.assertEqual(parsed_event['name'], '/usr/bin/') self.assertEqual(parsed_event['profile'], '/home/simi/bin/aa-test') self.assertEqual(parsed_event['aamode'], 'PERMITTING') self.assertEqual(parsed_event['request_mask'], 'r') self.assertIsNotNone(ReadLog.RE_LOG_ALL.search(event)) def test_parse_disconnected_path(self): # from https://bugzilla.opensuse.org/show_bug.cgi?id=918787 event = 'type=AVC msg=audit(1424425690.883:716630): apparmor="ALLOWED" operation="file_mmap" info="Failed name lookup - disconnected path" error=-13 profile="/sbin/klogd" name="var/run/nscd/passwd" pid=25333 comm="id" requested_mask="r" denied_mask="r" fsuid=1002 ouid=0' parsed_event = self.parser.parse_event(event) self.assertEqual( parsed_event, { 'aamode': 'ERROR', # aamode for disconnected paths overridden aamode in parse_event() 'active_hat': None, 'attr': None, 'denied_mask': 'r', 'error_code': 13, 'fsuid': 1002, 'info': 'Failed name lookup - disconnected path', 'magic_token': 0, 'name': 'var/run/nscd/passwd', 'name2': None, 'operation': 'file_mmap', 'ouid': 0, 'parent': 0, 'pid': 25333, 'profile': '/sbin/klogd', 'request_mask': 'r', 'resource': 'Failed name lookup - disconnected path', 'task': 0, 'time': 1424425690, 'family': None, 'protocol': None, 'sock_type': None, }) self.assertIsNotNone(ReadLog.RE_LOG_ALL.search(event))
def _run_test(self, params, expected): self.parser = ReadLog('', '', '') self.parser.profile_exists = self._fake_profile_exists # inject fake function that always returns True - much easier than handing over a ProfileList object to __init__ parsed_event = self.parser.parse_event(params) with self.assertRaises(expected): self.parser.parse_event_for_tree(parsed_event)