2
0
mirror of https://gitlab.com/apparmor/apparmor synced 2025-08-31 06:16:03 +00:00

A commit before changing modes style

This commit is contained in:
Kshitij Gupta
2013-08-11 15:22:07 +05:30
parent 3212422921
commit 05e695c7d3
10 changed files with 1394 additions and 359 deletions

View File

@@ -3,6 +3,7 @@ import sys
sys.path.append('../')
import apparmor.aa
import apparmor.logparser
#from apparmor.aa import parse_event
@@ -13,8 +14,9 @@ class Test(unittest.TestCase):
def test_parse_event(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 = apparmor.aa.parse_event(event)
parsed_event = apparmor.logparser.ReadLog.parse_event(apparmor.logparser.ReadLog, event)
self.assertEqual(parsed_event['name'], '/home/www/foo.bar.in/httpdocs/apparmor/images/test/image 1.jpg', 'Incorrectly parsed/decoded name')
self.assertEqual(parsed_event['profile'], '/usr/sbin/httpd2-prefork//vhost_foo', 'Incorrectly parsed/decode profile name')
self.assertEqual(parsed_event['aamode'], 'PERMITTING')
@@ -26,7 +28,7 @@ class Test(unittest.TestCase):
#print(parsed_event)
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="w" denied_mask="w" fsuid=0 ouid=1000'
parsed_event = apparmor.aa.parse_event(event)
parsed_event = apparmor.logparser.ReadLog.parse_event(apparmor.logparser.ReadLog, event)
self.assertEqual(parsed_event['name'], '/home/foo/.bash_history', 'Incorrectly parsed/decoded name')
self.assertEqual(parsed_event['profile'], 'foo bar', 'Incorrectly parsed/decode profile name')
self.assertEqual(parsed_event['aamode'], 'PERMITTING')

View File

@@ -16,7 +16,7 @@ class Test(unittest.TestCase):
for regex in regex_tests.sections():
parsed_regex = re.compile(apparmor.common.convert_regexp(regex))
for regex_testcase in regex_tests.options(regex):
self.assertEqual(bool(parsed_regex.search(regex_testcase)), eval(regex_tests[regex][regex_testcase]), 'Incorrectly Parsed regex')
self.assertEqual(bool(parsed_regex.search(regex_testcase)), eval(regex_tests[regex][regex_testcase]), 'Incorrectly Parsed regex: %s' %regex)
#def test_readkey(self):
# print("Please press the Y button on the keyboard.")

View File

@@ -6,13 +6,6 @@ import apparmor.aa
import os
import argparse
if sys.version_info < (3,0):
os.environ['AAPATH'] = '/bin/:/sbin/:/usr/bin/:/usr/sbin'
else:
os.environb.putenv('AAPATH', '/bin/:/sbin/:/usr/bin/:/usr/sbin')
logmark = ''
apparmor.aa.loadincludes()

651
Tools/out.out Normal file
View File

@@ -0,0 +1,651 @@
Reading log entries from /var/log/messages.
Updating AppArmor profiles in /etc/apparmor.d.
ARRAY(0x1ac8bb8) ARRAY(0x1af2b80) ARRAY(0xbbe5f8) ARRAY(0x1b06848) ARRAY(0x1af2bb0) ARRAY(0x1af0a20) ARRAY(0x1b0ab68) ARRAY(0x1ab3918) ARRAY(0x1a865b0) ARRAY(0x1b753e8) ARRAY(0x1afc510) ARRAY(0x1abf788)
be dumper@event = (
[
'path',
660,
'/usr/sbin/nscd',
'/usr/sbin/nscd',
'HINT',
'REJECTING',
65540,
'/proc/sys/vm/overcommit_memory',
''
]
);
$VAR2 = [
[
'path',
694,
'/usr/sbin/nscd',
'/usr/sbin/nscd',
'HINT',
'REJECTING',
65540,
'/proc/sys/vm/overcommit_memory',
''
]
];
$VAR3 = [
[
'path',
659,
'/usr/sbin/nscd',
'/usr/sbin/nscd',
'HINT',
'REJECTING',
65540,
'/proc/sys/vm/overcommit_memory',
''
]
];
$VAR4 = [
[
'path',
642,
'/usr/sbin/nscd',
'/usr/sbin/nscd',
'HINT',
'REJECTING',
65540,
'/proc/sys/vm/overcommit_memory',
''
]
];
$VAR5 = [
[
'path',
676,
'/usr/sbin/nscd',
'/usr/sbin/nscd',
'HINT',
'REJECTING',
65540,
'/proc/sys/vm/overcommit_memory',
''
]
];
$VAR6 = [
[
'path',
671,
'/usr/sbin/nscd',
'/usr/sbin/nscd',
'HINT',
'REJECTING',
65540,
'/proc/sys/vm/overcommit_memory',
''
]
];
$VAR7 = [
[
'path',
667,
'/usr/sbin/nscd',
'/usr/sbin/nscd',
'HINT',
'REJECTING',
65540,
'/proc/sys/vm/overcommit_memory',
''
]
];
$VAR8 = [
[
'path',
661,
'/usr/sbin/nscd',
'/usr/sbin/nscd',
'HINT',
'REJECTING',
65540,
'/proc/sys/vm/overcommit_memory',
''
]
];
$VAR9 = [
[
'path',
684,
'/usr/sbin/nscd',
'/usr/sbin/nscd',
'HINT',
'REJECTING',
65540,
'/proc/sys/vm/overcommit_memory',
''
]
];
$VAR10 = [
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
65540,
'/usr/lib64/empathy/libempathy-gtk-3.6.3.so',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
1114180,
'/usr/lib64/empathy/libempathy-gtk-3.6.3.so',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
65540,
'/usr/lib64/empathy/libempathy-3.6.3.so',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
1114180,
'/usr/lib64/empathy/libempathy-3.6.3.so',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
65540,
'/etc/ld.so.cache',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
65540,
'/usr/lib64/libdbus-glib-1.so.2.2.2',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
32770,
'/home/kshitij/.config/Empathy/geometry.ini.7YRV1W',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
98310,
'/home/kshitij/.config/Empathy/geometry.ini.7YRV1W',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
98310,
'/home/kshitij/.config/Empathy/geometry.ini.7YRV1W',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
32770,
'/home/kshitij/.config/Empathy/geometry.ini',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
98310,
'/run/user/1000/dconf/user',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
65540,
'/home/kshitij/.config/dconf/user',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
98310,
'/run/user/1000/dconf/user',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
65540,
'/home/kshitij/.config/dconf/user',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
98310,
'/run/user/1000/dconf/user',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
65540,
'/home/kshitij/.config/dconf/user',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
98310,
'/run/user/1000/dconf/user',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
65540,
'/home/kshitij/.config/dconf/user',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
98310,
'/run/user/1000/dconf/user',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
65540,
'/home/kshitij/.config/dconf/user',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
98310,
'/run/user/1000/dconf/user',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
65540,
'/home/kshitij/.config/dconf/user',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
98310,
'/run/user/1000/dconf/user',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
65540,
'/home/kshitij/.config/dconf/user',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
98310,
'/run/user/1000/dconf/user',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
65540,
'/home/kshitij/.config/dconf/user',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
98310,
'/run/user/1000/dconf/user',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
65540,
'/home/kshitij/.config/dconf/user',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
98310,
'/run/user/1000/dconf/user',
''
],
[
'path',
7664,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
65540,
'/home/kshitij/.config/dconf/user',
''
]
];
$VAR11 = [
[
'path',
7671,
'null-complain-profile',
'null-complain-profile',
'HINT',
'PERMITTING',
65540,
'/usr/share/icons/gnome/scalable/actions/list-add-symbolic.svg',
''
],
[
'path',
7671,
'null-complain-profile',
'null-complain-profile',
'HINT',
'PERMITTING',
65540,
'/usr/share/icons/gnome/scalable/actions/list-add-symbolic.svg',
''
],
[
'path',
7671,
'null-complain-profile',
'null-complain-profile',
'HINT',
'PERMITTING',
65540,
'/usr/share/icons/gnome/scalable/actions/list-remove-symbolic.svg',
''
],
[
'path',
7671,
'null-complain-profile',
'null-complain-profile',
'HINT',
'PERMITTING',
65540,
'/usr/share/icons/gnome/scalable/actions/list-remove-symbolic.svg',
''
],
[
'path',
7671,
'null-complain-profile',
'null-complain-profile',
'HINT',
'PERMITTING',
65540,
'/usr/share/icons/gnome/scalable/actions/list-add-symbolic.svg',
''
]
];
$VAR12 = [
[
'path',
7753,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
65540,
'/usr/lib64/empathy/libempathy-gtk-3.6.3.so',
''
],
[
'path',
7753,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
1114180,
'/usr/lib64/empathy/libempathy-gtk-3.6.3.so',
''
],
[
'path',
7753,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
65540,
'/usr/lib64/empathy/libempathy-3.6.3.so',
''
],
[
'path',
7753,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
1114180,
'/usr/lib64/empathy/libempathy-3.6.3.so',
''
],
[
'path',
7753,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
65540,
'/etc/ld.so.cache',
''
],
[
'path',
7753,
'/usr/bin/empathy',
'/usr/bin/empathy',
'HINT',
'PERMITTING',
65540,
'/usr/lib64/libdbus-glib-1.so.2.2.2',
''
]
];
\n end dumper ARRAY(0x1b65788)
Entry: ARRAY(0x1b65788)
ARRAY(0x1ac9188)
Entry: ARRAY(0x1ac9188)
ARRAY(0x1a98d98)
Entry: ARRAY(0x1a98d98)
ARRAY(0x1aae1c0)
Entry: ARRAY(0x1aae1c0)
ARRAY(0x1af09d8)
Entry: ARRAY(0x1af09d8)
ARRAY(0x1b6fa20)
Entry: ARRAY(0x1b6fa20)
ARRAY(0x1a9d920)
Entry: ARRAY(0x1a9d920)
ARRAY(0x1ae70b8)
Entry: ARRAY(0x1ae70b8)
ARRAY(0x1b0fac0)
Entry: ARRAY(0x1b0fac0)
ARRAY(0xbbe340) ARRAY(0x1b54130) ARRAY(0x1b18578) ARRAY(0x1b040e0) ARRAY(0x1b73600) ARRAY(0x1a9e298) ARRAY(0x1b0a370) ARRAY(0x1adfb70) ARRAY(0x1ac9008) ARRAY(0x1ac1aa0) ARRAY(0x1ab8428) ARRAY(0x1b12e90) ARRAY(0x1a89ab0) ARRAY(0x1aaa6d8) ARRAY(0x1b4d400) ARRAY(0x1b4d5c8) ARRAY(0x1adb230) ARRAY(0x1aadb60) ARRAY(0x1ab3d68) ARRAY(0x1b75fb8) ARRAY(0x1aaa588) ARRAY(0x1b4dcd0) ARRAY(0x1a96c48) ARRAY(0x1adf468) ARRAY(0x1b5ca10) ARRAY(0x1aa76e8) ARRAY(0x1ab8cf8) ARRAY(0x1b72df0) ARRAY(0x1ae9630) ARRAY(0x1a86a90)
Entry: ARRAY(0xbbe340)
Entry: ARRAY(0x1b54130)
Entry: ARRAY(0x1b18578)
Entry: ARRAY(0x1b040e0)
Entry: ARRAY(0x1b73600)
Entry: ARRAY(0x1a9e298)
Entry: ARRAY(0x1b0a370)
Entry: ARRAY(0x1adfb70)
Entry: ARRAY(0x1ac9008)
Entry: ARRAY(0x1ac1aa0)
Entry: ARRAY(0x1ab8428)
Entry: ARRAY(0x1b12e90)
Entry: ARRAY(0x1a89ab0)
Entry: ARRAY(0x1aaa6d8)
Entry: ARRAY(0x1b4d400)
Entry: ARRAY(0x1b4d5c8)
Entry: ARRAY(0x1adb230)
Entry: ARRAY(0x1aadb60)
Entry: ARRAY(0x1ab3d68)
Entry: ARRAY(0x1b75fb8)
Entry: ARRAY(0x1aaa588)
Entry: ARRAY(0x1b4dcd0)
Entry: ARRAY(0x1a96c48)
Entry: ARRAY(0x1adf468)
Entry: ARRAY(0x1b5ca10)
Entry: ARRAY(0x1aa76e8)
Entry: ARRAY(0x1ab8cf8)
Entry: ARRAY(0x1b72df0)
Entry: ARRAY(0x1ae9630)
Entry: ARRAY(0x1a86a90)
ARRAY(0x1afc540) ARRAY(0x1b15f38) ARRAY(0x1ab8b60) ARRAY(0x1abf7b8) ARRAY(0x1af6378)
Entry: ARRAY(0x1afc540)
Entry: ARRAY(0x1b15f38)
Entry: ARRAY(0x1ab8b60)
Entry: ARRAY(0x1abf7b8)
Entry: ARRAY(0x1af6378)
ARRAY(0x1b61898) ARRAY(0x1b12ec0) ARRAY(0x1af8bf0) ARRAY(0x1b0feb0) ARRAY(0x1b05ab0) ARRAY(0x1ab8470)
Entry: ARRAY(0x1b61898)
Entry: ARRAY(0x1b12ec0)
Entry: ARRAY(0x1af8bf0)
Entry: ARRAY(0x1b0feb0)
Entry: ARRAY(0x1b05ab0)
Entry: ARRAY(0x1ab8470)
Complain-mode changes:
Profile: /usr/bin/empathy
Path: /etc/ld.so.cache
Mode: r
Severity: 1
1 - #include <abstractions/base>
2 - #include <abstractions/gnome>
3 - #include <abstractions/kde>
4 - #include <abstractions/ubuntu-gnome-terminal>
5 -

View File

@@ -17,6 +17,7 @@ import atexit
import tempfile
import apparmor.config
import apparmor.logparser
import apparmor.severity
import LibAppArmor
@@ -61,7 +62,7 @@ user_globs = []
## Variables used under logprof
### Were our
t = hasher()#dict()
transitions = dict()
transitions = hasher()
aa = hasher() # Profiles originally in sd, replace by aa
original_aa = hasher()
extras = hasher() # Inactive profiles from extras
@@ -70,7 +71,7 @@ log = []
pid = dict()
seen = hasher()#dir()
profile_changes = dict()
profile_changes = hasher()
prelog = hasher()
log_dict = hasher()#dict()
changed = dict()
@@ -215,7 +216,7 @@ def check_for_apparmor():
def which(file):
"""Returns the executable fullpath for the file, None otherwise"""
env_dirs = os.getenv('AAPATH').split(':')
env_dirs = os.getenv('PATH').split(':')
for env_dir in env_dirs:
env_path = env_dir + '/' + file
# Test if the path is executable or not
@@ -244,7 +245,7 @@ def get_full_path(original_path):
return os.path.realpath(path)
def find_executable(bin_path):
"""Returns the full executable path for the binary given, None otherwise"""
"""Returns the full executable path for the given, None otherwise"""
full_bin = None
if os.path.exists(bin_path):
full_bin = get_full_path(bin_path)
@@ -259,7 +260,9 @@ def find_executable(bin_path):
def get_profile_filename(profile):
"""Returns the full profile name"""
if profile.startswith('/'):
if existing_profiles.get(profile, False):
return existing_profiles[profile]
elif profile.startswith('/'):
# Remove leading /
profile = profile[1:]
else:
@@ -370,14 +373,8 @@ def handle_binfmt(profile, path):
library = glob_common(library)
if not library:
continue
try:
profile['allow']['path'][library]['mode'] |= str_to_mode('mr')
except TypeError:
profile['allow']['path'][library]['mode'] = str_to_mode('mr')
try:
profile['allow']['path'][library]['audit'] |= 0
except TypeError:
profile['allow']['path'][library]['audit'] = 0
profile['allow']['path'][library]['mode'] = profile['allow']['path'][library].get('mode', set()) | str_to_mode('mr')
profile['allow']['path'][library]['audit'] |= profile['allow']['path'][library].get('audit', set())
def get_inactive_profile(local_profile):
if extras.get(local_profile, False):
@@ -404,11 +401,11 @@ def create_new_profile(localfile):
local_profile[localfile]['allow']['path'][localfile]['mode'] = local_profile[localfile]['allow']['path'][localfile].get('mode', str_to_mode('r')) | str_to_mode('r')
local_profile[localfile]['allow']['path'][localfile]['audit'] = local_profile[localfile]['allow']['path'][localfile].get('audit', 0)
local_profile[localfile]['allow']['path'][localfile]['audit'] = local_profile[localfile]['allow']['path'][localfile].get('audit', set())
local_profile[localfile]['allow']['path'][interpreter_path]['mode'] = local_profile[localfile]['allow']['path'][interpreter_path].get('mode', str_to_mode('ix')) | str_to_mode('ix')
local_profile[localfile]['allow']['path'][interpreter_path]['audit'] = local_profile[localfile]['allow']['path'][interpreter_path].get('audit', 0)
local_profile[localfile]['allow']['path'][interpreter_path]['audit'] = local_profile[localfile]['allow']['path'][interpreter_path].get('audit', set())
if interpreter == 'perl':
local_profile[localfile]['include']['abstractions/perl'] = True
@@ -619,6 +616,7 @@ def set_profile_flags(prof_filename, newflags):
def profile_exists(program):
"""Returns True if profile exists, False otherwise"""
# Check cache of profiles
if existing_profiles.get(program, False):
return True
# Check the disk for profile
@@ -626,7 +624,7 @@ def profile_exists(program):
#print(prof_path)
if os.path.isfile(prof_path):
# Add to cache of profile
existing_profiles[program] = True
existing_profiles[program] = prof_path
return True
return False
@@ -897,7 +895,7 @@ def handle_children(profile, hat, root):
profile_changes[pid] = profile + '//' + hat
else:
profile_changes[pid] = profile
elif type == 'unknown_hat':
elif typ == 'unknown_hat':
pid, p, h, aamode, uhat = entry[:5]
if not regex_nullcomplain.search(p):
profile = p
@@ -952,7 +950,7 @@ def handle_children(profile, hat, root):
elif ans == 'CMD_DENY':
return None
elif type == 'capability':
elif typ == 'capability':
pid, p, h, prog, aamode, capability = entry[:6]
if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h):
profile = p
@@ -961,9 +959,8 @@ def handle_children(profile, hat, root):
continue
prelog[aamode][profile][hat]['capability'][capability] = True
elif type == 'path' or type == 'exec':
elif typ == 'path' or typ == 'exec':
pid, p, h, prog, aamode, mode, detail, to_name = entry[:8]
if not mode:
mode = 0
if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h):
@@ -973,7 +970,7 @@ def handle_children(profile, hat, root):
continue
domainchange = 'nochange'
if type == 'exec':
if typ == 'exec':
domainchange = 'change'
# Escape special characters
@@ -1346,7 +1343,7 @@ def handle_children(profile, hat, root):
if not aa[profile].get(exec_target, False):
ynans = 'y'
if exec_mode & str_to_mode('i'):
ynans = UI_YesNo(_('A local profile for %s does not exit. Create one') % exec_target, 'n')
ynans = UI_YesNo(_('A local profile for %s does not exit. Create one?') % exec_target, 'n')
if ynans == 'y':
hat = exec_target
aa[profile][hat]['declared'] = False
@@ -1377,7 +1374,7 @@ def handle_children(profile, hat, root):
if domainchange == 'change':
return None
elif type == 'netdomain':
elif typ == 'netdomain':
pid, p, h, prog, aamode, family, sock_type, protocol = entry[:8]
if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h):
@@ -1390,296 +1387,12 @@ def handle_children(profile, hat, root):
return None
def add_to_tree(loc_pid, parent, type, event):
debug_logger.info('add_to_tree: pid [%s] type [%s] event [%s]' % (loc_pid, type, event))
if not pid.get(loc_pid, False):
profile, hat = event[:2]
if parent and pid.get(parent, False):
if not hat:
hat = 'null-complain-profile'
array_ref = ['fork', loc_pid, profile, hat]
pid[parent].append(array_ref)
pid[loc_pid] = array_ref
#else:
# array_ref = []
# log.append(array_ref)
# pid[pid] = array_ref
pid[loc_pid] = pid.get(loc_pid, []) + [type, loc_pid, event]
# Variables used by logparsing routines
LOG = None
next_log_entry = None
logmark = None
seenmark = None
RE_LOG_v2_6_syslog = re.compile('kernel:\s+(\[[\d\.\s]+\]\s+)?type=\d+\s+audit\([\d\.\:]+\):\s+apparmor=')
RE_LOG_v2_6_audit = re.compile('type=AVC\s+(msg=)?audit\([\d\.\:]+\):\s+apparmor=')
MODE_MAP_RE = re.compile('r|w|l|m|k|a|x|i|u|p|c|n|I|U|P|C|N')
LOG_MODE_RE = re.compile('r|w|l|m|k|a|x|ix|ux|px|cx|nx|pix|cix|Ix|Ux|Px|PUx|Cx|Nx|Pix|Cix')
PROFILE_MODE_RE = re.compile('r|w|l|m|k|a|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix')
PROFILE_MODE_NT_RE = re.compile('r|w|l|m|k|a|x|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix')
PROFILE_MODE_DENY_RE = re.compile('r|w|l|m|k|a|x')
def prefetch_next_log_entry():
if next_log_entry:
sys.stderr.out('A log entry already present: %s' % next_log_entry)
next_log_entry = LOG.readline()
while RE_LOG_v2_6_syslog.search(next_log_entry) or RE_LOG_v2_6_audit.search(next_log_entry) or re.search(logmark, next_log_entry):
next_log_entry = LOG.readline()
if not next_log_entry:
break
def get_next_log_entry():
# If no next log entry fetch it
if not next_log_entry:
prefetch_next_log_entry()
log_entry = next_log_entry
next_log_entry = None
return log_entry
def peek_at_next_log_entry():
# Take a peek at the next log entry
if not next_log_entry:
prefetch_next_log_entry()
return next_log_entry
def throw_away_next_log_entry():
next_log_entry = None
def parse_log_record(record):
debug_logger.debug('parse_log_record: %s' % record)
record_event = parse_event(record)
return record_event
def add_event_to_tree(e):
aamode = e.get('aamode', 'UNKNOWN')
if e.get('type', False):
if re.search('(UNKNOWN\[1501\]|APPARMOR_AUDIT|1501)', e['type']):
aamode = 'AUDIT'
elif re.search('(UNKNOWN\[1502\]|APPARMOR_ALLOWED|1502)', e['type']):
aamode = 'PERMITTING'
elif re.search('(UNKNOWN\[1503\]|APPARMOR_DENIED|1503)', e['type']):
aamode = 'REJECTING'
elif re.search('(UNKNOWN\[1504\]|APPARMOR_HINT|1504)', e['type']):
aamode = 'HINT'
elif re.search('(UNKNOWN\[1505\]|APPARMOR_STATUS|1505)', e['type']):
aamode = 'STATUS'
elif re.search('(UNKNOWN\[1506\]|APPARMOR_ERROR|1506)', e['type']):
aamode = 'ERROR'
else:
aamode = 'UNKNOWN'
if aamode in ['UNKNOWN', 'AUDIT', 'STATUS', 'ERROR']:
return None
if 'profile_set' in e['operation']:
return None
# Skip if AUDIT event was issued due to a change_hat in unconfined mode
if not e.get('profile', False):
return None
# Convert new null profiles to old single level null profile
if '//null-' in e['profile']:
e['profile'] = 'null-complain-profile'
profile = e['profile']
hat = None
if '\\' in e['profile']:
profile, hat = e['profile'].split('\\')
# Filter out change_hat events that aren't from learning
if e['operation'] == 'change_hat':
if aamode != 'HINT' and aamode != 'PERMITTING':
return None
profile = e['name']
if '\\' in e['name']:
profile, hat = e['name'].split('\\')
# prog is no longer passed around consistently
prog = 'HINT'
if profile != 'null-complain-profile' and not profile_exists(profile):
return None
if e['operation'] == 'exec':
if e.get('info', False) and e['info'] == 'mandatory profile missing':
add_to_tree(e['pid'], e['parent'], 'exec',
[profile, hat, aamode, 'PERMITTING', e['denied_mask'], e['name'], e['name2']])
elif e.get('name2', False) and '\\null-/' in e['name2']:
add_to_tree(e['pid'], e['parent'], 'exec',
[profile, hat, prog, aamode, e['denied_mask'], e['name'], ''])
elif e.get('name', False):
add_to_tree(e['pid'], e['parent'], 'exec',
[profile, hat, prog, aamode, e['denied_mask'], e['name'], ''])
else:
debug_logger.debug('add_event_to_tree: dropped exec event in %s' % e['profile'])
elif 'file_' in e['operation']:
add_to_tree(e['pid'], e['parent'], 'path',
[profile, hat, prog, aamode, e['denied_mask'], e['name'], ''])
elif e['operation'] in ['open', 'truncate', 'mkdir', 'mknod', 'rename_src',
'rename_dest', 'unlink', 'rmdir', 'symlink_create', 'link']:
add_to_tree(e['pid'], e['parent'], 'path',
[profile, hat, prog, aamode, e['denied_mask'], e['name'], ''])
elif e['operation'] == 'capable':
add_to_tree(e['pid'], e['parent'], 'capability',
[profile, hat, prog, aamode, e['name'], ''])
elif e['operation'] == 'setattr' or 'xattr' in e['operation']:
add_to_tree(e['pid'], e['parent'], 'path',
[profile, hat, prog, aamode, e['denied_mask'], e['name'], ''])
elif 'inode_' in e['operation']:
is_domain_change = False
if e['operation'] == 'inode_permission' and (e['denied_mask'] & AA_MAY_EXEC) and aamode == 'PERMITTING':
following = peek_at_next_log_entry()
if following:
entry = parse_log_record(following)
if entry and entry.get('info', False) == 'set profile':
is_domain_change = True
throw_away_next_log_entry()
if is_domain_change:
add_to_tree(e['pid'], e['parent'], 'exec',
[profile, hat, prog, aamode, e['denied_mask'], e['name'], e['name2']])
else:
add_to_tree(e['pid'], e['parent'], 'path',
[profile, hat, prog, aamode, e['denied_mask'], e['name'], ''])
elif e['operation'] == 'sysctl':
add_to_tree(e['pid'], e['parent'], 'path',
[profile, hat, prog, aamode, e['denied_mask'], e['name'], ''])
elif e['operation'] == 'clone':
parent , child = e['pid'], e['task']
if not parent:
parent = 'null-complain-profile'
if not hat:
hat = 'null-complain-profile'
arrayref = ['fork', child, profile, hat]
if pid.get(parent, False):
pid[parent] += [arrayref]
else:
log += [arrayref]
pid[child] = arrayref
elif op_type(e['operation']) == 'net':
add_to_tree(e['pid'], e['parent'], 'netdomain',
[profile, hat, prog, aamode, e['family'], e['sock_type'], e['protocol']])
elif e['operation'] == 'change_hat':
add_to_tree(e['pid'], e['parent'], 'unknown_hat',
[profile, hat, aamode, hat])
else:
debug_logger.debug('UNHANDLED: %s' % e)
def read_log(logmark):
seenmark = True
if logmark:
seenmark = False
#last = None
#event_type = None
try:
#print(filename)
log_open = open_file_read(filename)
except IOError:
raise AppArmorException('Can not read AppArmor logfile: ' + filename)
with log_open as f_in:
for line in f_in:
line = line.strip()
debug_logger.debug('read_log: %s' % line)
if logmark in line:
seenmark = True
if not seenmark:
debug_logger.debug('read_log: seenmark = %s' % seenmark)
event = parse_log_record(line)
if event:
add_event_to_tree(event)
logmark = ''
def parse_event(msg):
"""Parse the event from log into key value pairs"""
msg = msg.strip()
debug_logger.info('parse_event: %s' % msg)
#print(repr(msg))
if sys.version_info < (3,0):
# parse_record fails with u'foo' style strings hence typecasting to string
msg = str(msg)
event = LibAppArmor.parse_record(msg)
ev = dict()
ev['resource'] = event.info
ev['active_hat'] = event.active_hat
ev['aamode'] = event.event
ev['time'] = event.epoch
ev['operation'] = event.operation
ev['profile'] = event.profile
ev['name'] = event.name
ev['name2'] = event.name2
ev['attr'] = event.attribute
ev['parent'] = event.parent
ev['pid'] = event.pid
ev['task'] = event.task
ev['info'] = event.info
dmask = event.denied_mask
rmask = event.requested_mask
ev['magic_token'] = event.magic_token
if ev['operation'] and op_type(ev['operation']) == 'net':
ev['family'] = event.net_family
ev['protocol'] = event.net_protocol
ev['sock_type'] = event.net_sock_type
LibAppArmor.free_record(event)
# Map c (create) to a and d (delete) to w, logprof doesn't support c and d
if rmask:
rmask = rmask.replace('c', 'a')
rmask = rmask.replace('d', 'w')
if not validate_log_mode(hide_log_mode(rmask)):
fatal_error(_('Log contains unknown mode %s') % rmask)
if dmask:
dmask = dmask.replace('c', 'a')
dmask = dmask.replace('d', 'w')
if not validate_log_mode(hide_log_mode(dmask)):
fatal_error(_('Log contains unknown mode %s') % dmask)
#print('parse_event:', ev['profile'], dmask, ev['name2'])
mask, name = log_str_to_mode(ev['profile'], dmask, ev['name2'])
ev['denied_mask'] = mask
ev['name2'] = name
mask, name = log_str_to_mode(ev['profile'], rmask, ev['name2'])
ev['request_mask'] = mask
ev['name2'] = name
if not ev['time']:
ev['time'] = int(time.time())
# Remove None keys
#for key in ev.keys():
# if not ev[key] or not re.search('[\w]+', ev[key]):
# ev.pop(key)
if ev['aamode']:
# Convert aamode values to their counter-parts
mode_convertor = {
0: 'UNKNOWN',
1: 'ERROR',
2: 'AUDITING',
3: 'PERMITTING',
4: 'REJECTING',
5: 'HINT',
6: 'STATUS'
}
try:
ev['aamode'] = mode_convertor[ev['aamode']]
except KeyError:
ev['aamode'] = None
if ev['aamode']:
#debug_logger.debug(ev)
return ev
else:
return None
def hide_log_mode(mode):
mode = mode.replace('::', '')
return mode
@@ -1747,8 +1460,8 @@ def order_globs(globs, path):
return sorted(globs)
def ask_the_questions():
found = None
print(log_dict)
found = 0
global seen_events
for aamode in sorted(log_dict.keys()):
# Describe the type of changes
if aamode == 'PERMITTING':
@@ -1939,14 +1652,15 @@ def ask_the_questions():
# If already present skip
if aa[profile][hat][incname]:
continue
if incname.startswith(profile_dir):
incname = incname.replace(profile_dir+'/', '', 1)
include_valid = valid_include(profile, incname)
include_valid = valid_include('', incname)
if not include_valid:
continue
cm, am, m = match_include_to_path(incname, 'allow', path)
if 'base' in incname: print(cm,am,m,mode,mode_contains(cm, mode))
if cm and mode_contains(cm, mode):
dm = match_include_to_path(incname, 'deny', path)
# If the mode is denied
@@ -2041,7 +1755,7 @@ def ask_the_questions():
q['options'] = options
q['selected'] = default_option - 1
q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_GLOB',
'CMD_GLOBTEXT', 'CMD_NEW', 'CMD_ABORT',
'CMD_GLOBEXT', 'CMD_NEW', 'CMD_ABORT',
'CMD_FINISHED', 'CMD_OTHER', 'CMD_IGNORE_ENTRY']
q['default'] = 'CMD_DENY'
if aamode == 'PERMITTING':
@@ -2183,16 +1897,16 @@ def ask_the_questions():
if match:
# /foo/**.ext and /foo/*.ext => /**.ext
newpath = re.sub('/[^/]+/\*{1,2}\.[^/]+$', '/**'+match.group()[0], newpath)
elif re.search('/[^/]+\*\*[^/]*\.[^/]+$'):
elif re.search('/[^/]+\*\*[^/]*\.[^/]+$', newpath):
# /foo**.ext and /foo**bar.ext => /**.ext
match = re.search('/[^/]+\*\*[^/]*(\.[^/]+)$')
match = re.search('/[^/]+\*\*[^/]*(\.[^/]+)$', newpath)
newpath = re.sub('/[^/]+\*\*[^/]*\.[^/]+$', '/**'+match.groups()[0], newpath)
elif re.search('/\*\*[^/]+\.[^/]+$'):
elif re.search('/\*\*[^/]+\.[^/]+$', newpath):
# /**foo.ext => /**.ext
match = re.search('/\*\*[^/]+(\.[^/]+)$')
match = re.search('/\*\*[^/]+(\.[^/]+)$', newpath)
newpath = re.sub('/\*\*[^/]+\.[^/]+$', '/**'+match.groups()[0], newpath)
else:
match = re.search('(\.[^/]+)$')
match = re.search('(\.[^/]+)$', newpath)
newpath = re.sub('/[^/]+(\.[^/]+)$', '/*'+match.groups()[0], newpath)
if newpath not in options:
@@ -2388,7 +2102,7 @@ def re_match_include(path):
return None
def valid_include(profile, incname):
if profile['include'].get(incname, False):
if profile and profile['include'].get(incname, False):
return False
if cfg['settings']['custom_includes']:
@@ -2410,19 +2124,22 @@ def match_net_includes(profile, family, nettype):
return newincludes
def do_logprof_pass(logmark='', sev_db=sev_db):
def do_logprof_pass(logmark='', pid=pid, existing_profiles=existing_profiles):
# set up variables for this pass
t = hasher()
transitions = hasher()
# transitions = hasher()
seen = hasher()
aa = hasher()
profile_changes = hasher()
prelog = hasher()
global log
log = []
log_dict = hasher()
changed = dict()
global sev_db
# aa = hasher()
# profile_changes = hasher()
# prelog = hasher()
# log = []
# log_dict = hasher()
# changed = dict()
skip = hasher()
filelist = hasher()
# filelist = hasher()
UI_Info(_('Reading log entries from %s.') %filename)
UI_Info(_('Updating AppArmor profiles in %s.') %profile_dir)
@@ -2431,17 +2148,21 @@ def do_logprof_pass(logmark='', sev_db=sev_db):
if not sev_db:
sev_db = apparmor.severity.Severity(CONFDIR + '/severity.db', _('unknown'))
#print(pid)
#print(existing_profiles)
##if not repo_cf and cfg['repostory']['url']:
## repo_cfg = read_config('repository.conf')
## if not repo_cfg['repository'].get('enabled', False) or repo_cfg['repository]['enabled'] not in ['yes', 'no']:
## UI_ask_to_enable_repo()
read_log(logmark)
log_reader = apparmor.logparser.ReadLog(pid, filename, existing_profiles, profile_dir, log)
log = log_reader.read_log(logmark)
#read_log(logmark)
for root in log:
handle_children('', '', root)
#for root in range(len(log)):
#log[root] = handle_children('', '', log[root])
#print(log)
for pid in sorted(profile_changes.keys()):
set_process(pid, profile_changes[pid])
@@ -2624,27 +2345,27 @@ def collapse_log():
combinedmode |= aa[profile][hat]['allow']['path'][path]
# Match path to regexps in profile
combinedmode |= rematchfrag(aa[profile][hat], 'allow', path)
combinedmode |= rematchfrag(aa[profile][hat], 'allow', path)[0]
# Match path from includes
combinedmode |= match_prof_incs_to_path(aa[profile][hat], 'allow', path)
combinedmode |= match_prof_incs_to_path(aa[profile][hat], 'allow', path)[0]
if not combinedmode or not mode_contains(combinedmode, mode):
if log[aamode][profile][hat]['path'].get(path, False):
mode |= log[aamode][profile][hat]['path'][path]
if log_dict[aamode][profile][hat]['path'].get(path, False):
mode |= log_dict[aamode][profile][hat]['path'][path]
log[aamode][profile][hat]['path'][path] = mode
log_dict[aamode][profile][hat]['path'][path] = mode
for capability in prelog[aamode][profile][hat]['capability'].keys():
# If capability not already in profile
if not aa[profile][hat]['allow']['capability'][capability].get('set', False):
log[aamode][profile][hat]['capability'][capability] = True
log_dict[aamode][profile][hat]['capability'][capability] = True
nd = prelog[aamode][profile][hat]['netdomain']
for family in nd.keys():
for sock_type in nd[family].keys():
if not profile_known_network(aa[profile][hat], family, sock_type):
log[aamode][profile][hat]['netdomain'][family][sock_type] = True
log_dict[aamode][profile][hat]['netdomain'][family][sock_type] = True
def profilemode(mode):
pass
@@ -2905,6 +2626,7 @@ def read_profiles():
if is_skippable_file(file):
continue
else:
#print('read %s' %file)
read_profile(profile_dir + '/' + file, True)
def read_inactive_profiles():
@@ -2932,6 +2654,7 @@ def read_profile(file, active_profile):
return None
profile_data = parse_profile_data(data, file, 0)
if profile_data and active_profile:
attach_profile_data(aa, profile_data)
attach_profile_data(original_aa, profile_data)
@@ -2953,7 +2676,7 @@ def parse_profile_data(data, file, do_include):
repo_data = None
parsed_profiles = []
initial_comment = ''
RE_PROFILE_START = re.compile('^(("??\/.+?"??)|(profile\s+("??.+?"??)))\s+((flags=)?\((.+)\)\s+)?\{\s*(#.*)?$')
RE_PROFILE_START = re.compile('^(("??/.+?"??)|(profile\s+("??.+?"??)))\s+((flags=)?\((.+)\)\s+)?\{\s*(#.*)?$')
RE_PROFILE_END = re.compile('^\}\s*(#.*)?$')
RE_PROFILE_CAP = re.compile('^(audit\s+)?(allow\s+|deny\s+)?capability\s+(\S+)\s*,\s*(#.*)?$')
RE_PROFILE_LINK = re.compile('^(audit\s+)?(allow\s+|deny\s+)?link\s+(((subset)|(<=))\s+)?([\"\@\/].*?"??)\s+->\s*([\"\@\/].*?"??)\s*,\s*(#.*)?$')
@@ -3006,6 +2729,8 @@ def parse_profile_data(data, file, do_include):
profile_data[profile][hat]['external'] = True
else:
hat = profile
# Profile stored
existing_profiles[profile] = file
flags = matches[6]
@@ -3362,11 +3087,13 @@ def store_list_var(var, list_var, value, var_operation):
print('Ignored: New definition for variable for:',list_var,'=', value, 'operation was:',var_operation,'old value=', var[list_var])
pass
#raise AppArmorException('An existing variable redefined: %s' %list_var)
else:
elif var_operation == '+=':
if var.get(list_var, False):
var[list_var] = set(var[list_var] + vlist)
else:
raise AppArmorException('An existing variable redefined: %s' %list_var)
raise AppArmorException('Values added to a non-existing variable: %s' %list_var)
else:
raise AppArmorException('Unknown variable operation: %s' %var_operation)
def strip_quotes(data):
@@ -3870,7 +3597,7 @@ def match_include_to_path(incname, allow, path):
combinedmode = 0
combinedaudit = 0
matches = []
incname = profile_dir + '/' + incname
includelist = [incname]
while includelist:
incfile = includelist.pop(0)

258
apparmor/aamode.py Normal file
View File

@@ -0,0 +1,258 @@
import re
AA_MAY_EXEC = set('x')
AA_MAY_WRITE = set('w')
AA_MAY_READ = set('r')
AA_MAY_APPEND = set('a')
AA_MAY_LINK = set('l')
AA_MAY_LOCK = set('k')
AA_EXEC_MMAP = set('m')
AA_EXEC_UNSAFE = set('unsafe')
AA_EXEC_INHERIT = set('i')
AA_EXEC_UNCONFINED = set('U')
AA_EXEC_PROFILE = set('P')
AA_EXEC_CHILD = set('C')
AA_EXEC_NT = set('N')
AA_LINK_SUBSET = set('ls')
AA_OTHER_SHIFT = 14
AA_USER_MASK = 16384 - 1
AA_EXEC_TYPE = (AA_MAY_EXEC | AA_EXEC_UNSAFE | AA_EXEC_INHERIT |
AA_EXEC_UNCONFINED | AA_EXEC_PROFILE | AA_EXEC_CHILD | AA_EXEC_NT)
MODE_HASH = {'x': AA_MAY_EXEC, 'X': AA_MAY_EXEC,
'w': AA_MAY_WRITE, 'W': AA_MAY_WRITE,
'r': AA_MAY_READ, 'R': AA_MAY_READ,
'a': AA_MAY_APPEND, 'A': AA_MAY_APPEND,
'l': AA_MAY_LINK, 'L': AA_MAY_LINK,
'k': AA_MAY_LOCK, 'K': AA_MAY_LOCK,
'm': AA_EXEC_MMAP, 'M': AA_EXEC_MMAP,
'i': AA_EXEC_INHERIT, 'I': AA_EXEC_INHERIT,
'u': AA_EXEC_UNCONFINED + AA_EXEC_UNSAFE, # Unconfined + Unsafe
'U': AA_EXEC_UNCONFINED,
'p': AA_EXEC_PROFILE + AA_EXEC_UNSAFE, # Profile + unsafe
'P': AA_EXEC_PROFILE,
'c': AA_EXEC_CHILD + AA_EXEC_UNSAFE, # Child + Unsafe
'C': AA_EXEC_CHILD,
'n': AA_EXEC_NT + AA_EXEC_UNSAFE,
'N': AA_EXEC_NT
}
LOG_MODE_RE = re.compile('r|w|l|m|k|a|x|ix|ux|px|cx|nx|pix|cix|Ix|Ux|Px|PUx|Cx|Nx|Pix|Cix')
MODE_MAP_RE = re.compile('r|w|l|m|k|a|x|i|u|p|c|n|I|U|P|C|N')
def str_to_mode(string):
if not string:
return set()
user, other = split_log_mode(string)
if not user:
user = other
mode = sub_str_to_mode(user)
#print(string, mode)
#print(string, 'other', sub_str_to_mode(other))
mode |= (sub_str_to_mode(other) << AA_OTHER_SHIFT)
#print (string, mode)
#print('str_to_mode:', mode)
return mode
def sub_str_to_mode(string):
mode = set()
if not string:
return mode
while string:
pattern = '(%s)' % MODE_MAP_RE.pattern
tmp = re.search(pattern, string)
if tmp:
tmp = tmp.groups()[0]
string = re.sub(pattern, '', string)
if tmp and MODE_HASH.get(tmp, False):
mode |= MODE_HASH[tmp]
else:
pass
return mode
def split_log_mode(mode):
user = ''
other = ''
match = re.search('(.*?)::(.*)', mode)
if match:
user, other = match.groups()
else:
user = mode
other = mode
#print ('split_logmode:', user, mode)
return user, other
def mode_contains(mode, subset):
# w implies a
if mode & AA_MAY_WRITE:
mode |= AA_MAY_APPEND
if mode & (AA_MAY_WRITE << AA_OTHER_SHIFT):
mode |= (AA_MAY_APPEND << AA_OTHER_SHIFT)
return (mode & subset) == subset
def contains(mode, string):
return mode_contains(mode, str_to_mode(string))
def validate_log_mode(mode):
pattern = '^(%s)+$' % LOG_MODE_RE.pattern
if re.search(pattern, mode):
#if LOG_MODE_RE.search(mode):
return True
else:
return False
def hide_log_mode(mode):
mode = mode.replace('::', '')
return mode
AA_MAY_EXEC = 1
AA_MAY_WRITE = 2
AA_MAY_READ = 4
AA_MAY_APPEND = 8
AA_MAY_LINK = 16
AA_MAY_LOCK = 32
AA_EXEC_MMAP = 64
AA_EXEC_UNSAFE = 128
AA_EXEC_INHERIT = 256
AA_EXEC_UNCONFINED = 512
AA_EXEC_PROFILE = 1024
AA_EXEC_CHILD = 2048
AA_EXEC_NT = 4096
AA_LINK_SUBSET = 8192
AA_OTHER_SHIFT = 14
AA_USER_MASK = 16384 - 1
AA_EXEC_TYPE = (AA_MAY_EXEC | AA_EXEC_UNSAFE | AA_EXEC_INHERIT |
AA_EXEC_UNCONFINED | AA_EXEC_PROFILE | AA_EXEC_CHILD | AA_EXEC_NT)
ALL_AA_EXEC_TYPE = AA_EXEC_TYPE # The same value
# Modes and their values
MODE_HASH = {'x': AA_MAY_EXEC, 'X': AA_MAY_EXEC,
'w': AA_MAY_WRITE, 'W': AA_MAY_WRITE,
'r': AA_MAY_READ, 'R': AA_MAY_READ,
'a': AA_MAY_APPEND, 'A': AA_MAY_APPEND,
'l': AA_MAY_LINK, 'L': AA_MAY_LINK,
'k': AA_MAY_LOCK, 'K': AA_MAY_LOCK,
'm': AA_EXEC_MMAP, 'M': AA_EXEC_MMAP,
'i': AA_EXEC_INHERIT, 'I': AA_EXEC_INHERIT,
'u': AA_EXEC_UNCONFINED + AA_EXEC_UNSAFE, # Unconfined + Unsafe
'U': AA_EXEC_UNCONFINED,
'p': AA_EXEC_PROFILE + AA_EXEC_UNSAFE, # Profile + unsafe
'P': AA_EXEC_PROFILE,
'c': AA_EXEC_CHILD + AA_EXEC_UNSAFE, # Child + Unsafe
'C': AA_EXEC_CHILD,
'n': AA_EXEC_NT + AA_EXEC_UNSAFE,
'N': AA_EXEC_NT
}
def log_str_to_mode(profile, string, nt_name):
mode = str_to_mode(string)
# If contains nx and nix
#print (profile, string, nt_name)
if contains(mode, 'Nx'):
# Transform to px, cx
match = re.search('(.+?)//(.+?)', nt_name)
if match:
lprofile, lhat = match.groups()
tmode = 0
if lprofile == profile:
if mode & AA_MAY_EXEC:
tmode = str_to_mode('Cx::')
if mode & (AA_MAY_EXEC << AA_OTHER_SHIFT):
tmode |= str_to_mode('Cx')
nt_name = lhat
else:
if mode & AA_MAY_EXEC:
tmode = str_to_mode('Px::')
if mode & (AA_MAY_EXEC << AA_OTHER_SHIFT):
tmode |= str_to_mode('Px')
nt_name = lhat
mode = mode & ~str_to_mode('Nx')
mode |= tmode
return mode, nt_name
def hide_log_mode(mode):
mode = mode.replace('::', '')
return mode
def validate_log_mode(mode):
pattern = '^(%s)+$' % LOG_MODE_RE.pattern
if re.search(pattern, mode):
#if LOG_MODE_RE.search(mode):
return True
else:
return False
def str_to_mode(string):
if not string:
return 0
user, other = split_log_mode(string)
if not user:
user = other
mode = sub_str_to_mode(user)
#print(string, mode)
#print(string, 'other', sub_str_to_mode(other))
mode |= (sub_str_to_mode(other) << AA_OTHER_SHIFT)
#print (string, mode)
#print('str_to_mode:', mode)
return mode
def mode_contains(mode, subset):
# w implies a
if mode & AA_MAY_WRITE:
mode |= AA_MAY_APPEND
if mode & (AA_MAY_WRITE << AA_OTHER_SHIFT):
mode |= (AA_MAY_APPEND << AA_OTHER_SHIFT)
# ix does not imply m
### ix implies m
##if mode & AA_EXEC_INHERIT:
## mode |= AA_EXEC_MMAP
##if mode & (AA_EXEC_INHERIT << AA_OTHER_SHIFT):
## mode |= (AA_EXEC_MMAP << AA_OTHER_SHIFT)
return (mode & subset) == subset
def contains(mode, string):
return mode_contains(mode, str_to_mode(string))
def sub_str_to_mode(string):
mode = 0
if not string:
return mode
while string:
pattern = '(%s)' % MODE_MAP_RE.pattern
tmp = re.search(pattern, string)
if tmp:
tmp = tmp.groups()[0]
string = re.sub(pattern, '', string)
if tmp and MODE_HASH.get(tmp, False):
mode |= MODE_HASH[tmp]
else:
pass
return mode
def split_log_mode(mode):
user = ''
other = ''
match = re.search('(.*?)::(.*)', mode)
if match:
user, other = match.groups()
else:
user = mode
other = mode
#print ('split_logmode:', user, mode)
return user, other

View File

@@ -157,6 +157,7 @@ def hasher():
def convert_regexp(regexp):
regex_paren = re.compile('^(.*){([^}]*)}(.*)$')
regexp = regexp.strip()
new_reg = re.sub(r'(?<!\\)(\.|\+|\$)',r'\\\1',regexp)
# below will fail if { or } or , are part of a path too?
@@ -165,8 +166,8 @@ def convert_regexp(regexp):
# new_reg = new_reg.replace('}', '}')
# new_reg = new_reg.replace(',', '|')
while re.search('{[^}]*,[^}]*}', new_reg):
match = re.search('(.*){([^}]*)}(.*)', new_reg).groups()
while regex_paren.search(new_reg):
match = regex_paren.search(new_reg).groups()
prev = match[0]
after = match[2]
p1 = match[1].replace(',','|')
@@ -194,10 +195,18 @@ class DebugLogger:
self.debug_level = logging.DEBUG
if os.getenv('LOGPROF_DEBUG', False):
self.debugging = os.getenv('LOGPROF_DEBUG')
try:
self.debugging = int(self.debugging)
except:
self.debugging = False
if self.debugging not in range(1,4):
sys.stderr.out('Environment Variable: LOGPROF_DEBUG contains invalid value: %s' %os.getenv('LOGPROF_DEBUG'))
if self.debugging == 1:
debug_level = logging.ERROR
elif self.debug_level == 2:
debug_level = logging.INFO
elif debug_level == 3:
debug_level = logging.DEBUG
#logging.basicConfig(filename='/var/log/apparmor/logprof.log', level=self.debug_level, format='%(asctime)s - %(name)s - %(message)s\n')
logging.basicConfig(filename='/home/kshitij/logprof.log', level=self.debug_level, format='%(asctime)s - %(name)s - %(message)s\n')

379
apparmor/logparser.py Normal file
View File

@@ -0,0 +1,379 @@
import os
import re
import sys
import time
import LibAppArmor
from apparmor.common import (AppArmorException, error, debug, msg,
open_file_read, valid_path,
hasher, open_file_write, convert_regexp, DebugLogger)
from apparmor.aamode import *
class ReadLog:
RE_LOG_v2_6_syslog = re.compile('kernel:\s+(\[[\d\.\s]+\]\s+)?type=\d+\s+audit\([\d\.\:]+\):\s+apparmor=')
RE_LOG_v2_6_audit = re.compile('type=AVC\s+(msg=)?audit\([\d\.\:]+\):\s+apparmor=')
MODE_MAP_RE = re.compile('r|w|l|m|k|a|x|i|u|p|c|n|I|U|P|C|N')
LOG_MODE_RE = re.compile('r|w|l|m|k|a|x|ix|ux|px|cx|nx|pix|cix|Ix|Ux|Px|PUx|Cx|Nx|Pix|Cix')
PROFILE_MODE_RE = re.compile('r|w|l|m|k|a|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix')
PROFILE_MODE_NT_RE = re.compile('r|w|l|m|k|a|x|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix')
PROFILE_MODE_DENY_RE = re.compile('r|w|l|m|k|a|x')
# Used by netdomain to identify the operation types
OPERATION_TYPES = {
# New socket names
'create': 'net',
'post_create': 'net',
'bind': 'net',
'connect': 'net',
'listen': 'net',
'accept': 'net',
'sendmsg': 'net',
'recvmsg': 'net',
'getsockname': 'net',
'getpeername': 'net',
'getsockopt': 'net',
'setsockopt': 'net',
'sock_shutdown': 'net'
}
def __init__(self, pid, filename, existing_profiles, profile_dir, log):
self.filename = filename
self.profile_dir = profile_dir
self.pid = pid
self.existing_profiles = existing_profiles
self.log = log
self.debug_logger = DebugLogger('ReadLog')
self.LOG = None
self.logmark = ''
self.seenmark = None
self.next_log_entry = None
def prefetch_next_log_entry(self):
if self.next_log_entry:
sys.stderr.out('A log entry already present: %s' % self.next_log_entry)
self.next_log_entry = self.LOG.readline()
while not (self.RE_LOG_v2_6_syslog.search(self.next_log_entry) or self.RE_LOG_v2_6_audit.search(self.next_log_entry)): #or re.search(self.logmark, self.next_log_entry)):
self.next_log_entry = self.LOG.readline()
if not self.next_log_entry:
break
def get_next_log_entry(self):
# If no next log entry fetch it
if not self.next_log_entry:
self.prefetch_next_log_entry()
log_entry = self.next_log_entry
self.next_log_entry = None
return log_entry
def peek_at_next_log_entry(self):
# Take a peek at the next log entry
if not self.next_log_entry:
self.prefetch_next_log_entry()
return self.next_log_entry
def throw_away_next_log_entry(self):
self.next_log_entry = None
def parse_log_record(self, record):
self.debug_logger.debug('parse_log_record: %s' % record)
record_event = self.parse_event(record)
return record_event
def parse_event(self, msg):
"""Parse the event from log into key value pairs"""
msg = msg.strip()
self.debug_logger.info('parse_event: %s' % msg)
#print(repr(msg))
if sys.version_info < (3,0):
# parse_record fails with u'foo' style strings hence typecasting to string
msg = str(msg)
event = LibAppArmor.parse_record(msg)
ev = dict()
ev['resource'] = event.info
ev['active_hat'] = event.active_hat
ev['aamode'] = event.event
ev['time'] = event.epoch
ev['operation'] = event.operation
ev['profile'] = event.profile
ev['name'] = event.name
ev['name2'] = event.name2
ev['attr'] = event.attribute
ev['parent'] = event.parent
ev['pid'] = event.pid
ev['task'] = event.task
ev['info'] = event.info
dmask = event.denied_mask
rmask = event.requested_mask
ev['magic_token'] = event.magic_token
if ev['operation'] and self.op_type(ev['operation']) == 'net':
ev['family'] = event.net_family
ev['protocol'] = event.net_protocol
ev['sock_type'] = event.net_sock_type
LibAppArmor.free_record(event)
# Map c (create) to a and d (delete) to w, logprof doesn't support c and d
if rmask:
rmask = rmask.replace('c', 'a')
rmask = rmask.replace('d', 'w')
if not validate_log_mode(hide_log_mode(rmask)):
pass#fatal_error(_('Log contains unknown mode %s') % rmask)
if dmask:
dmask = dmask.replace('c', 'a')
dmask = dmask.replace('d', 'w')
if not validate_log_mode(hide_log_mode(dmask)):
pass #fatal_error(_('Log contains unknown mode %s') % dmask)
#print('parse_event:', ev['profile'], dmask, ev['name2'])
mask, name = log_str_to_mode(ev['profile'], dmask, ev['name2'])
ev['denied_mask'] = mask
ev['name2'] = name
mask, name = log_str_to_mode(ev['profile'], rmask, ev['name2'])
ev['request_mask'] = mask
ev['name2'] = name
if not ev['time']:
ev['time'] = int(time.time())
# Remove None keys
#for key in ev.keys():
# if not ev[key] or not re.search('[\w]+', ev[key]):
# ev.pop(key)
if ev['aamode']:
# Convert aamode values to their counter-parts
mode_convertor = {
0: 'UNKNOWN',
1: 'ERROR',
2: 'AUDITING',
3: 'PERMITTING',
4: 'REJECTING',
5: 'HINT',
6: 'STATUS'
}
try:
ev['aamode'] = mode_convertor[ev['aamode']]
except KeyError:
ev['aamode'] = None
if ev['aamode']:
#debug_logger.debug(ev)
return ev
else:
return None
def add_to_tree(self, loc_pid, parent, type, event):
self.debug_logger.info('add_to_tree: pid [%s] type [%s] event [%s]' % (loc_pid, type, event))
if not self.pid.get(loc_pid, False):
profile, hat = event[:2]
if parent and self.pid.get(parent, False):
if not hat:
hat = 'null-complain-profile'
arrayref = []
self.pid[parent].append(arrayref)
self.pid[loc_pid] = arrayref
for ia in ['fork', loc_pid, profile, hat]:
arrayref.append(ia)
# self.pid[parent].append(array_ref)
# self.pid[loc_pid] = array_ref
else:
arrayref = []
self.log.append(arrayref)
self.pid[loc_pid] = arrayref
# self.log.append(array_ref)
# self.pid[loc_pid] = array_ref
self.pid[loc_pid].append([type, loc_pid] + event)
#print("\n\npid",self.pid)
#print("log",self.log)
def add_event_to_tree(self, e):
aamode = e.get('aamode', 'UNKNOWN')
if e.get('type', False):
if re.search('(UNKNOWN\[1501\]|APPARMOR_AUDIT|1501)', e['type']):
aamode = 'AUDIT'
elif re.search('(UNKNOWN\[1502\]|APPARMOR_ALLOWED|1502)', e['type']):
aamode = 'PERMITTING'
elif re.search('(UNKNOWN\[1503\]|APPARMOR_DENIED|1503)', e['type']):
aamode = 'REJECTING'
elif re.search('(UNKNOWN\[1504\]|APPARMOR_HINT|1504)', e['type']):
aamode = 'HINT'
elif re.search('(UNKNOWN\[1505\]|APPARMOR_STATUS|1505)', e['type']):
aamode = 'STATUS'
elif re.search('(UNKNOWN\[1506\]|APPARMOR_ERROR|1506)', e['type']):
aamode = 'ERROR'
else:
aamode = 'UNKNOWN'
if aamode in ['UNKNOWN', 'AUDIT', 'STATUS', 'ERROR']:
return None
if 'profile_set' in e['operation']:
return None
# Skip if AUDIT event was issued due to a change_hat in unconfined mode
if not e.get('profile', False):
return None
# Convert new null profiles to old single level null profile
if '//null-' in e['profile']:
e['profile'] = 'null-complain-profile'
profile = e['profile']
hat = None
if '//' in e['profile']:
profile, hat = e['profile'].split('//')[:2]
# Filter out change_hat events that aren't from learning
if e['operation'] == 'change_hat':
if aamode != 'HINT' or aamode != 'PERMITTING':
return None
profile = e['name']
#hat = None
if '//' in e['name']:
profile, hat = e['name'].split('//')
if not hat:
hat = profile
# prog is no longer passed around consistently
prog = 'HINT'
if profile != 'null-complain-profile' and not self.profile_exists(profile):
return None
if e['operation'] == 'exec':
if e.get('info', False) and e['info'] == 'mandatory profile missing':
self.add_to_tree(e['pid'], e['parent'], 'exec',
[profile, hat, aamode, 'PERMITTING', e['denied_mask'], e['name'], e['name2']])
elif e.get('name2', False) and '\\null-/' in e['name2']:
self.add_to_tree(e['pid'], e['parent'], 'exec',
[profile, hat, prog, aamode, e['denied_mask'], e['name'], ''])
elif e.get('name', False):
self.add_to_tree(e['pid'], e['parent'], 'exec',
[profile, hat, prog, aamode, e['denied_mask'], e['name'], ''])
else:
self.debug_logger.debug('add_event_to_tree: dropped exec event in %s' % e['profile'])
elif 'file_' in e['operation']:
self.add_to_tree(e['pid'], e['parent'], 'path',
[profile, hat, prog, aamode, e['denied_mask'], e['name'], ''])
elif e['operation'] in ['open', 'truncate', 'mkdir', 'mknod', 'rename_src',
'rename_dest', 'unlink', 'rmdir', 'symlink_create', 'link']:
#print(e['operation'], e['name'])
self.add_to_tree(e['pid'], e['parent'], 'path',
[profile, hat, prog, aamode, e['denied_mask'], e['name'], ''])
elif e['operation'] == 'capable':
self.add_to_tree(e['pid'], e['parent'], 'capability',
[profile, hat, prog, aamode, e['name'], ''])
elif e['operation'] == 'setattr' or 'xattr' in e['operation']:
self.add_to_tree(e['pid'], e['parent'], 'path',
[profile, hat, prog, aamode, e['denied_mask'], e['name'], ''])
elif 'inode_' in e['operation']:
is_domain_change = False
if e['operation'] == 'inode_permission' and (e['denied_mask'] & AA_MAY_EXEC) and aamode == 'PERMITTING':
following = self.peek_at_next_log_entry()
if following:
entry = self.parse_log_record(following)
if entry and entry.get('info', False) == 'set profile':
is_domain_change = True
self.throw_away_next_log_entry()
if is_domain_change:
self.add_to_tree(e['pid'], e['parent'], 'exec',
[profile, hat, prog, aamode, e['denied_mask'], e['name'], e['name2']])
else:
self.add_to_tree(e['pid'], e['parent'], 'path',
[profile, hat, prog, aamode, e['denied_mask'], e['name'], ''])
elif e['operation'] == 'sysctl':
self.add_to_tree(e['pid'], e['parent'], 'path',
[profile, hat, prog, aamode, e['denied_mask'], e['name'], ''])
elif e['operation'] == 'clone':
parent , child = e['pid'], e['task']
if not parent:
parent = 'null-complain-profile'
if not hat:
hat = 'null-complain-profile'
arrayref = []
if self.pid.get(parent, False):
self.pid[parent].append(arrayref)
else:
self.log.append(arrayref)
self.pid[child].append(arrayref)
for ia in ['fork', child, profile, hat]:
arrayref.append(ia)
# if self.pid.get(parent, False):
# self.pid[parent] += [arrayref]
# else:
# self.log += [arrayref]
# self.pid[child] = arrayref
elif self.op_type(e['operation']) == 'net':
self.add_to_tree(e['pid'], e['parent'], 'netdomain',
[profile, hat, prog, aamode, e['family'], e['sock_type'], e['protocol']])
elif e['operation'] == 'change_hat':
self.add_to_tree(e['pid'], e['parent'], 'unknown_hat',
[profile, hat, aamode, hat])
else:
self.debug_logger.debug('UNHANDLED: %s' % e)
def read_log(self, logmark):
seenmark = True
if logmark:
seenmark = False
#last = None
#event_type = None
try:
#print(self.filename)
self.LOG = open_file_read(self.filename)
except IOError:
raise AppArmorException('Can not read AppArmor logfile: ' + self.filename)
#LOG = open_file_read(log_open)
line = self.get_next_log_entry()
while line:
line = line.strip()
self.debug_logger.debug('read_log: %s' % line)
if logmark in line:
seenmark = True
self.debug_logger.debug('read_log: seenmark = %s' %seenmark)
if not seenmark:
line = self.get_next_log_entry()
continue
event = self.parse_log_record(line)
#print(event)
if event:
self.add_event_to_tree(event)
line = self.get_next_log_entry()
self.LOG.close()
logmark = ''
return self.log
def op_type(self, operation):
"""Returns the operation type if known, unkown otherwise"""
operation_type = self.OPERATION_TYPES.get(operation, 'unknown')
return operation_type
def profile_exists(self, program):
"""Returns True if profile exists, False otherwise"""
# Check cache of profiles
if self.existing_profiles.get(program, False):
return True
# Check the disk for profile
prof_path = self.get_profile_filename(program)
#print(prof_path)
if os.path.isfile(prof_path):
# Add to cache of profile
self.existing_profiles[program] = True
return True
return False
def get_profile_filename(self, profile):
"""Returns the full profile name"""
if profile.startswith('/'):
# Remove leading /
profile = profile[1:]
else:
profile = "profile_" + profile
profile = profile.replace('/', '.')
full_profilename = self.profile_dir + '/' + profile
return full_profilename

View File

@@ -167,7 +167,7 @@ class Severity:
"""Loads the variables for the given profile"""
regex_include = re.compile('^#?include\s*<(\S*)>')
if os.path.isfile(prof_path):
with open_file_read(prof_path) as f_in:
with open(prof_path, 'r') as f_in:
for line in f_in:
line = line.strip()
# If any includes, load variables from them first

View File

@@ -235,6 +235,15 @@ def UI_PromptUser(q):
cmd == 'XXXINVALIDXXX'
return (cmd, arg)
def confirm_and_abort():
ans = UI_YesNo(_('Are you sure you want to abandon this set of profile changes and exit?'), 'n')
if ans == 'y':
UI_Info(_('Abandoning all changes.'))
#shutdown_yast()
#for prof in created:
# delete_profile(prof)
sys.exit(0)
def UI_ShortMessage(title, message):
SendDataToYast({
'type': 'short-dialog-message',
@@ -303,7 +312,7 @@ def Text_PromptUser(question):
default_key = defaulthotkey.groups()[0].lower()
if keys.get(default_key, False):
if not keys.get(default_key, False):
raise AppArmorException('PromptUser: %s %s' %(_('Invalid default'), default))
widest = 0
@@ -315,7 +324,7 @@ def Text_PromptUser(question):
widest = len(header)
widest += 1
formatstr = '%-' + widest + 's %s\n'
formatstr = '%-' + str(widest) + 's %s\n'
function_regexp = '^('
function_regexp += '|'.join(keys.keys())
@@ -348,7 +357,7 @@ def Text_PromptUser(question):
else:
format_option = ' %s - %s '
prompt += format_option %(index+1, option)
prompt += '\n'
prompt += '\n'
prompt += ' / '.join(menu_items)
@@ -371,7 +380,7 @@ def Text_PromptUser(question):
# sys.stdout.write('\n%s\n' %helptext)
# ans = 'XXXINVALIDXXX'
elif int(ans) == 10:
elif is_number(ans) == 10:
# If they hit return choose default option
ans = default_key
@@ -389,3 +398,10 @@ def Text_PromptUser(question):
ans = keys[ans]
return ans, selected
def is_number(number):
try:
return int(number)
except:
return False