------------------------------------------------------------ revno: 3628 committer: Christian Boltz branch nick: apparmor timestamp: Mon 2017-01-30 20:43:47 +0100 message: Dovecot profile: change Px to mrPx for /usr/lib/dovecot/* Some of the /usr/lib/dovecot/* rules already have mrPx permissions, while others don't. With a more recent kernel, I noticed that at least auth, config, dict, lmtp, pop3 and ssl-params need mrPx instead of just Px (confirmed by the audit.log and actual breakage caused by the missing mr permissions). The mr additions for anvil, log and managesieve are just a wild guess, but I would be very surprised if they don't need mr. Acked-by: Seth Arnold for trunk, 2.10 and 2.9. ------------------------------------------------------------ revno: 3627 fixes bug: https://launchpad.net/bugs/1512131 committer: Christian Boltz branch nick: apparmor timestamp: Thu 2017-01-26 21:41:38 +0100 message: Dovecot profile update Add several permissions to the dovecot profiles that are needed on ubuntu (surprisingly not on openSUSE, maybe it depends on the dovecot config?) As discussed some weeks ago, the added permissions use only /run/ instead of /{var/,}run/ (which is hopefully superfluous nowadays). References: https://bugs.launchpad.net/apparmor/+bug/1512131 Acked-by: Seth Arnold for trunk, 2.10 and 2.9. ------------------------------------------------------------ revno: 3626 fixes bug: https://launchpad.net/bugs/1658239 author: Kees Cook committer: Seth Arnold branch nick: apparmor timestamp: Fri 2017-01-20 17:01:50 -0800 message: glibc uses /proc/*/auxv and /proc/*/status files, too Acked-by: Seth Arnold ------------------------------------------------------------ revno: 3625 fixes bug: https://launchpad.net/bugs/1658238 author: Kees Cook committer: Seth Arnold branch nick: apparmor timestamp: Fri 2017-01-20 16:58:46 -0800 message: Apache2 profile updates for proper signal handling, optional saslauth, and OCSP stapling Acked-by: Seth Arnold ------------------------------------------------------------ revno: 3624 committer: Christian Boltz branch nick: apparmor timestamp: Fri 2017-01-20 01:20:41 +0100 message: Drop unused global variables in aa.py Grepping through the code shows that running_under_genprof, unimplemented_warning, ALL, t, seen and skip are unused, so drop them. Acked-by: Steve Beattie Also drop a '# t = hasher()" comment, as noticed by Steve. ------------------------------------------------------------ revno: 3623 author: Kees Cook committer: Tyler Hicks branch nick: apparmor timestamp: Thu 2017-01-19 23:04:34 +0000 message: pass LDFLAGS fully into build Acked-by: John Johansen Signed-off-by: Tyler Hicks ------------------------------------------------------------ revno: 3622 committer: Christian Boltz branch nick: apparmor timestamp: Thu 2017-01-19 16:54:47 +0100 message: [7/7] Drop most of aa-mergeprof ask_the_questions() Replace most of aa-mergeprof ask_merge_questions() with a call to aa.py ask_the_questions() (which is, besides some small exceptions that are not relevant for aa-mergeprof, in sync with the dropped code). The remaining part gets renamed to ask_merge_questions() to avoid confusion with the function name in aa.py. Also drop the (now superfluous) parameter. aa.py ask_the_questions() needs to allow 'merge' as aamode. While on it, replace the fatal_error() call for unknown aamode with raising an AppArmorBug. Acked-by: Seth Arnold ------------------------------------------------------------ revno: 3621 committer: Christian Boltz branch nick: apparmor timestamp: Thu 2017-01-19 16:52:38 +0100 message: [6/7] make log_dict a parameter of ask_the_questions() This allows to hand over any source instead of using the global variable. Now that the function expects its input as parameter, get rid of the global log_dict, which means - change collapse_log() to initialize log_dict as local variable and return it - change do_logprof_pass() to catch collapse_log()'s return value and hand it over to ask_the_questions() - drop all references to the global log_dict variable - update test-libapparmor-test_multi to follow the changes Also fix an if condition that would fail if aa[profile][hat] does not exist - get() defaults to None if the requested item doesn't exist, and None.get('file') will raise an Exception. Acked-by: Seth Arnold ------------------------------------------------------------ revno: 3620 committer: Christian Boltz branch nick: apparmor timestamp: Thu 2017-01-19 16:48:44 +0100 message: [5/7] move ask_conflict_mode() to aa.py The function is an exact copy of the code in aa-mergeprof (except removing the 'self' function parameter and changing the whitespace level) Also add a ask_conflict_mode() call to aa.py ask_the_questions(). This is needed for aa-mergeprof, and won't hurt in aa-logprof mode because handle_children() already handles all exec events. Acked-by: Seth Arnold ------------------------------------------------------------ revno: 3619 committer: Christian Boltz branch nick: apparmor timestamp: Thu 2017-01-19 16:47:35 +0100 message: [4/7] Copy code to ask for adding hats to aa.py ask_the_questions() Everything below "if aamode == 'merge':" is an exact copy of the code in aa-mergeprof (with whitespace changed). aa-logprof and aa-mergeprof will continue to ignore events from unknown hats and subprofiles. Acked-by: Seth Arnold ------------------------------------------------------------ revno: 3618 committer: Christian Boltz branch nick: apparmor timestamp: Thu 2017-01-19 16:47:05 +0100 message: [3/7] Copy code to ask for adding includes to aa.py ask_the_questions() This is an exact copy of the code in aa-mergeprof (with whitespace changed). Acked-by: Seth Arnold ------------------------------------------------------------ revno: 3617 committer: Christian Boltz branch nick: apparmor timestamp: Thu 2017-01-19 16:46:23 +0100 message: [2/7] replace other.aa with log_dict['merge'] Set log_dict['merge'] = other.aa and aamode = 'merge', and use log_dict[aamode] everywhere. This brings aa-mergeprof ask_the_questions() closer to the code in aa.py. Acked-by: Seth Arnold ------------------------------------------------------------ revno: 3616 committer: Christian Boltz branch nick: apparmor timestamp: Thu 2017-01-19 16:45:29 +0100 message: [1/7] drop traces of 3-way-merge in aa-mergeprof 3-way-merge was never really implemented. This patch drops all traces of it to make the code more readable and easier to maintain. Acked-by: Seth Arnold === modified file 'changehat/pam_apparmor/Makefile' --- changehat/pam_apparmor/Makefile 2016-12-10 18:25:31 +0000 +++ changehat/pam_apparmor/Makefile 2017-01-19 23:04:34 +0000 @@ -55,7 +55,7 @@ AA_LDLIBS = -lapparmor endif EXTRA_CFLAGS=$(CFLAGS) $(CPPFLAGS) -fPIC -shared -Wall $(LIBAPPARMOR_INCLUDE) -LINK_FLAGS=-Xlinker -x $(AA_LINK_FLAGS) +LINK_FLAGS=-Xlinker -x $(AA_LINK_FLAGS) $(LDFLAGS) LIBS=-lpam $(AA_LDLIBS) OBJECTS=${NAME}.o get_options.o === modified file 'libraries/libapparmor/swig/perl/Makefile.PL.in' --- libraries/libapparmor/swig/perl/Makefile.PL.in 2014-01-06 22:08:55 +0000 +++ libraries/libapparmor/swig/perl/Makefile.PL.in 2017-01-19 23:04:34 +0000 @@ -13,5 +13,6 @@ 'INC' => q[@CPPFLAGS@ -I@top_srcdir@/include @CFLAGS@], 'LIBS' => q[-L@top_builddir@/src/.libs/ -lapparmor @LIBS@], 'OBJECT' => 'libapparmor_wrap.o', # $(OBJ_EXT) + 'dynamic_lib' => { 'OTHERLDFLAGS' => q[@LDFLAGS@], }, ) ; === modified file 'parser/Makefile' --- parser/Makefile 2016-12-10 18:25:31 +0000 +++ parser/Makefile 2017-01-19 23:04:34 +0000 @@ -86,7 +86,7 @@ AAREDIR= libapparmor_re AAREOBJECT = ${AAREDIR}/libapparmor_re.a AAREOBJECTS = $(AAREOBJECT) -AARE_LDFLAGS = -static-libgcc -static-libstdc++ -L. +AARE_LDFLAGS = -static-libgcc -static-libstdc++ -L. $(LDFLAGS) AALIB = -Wl,-Bstatic -lapparmor -Wl,-Bdynamic -lpthread ifdef USE_SYSTEM === modified file 'profiles/apparmor.d/abstractions/apache2-common' --- profiles/apparmor.d/abstractions/apache2-common 2014-06-24 18:06:06 +0000 +++ profiles/apparmor.d/abstractions/apache2-common 2017-01-21 00:58:46 +0000 @@ -8,6 +8,8 @@ signal (receive) peer=unconfined, # Allow apache to send us signals by default signal (receive) peer=/usr/sbin/apache2, + # Allow other hats to signal by default + signal peer=/usr/sbin/apache2//*, # Allow us to signal ourselves signal peer=@{profile_name}, @@ -25,3 +27,8 @@ /dev/urandom r, + # sasl-auth + /run/saslauthd/mux rw, + + # OCSP stapling + /var/log/apache2/stapling-cache rw, === modified file 'profiles/apparmor.d/abstractions/base' --- profiles/apparmor.d/abstractions/base 2016-12-03 15:52:47 +0000 +++ profiles/apparmor.d/abstractions/base 2017-01-21 01:01:50 +0000 @@ -85,7 +85,7 @@ /sys/devices/system/cpu/online r, # glibc's *printf protections read the maps file - @{PROC}/@{pid}/maps r, + @{PROC}/@{pid}/{maps,auxv,status} r, # libgcrypt reads some flags from /proc @{PROC}/sys/crypto/* r, === modified file 'profiles/apparmor.d/usr.lib.dovecot.anvil' --- profiles/apparmor.d/usr.lib.dovecot.anvil 2014-06-27 19:14:53 +0000 +++ profiles/apparmor.d/usr.lib.dovecot.anvil 2017-01-26 20:41:38 +0000 @@ -18,6 +18,7 @@ capability setuid, capability sys_chroot, + /run/dovecot/anvil rw, /usr/lib/dovecot/anvil mr, # Site-specific additions and overrides. See local/README for details. === modified file 'profiles/apparmor.d/usr.lib.dovecot.auth' --- profiles/apparmor.d/usr.lib.dovecot.auth 2016-12-27 16:46:07 +0000 +++ profiles/apparmor.d/usr.lib.dovecot.auth 2017-01-26 20:41:38 +0000 @@ -37,6 +37,9 @@ /var/tmp/sieve_* rw, /var/tmp/smtp_* rw, + /run/dovecot/auth-master rw, + /run/dovecot/auth-worker rw, + /run/dovecot/login/login rw, /{var/,}run/dovecot/auth-token-secret.dat{,.tmp} rw, /{var/,}run/dovecot/stats-user rw, /{var/,}run/dovecot/anvil-auth-penalty rw, === modified file 'profiles/apparmor.d/usr.lib.dovecot.imap' --- profiles/apparmor.d/usr.lib.dovecot.imap 2016-10-05 18:46:03 +0000 +++ profiles/apparmor.d/usr.lib.dovecot.imap 2017-01-26 20:41:38 +0000 @@ -21,6 +21,8 @@ capability setuid, deny capability block_suspend, + network unix stream, + @{DOVECOT_MAILSTORE}/ rw, @{DOVECOT_MAILSTORE}/** rwkl, @@ -33,6 +35,7 @@ /usr/bin/doveconf rix, /usr/lib/dovecot/imap mrix, /usr/share/dovecot/** r, + /run/dovecot/login/imap rw, /{,var/}run/dovecot/auth-master rw, /{,var/}run/dovecot/mounts r, === modified file 'profiles/apparmor.d/usr.lib.dovecot.imap-login' --- profiles/apparmor.d/usr.lib.dovecot.imap-login 2014-12-22 16:41:59 +0000 +++ profiles/apparmor.d/usr.lib.dovecot.imap-login 2017-01-26 20:41:38 +0000 @@ -22,6 +22,7 @@ network inet stream, network inet6 stream, + network unix stream, /usr/lib/dovecot/imap-login mr, /{,var/}run/dovecot/anvil rw, === modified file 'profiles/apparmor.d/usr.lib.dovecot.ssl-params' --- profiles/apparmor.d/usr.lib.dovecot.ssl-params 2014-06-27 19:14:53 +0000 +++ profiles/apparmor.d/usr.lib.dovecot.ssl-params 2017-01-26 20:41:38 +0000 @@ -15,6 +15,7 @@ #include #include + /run/dovecot/login/ssl-params rw, /usr/lib/dovecot/ssl-params mr, /var/lib/dovecot/ssl-parameters.dat rw, /var/lib/dovecot/ssl-parameters.dat.tmp rwk, === modified file 'profiles/apparmor.d/usr.sbin.dovecot' --- profiles/apparmor.d/usr.sbin.dovecot 2016-11-29 20:35:14 +0000 +++ profiles/apparmor.d/usr.sbin.dovecot 2017-01-30 19:43:47 +0000 @@ -36,21 +36,21 @@ /etc/SuSE-release r, @{PROC}/@{pid}/mounts r, /usr/bin/doveconf rix, - /usr/lib/dovecot/anvil Px, - /usr/lib/dovecot/auth Px, - /usr/lib/dovecot/config Px, - /usr/lib/dovecot/dict Px, + /usr/lib/dovecot/anvil mrPx, + /usr/lib/dovecot/auth mrPx, + /usr/lib/dovecot/config mrPx, + /usr/lib/dovecot/dict mrPx, /usr/lib/dovecot/dovecot-auth Pxmr, /usr/lib/dovecot/imap Pxmr, /usr/lib/dovecot/imap-login Pxmr, - /usr/lib/dovecot/lmtp Px, - /usr/lib/dovecot/log Px, - /usr/lib/dovecot/managesieve Px, + /usr/lib/dovecot/lmtp mrPx, + /usr/lib/dovecot/log mrPx, + /usr/lib/dovecot/managesieve mrPx, /usr/lib/dovecot/managesieve-login Pxmr, - /usr/lib/dovecot/pop3 Px, + /usr/lib/dovecot/pop3 mrPx, /usr/lib/dovecot/pop3-login Pxmr, /usr/lib/dovecot/ssl-build-param rix, - /usr/lib/dovecot/ssl-params Px, + /usr/lib/dovecot/ssl-params mrPx, /usr/sbin/dovecot mrix, /usr/share/dovecot/protocols.d/ r, /usr/share/dovecot/protocols.d/** r, === modified file 'utils/aa-mergeprof' --- utils/aa-mergeprof 2016-10-01 18:57:09 +0000 +++ utils/aa-mergeprof 2017-01-19 15:54:47 +0000 @@ -1,7 +1,7 @@ #! /usr/bin/python3 # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta -# Copyright (C) 2014-2016 Christian Boltz +# Copyright (C) 2014-2017 Christian Boltz # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public @@ -23,10 +23,6 @@ import apparmor.cleanprofile as cleanprofile import apparmor.ui as aaui -from apparmor.aa import (add_to_options, available_buttons, combine_name, delete_duplicates, - get_profile_filename, is_known_rule, match_includes, profile_storage, - set_options_audit_mode, propose_file_rules, selection_to_rule_obj) -from apparmor.aare import AARE from apparmor.common import AppArmorException from apparmor.regex import re_match_include @@ -41,16 +37,13 @@ parser = argparse.ArgumentParser(description=_('Merge the given profiles into /etc/apparmor.d/ (or the directory specified with -d)')) parser.add_argument('files', nargs='+', type=str, help=_('Profile(s) to merge')) -#parser.add_argument('other', nargs='?', type=str, help=_('other profile')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) #parser.add_argument('-a', '--auto', action='store_true', help=_('Automatically merge profiles, exits incase of *x conflicts')) args = parser.parse_args() args.other = None -# 2-way merge or 3-way merge based on number of params -merge_mode = 2 #if args.other == None else 3 -profiles = [args.files, [args.other]] +profiles = args.files profiledir = args.dir if profiledir: @@ -87,61 +80,29 @@ return profile_to_filename def main(): - profiles_to_merge = set() - - base_files, other_files = profiles - - base_profile_to_file = find_profiles_from_files(base_files) - - profiles_to_merge = profiles_to_merge.union(set(base_profile_to_file.keys())) - - other_profile_to_file = dict() - - if merge_mode == 3: - other_profile_to_file = find_profiles_from_files(other_files) - profiles_to_merge.add(other_profile_to_file.keys()) + base_profile_to_file = find_profiles_from_files(profiles) + + profiles_to_merge = set(base_profile_to_file.keys()) user_profile_to_file = find_files_from_profiles(profiles_to_merge) -# print(base_files,"\n",other_files) -# print(base_profile_to_file,"\n",other_profile_to_file,"\n",user_profile_to_file) -# print(profiles_to_merge) - for profile_name in profiles_to_merge: aaui.UI_Info("\n\n" + _("Merging profile for %s" % profile_name)) user_file = user_profile_to_file[profile_name] base_file = base_profile_to_file.get(profile_name, None) - other_file = None - - if merge_mode == 3: - other_file = other_profile_to_file.get(profile_name, None) - - if base_file == None: - if other_file == None: - continue - - act([user_file, other_file, None], 2, profile_name) - else: - if other_file == None: - act([user_file, base_file, None], 2, profile_name) - else: - act([user_file, base_file, other_file], 3, profile_name) + + act([user_file, base_file], profile_name) reset_aa() -def act(files, merge_mode, merging_profile): +def act(files, merging_profile): mergeprofiles = Merge(files) #Get rid of common/superfluous stuff mergeprofiles.clear_common() # if not args.auto: if 1 == 1: # workaround to avoid lots of whitespace changes - if merge_mode == 3: - mergeprofiles.ask_the_questions('other', merging_profile) - - mergeprofiles.clear_common() - - mergeprofiles.ask_the_questions('base', merging_profile) + mergeprofiles.ask_merge_questions() q = aaui.PromptQuestion() q.title = _('Changed Local Profiles') @@ -172,7 +133,7 @@ class Merge(object): def __init__(self, profiles): - user, base, other = profiles + user, base = profiles #Read and parse base profile and save profile data, include data from it and reset them apparmor.aa.read_profile(base, True) @@ -180,12 +141,6 @@ reset_aa() - #Read and parse other profile and save profile data, include data from it and reset them - if merge_mode == 3: - apparmor.aa.read_profile(other, True) - self.other = cleanprofile.Prof(other) - reset_aa() - #Read and parse user profile apparmor.aa.read_profile(user, True) self.user = cleanprofile.Prof(user) @@ -193,67 +148,18 @@ def clear_common(self): deleted = 0 - if merge_mode == 3: - #Remove off the parts in other profile which are common/superfluous from user profile - user_other = cleanprofile.CleanProf(False, self.user, self.other) - deleted += user_other.compare_profiles() - #Remove off the parts in base profile which are common/superfluous from user profile user_base = cleanprofile.CleanProf(False, self.user, self.base) deleted += user_base.compare_profiles() - if merge_mode == 3: - #Remove off the parts in other profile which are common/superfluous from base profile - base_other = cleanprofile.CleanProf(False, self.base, self.other) - deleted += base_other.compare_profiles() - - def ask_conflict_mode(self, profile, hat, old_profile, merge_profile): - '''ask user about conflicting exec rules''' - for oldrule in old_profile['file'].rules: - conflictingrules = merge_profile['file'].get_exec_conflict_rules(oldrule) - - if conflictingrules.rules: - q = aaui.PromptQuestion() - q.headers = [_('Path'), oldrule.path.regex] - q.headers += [_('Select the appropriate mode'), ''] - options = [] - options.append(oldrule.get_clean()) - for rule in conflictingrules.rules: - options.append(rule.get_clean()) - q.options = options - q.functions = ['CMD_ALLOW', 'CMD_ABORT'] - done = False - while not done: - ans, selected = q.promptUser() - if ans == 'CMD_ALLOW': - if selected == 0: - pass # just keep the existing rule - elif selected > 0: - # replace existing rule with merged one - old_profile['file'].delete(oldrule) - old_profile['file'].add(conflictingrules.rules[selected - 1]) - else: - raise AppArmorException(_('Unknown selection')) - - for rule in conflictingrules.rules: - merge_profile['file'].delete(rule) # make sure aa-mergeprof doesn't ask to add conflicting rules later - - done = True - - def ask_the_questions(self, other, profile): - aa = self.user.aa # keep references so that the code in this function can use the short name - changed = apparmor.aa.changed # (and be more in sync with aa.py ask_the_questions()) - - if other == 'other': - other = self.other - else: - other = self.base - #print(other.aa) - - #Add the file-wide includes from the other profile to the user profile + def ask_merge_questions(self): + other = self.base + log_dict = {'merge': other.aa} + apparmor.aa.loadincludes() done = False + #Add the file-wide includes from the other profile to the user profile options = [] for inc in other.filelist[other.filename]['include'].keys(): if not inc in self.user.filelist[self.user.filename]['include'].keys(): @@ -281,211 +187,10 @@ elif ans == 'CMD_FINISHED': return - sev_db = apparmor.aa.sev_db - if not sev_db: - sev_db = apparmor.severity.Severity(apparmor.aa.CONFDIR + '/severity.db', _('unknown')) - - sev_db.unload_variables() - sev_db.load_variables(get_profile_filename(profile)) - - for hat in sorted(other.aa[profile].keys()): - - if not aa[profile].get(hat): - ans = '' - while ans not in ['CMD_ADDHAT', 'CMD_ADDSUBPROFILE', 'CMD_DENY']: - q = aaui.PromptQuestion() - q.headers += [_('Profile'), profile] - - if other.aa[profile][hat]['profile']: - q.headers += [_('Requested Subprofile'), hat] - q.functions.append('CMD_ADDSUBPROFILE') - else: - q.headers += [_('Requested Hat'), hat] - q.functions.append('CMD_ADDHAT') - - q.functions += ['CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED'] - - q.default = 'CMD_DENY' - - ans = q.promptUser()[0] - - if ans == 'CMD_FINISHED': - return - - if ans == 'CMD_DENY': - continue # don't ask about individual rules if the user doesn't want the additional subprofile/hat - - if other.aa[profile][hat]['profile']: - aa[profile][hat] = profile_storage(profile, hat, 'mergeprof ask_the_questions() - missing subprofile') - aa[profile][hat]['profile'] = True - else: - aa[profile][hat] = profile_storage(profile, hat, 'mergeprof ask_the_questions() - missing hat') - aa[profile][hat]['profile'] = False - - #Add the includes from the other profile to the user profile - done = False - - options = [] - for inc in other.aa[profile][hat]['include'].keys(): - if not inc in aa[profile][hat]['include'].keys(): - options.append('#include <%s>' %inc) - - default_option = 1 - - q = aaui.PromptQuestion() - q.options = options - q.selected = default_option - 1 - q.headers = [_('File includes'), _('Select the ones you wish to add')] - q.functions = ['CMD_ALLOW', 'CMD_IGNORE_ENTRY', 'CMD_ABORT', 'CMD_FINISHED'] - q.default = 'CMD_ALLOW' - - while not done and options: - ans, selected = q.promptUser() - if ans == 'CMD_IGNORE_ENTRY': - done = True - elif ans == 'CMD_ALLOW': - selection = options[selected] - inc = re_match_include(selection) - deleted = apparmor.aa.delete_duplicates(aa[profile][hat], inc) - aa[profile][hat]['include'][inc] = True - options.pop(selected) - aaui.UI_Info(_('Adding %s to the file.') % selection) - if deleted: - aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) - elif ans == 'CMD_FINISHED': - return - - # check for and ask about conflicting exec modes - self.ask_conflict_mode(profile, hat, aa[profile][hat], other.aa[profile][hat]) - - for ruletype in apparmor.aa.ruletypes: - if other.aa[profile][hat].get(ruletype, False): # needed until we have proper profile initialization - for rule_obj in other.aa[profile][hat][ruletype].rules: - - if is_known_rule(aa[profile][hat], ruletype, rule_obj): - continue - - default_option = 1 - options = [] - newincludes = match_includes(aa[profile][hat], ruletype, rule_obj) - q = aaui.PromptQuestion() - if newincludes: - options += list(map(lambda inc: '#include <%s>' % inc, sorted(set(newincludes)))) - - if ruletype == 'file' and rule_obj.path: - options += propose_file_rules(aa[profile][hat], rule_obj) - else: - options.append(rule_obj.get_clean()) - - done = False - while not done: - q.options = options - q.selected = default_option - 1 - q.headers = [_('Profile'), combine_name(profile, hat)] - q.headers += rule_obj.logprof_header() - - # Load variables into sev_db? Not needed/used for capabilities and network rules. - severity = rule_obj.severity(sev_db) - if severity != sev_db.NOT_IMPLEMENTED: - q.headers += [_('Severity'), severity] - - q.functions = available_buttons(rule_obj) - q.default = q.functions[0] - - ans, selected = q.promptUser() - selection = options[selected] - if ans == 'CMD_IGNORE_ENTRY': - done = True - break - - elif ans == 'CMD_FINISHED': - return - - elif ans.startswith('CMD_AUDIT'): - if ans == 'CMD_AUDIT_NEW': - rule_obj.audit = True - rule_obj.raw_rule = None - else: - rule_obj.audit = False - rule_obj.raw_rule = None - - options = set_options_audit_mode(rule_obj, options) - - elif ans == 'CMD_ALLOW': - done = True - changed[profile] = True - - inc = re_match_include(selection) - if inc: - deleted = delete_duplicates(aa[profile][hat], inc) - - aa[profile][hat]['include'][inc] = True - - aaui.UI_Info(_('Adding %s to profile.') % selection) - if deleted: - aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) - - else: - rule_obj = selection_to_rule_obj(rule_obj, selection) - deleted = aa[profile][hat][ruletype].add(rule_obj, cleanup=True) - - aaui.UI_Info(_('Adding %s to profile.') % rule_obj.get_clean()) - if deleted: - aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) - - elif ans == 'CMD_DENY': - if re_match_include(selection): - aaui.UI_Important("Denying via an include file isn't supported by the AppArmor tools") - - else: - done = True - changed[profile] = True - - rule_obj = selection_to_rule_obj(rule_obj, selection) - rule_obj.deny = True - rule_obj.raw_rule = None # reset raw rule after manually modifying rule_obj - deleted = aa[profile][hat][ruletype].add(rule_obj, cleanup=True) - aaui.UI_Info(_('Adding %s to profile.') % rule_obj.get_clean()) - if deleted: - aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) - - elif ans == 'CMD_GLOB': - if not re_match_include(selection): - globbed_rule_obj = selection_to_rule_obj(rule_obj, selection) - globbed_rule_obj.glob() - options, default_option = add_to_options(options, globbed_rule_obj.get_raw()) - - elif ans == 'CMD_GLOBEXT': - if not re_match_include(selection): - globbed_rule_obj = selection_to_rule_obj(rule_obj, selection) - globbed_rule_obj.glob_ext() - options, default_option = add_to_options(options, globbed_rule_obj.get_raw()) - - elif ans == 'CMD_NEW': - if not re_match_include(selection): - edit_rule_obj = selection_to_rule_obj(rule_obj, selection) - prompt, oldpath = edit_rule_obj.edit_header() - - newpath = aaui.UI_GetString(prompt, oldpath) - if newpath: - try: - input_matches_path = rule_obj.validate_edit(newpath) # note that we check against the original rule_obj here, not edit_rule_obj (which might be based on a globbed path) - except AppArmorException: - aaui.UI_Important(_('The path you entered is invalid (not starting with / or a variable)!')) - continue - - if not input_matches_path: - ynprompt = _('The specified path does not match this log entry:\n\n Log Entry: %(path)s\n Entered Path: %(ans)s\nDo you really want to use this path?') % { 'path': oldpath, 'ans': newpath } - key = aaui.UI_YesNo(ynprompt, 'n') - if key == 'n': - continue - - edit_rule_obj.store_edit(newpath) - options, default_option = add_to_options(options, edit_rule_obj.get_raw()) - apparmor.aa.user_globs[newpath] = AARE(newpath, True) - - else: - done = False + if not apparmor.aa.sev_db: + apparmor.aa.sev_db = apparmor.severity.Severity(apparmor.aa.CONFDIR + '/severity.db', _('unknown')) + + apparmor.aa.ask_the_questions(log_dict) if __name__ == '__main__': main() === modified file 'utils/apparmor/aa.py' --- utils/apparmor/aa.py 2016-12-30 23:48:41 +0000 +++ utils/apparmor/aa.py 2017-01-20 00:20:41 +0000 @@ -1,6 +1,6 @@ # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta -# Copyright (C) 2014-2016 Christian Boltz +# Copyright (C) 2014-2017 Christian Boltz # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public @@ -74,8 +74,6 @@ debug_logger = DebugLogger('aa') CONFDIR = '/etc/apparmor' -running_under_genprof = False -unimplemented_warning = False # The database for severity sev_db = None @@ -99,12 +97,7 @@ # format: user_globs['/foo*'] = AARE('/foo*') user_globs = {} -# The key for representing bare "file," rules -ALL = '\0ALL' - ## Variables used under logprof -### Were our -t = hasher() # dict() transitions = hasher() aa = hasher() # Profiles originally in sd, replace by aa @@ -114,13 +107,10 @@ log = [] pid = dict() -seen = hasher() # dir() profile_changes = hasher() prelog = hasher() -log_dict = hasher() # dict() changed = dict() created = [] -skip = hasher() helpers = dict() # Preserve this between passes # was our ### logprof ends @@ -1486,16 +1476,17 @@ return globs -def ask_the_questions(): +def ask_the_questions(log_dict): for aamode in sorted(log_dict.keys()): # Describe the type of changes if aamode == 'PERMITTING': aaui.UI_Info(_('Complain-mode changes:')) elif aamode == 'REJECTING': aaui.UI_Info(_('Enforce-mode changes:')) + elif aamode == 'merge': + pass # aa-mergeprof else: - # This is so wrong! - fatal_error(_('Invalid mode found: %s') % aamode) + raise AppArmorBug(_('Invalid mode found: %s') % aamode) for profile in sorted(log_dict[aamode].keys()): # Update the repo profiles @@ -1513,16 +1504,83 @@ for hat in hats: - if not aa[profile].get(hat).get('file'): - # Ignore log events for a non-existing profile or child profile. Such events can occour - # after deleting a profile or hat manually, or when processing a foreign log. - # (Checking for 'file' is a simplified way to check if it's a profile_storage() struct.) - debug_logger.debug("Ignoring events for non-existing profile %s" % combine_name(profile, hat)) - continue + if not aa[profile].get(hat, {}).get('file'): + if aamode != 'merge': + # Ignore log events for a non-existing profile or child profile. Such events can occour + # after deleting a profile or hat manually, or when processing a foreign log. + # (Checking for 'file' is a simplified way to check if it's a profile_storage() struct.) + debug_logger.debug("Ignoring events for non-existing profile %s" % combine_name(profile, hat)) + continue + + ans = '' + while ans not in ['CMD_ADDHAT', 'CMD_ADDSUBPROFILE', 'CMD_DENY']: + q = aaui.PromptQuestion() + q.headers += [_('Profile'), profile] + + if log_dict[aamode][profile][hat]['profile']: + q.headers += [_('Requested Subprofile'), hat] + q.functions.append('CMD_ADDSUBPROFILE') + else: + q.headers += [_('Requested Hat'), hat] + q.functions.append('CMD_ADDHAT') + + q.functions += ['CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED'] + + q.default = 'CMD_DENY' + + ans = q.promptUser()[0] + + if ans == 'CMD_FINISHED': + return + + if ans == 'CMD_DENY': + continue # don't ask about individual rules if the user doesn't want the additional subprofile/hat + + if log_dict[aamode][profile][hat]['profile']: + aa[profile][hat] = profile_storage(profile, hat, 'mergeprof ask_the_questions() - missing subprofile') + aa[profile][hat]['profile'] = True + else: + aa[profile][hat] = profile_storage(profile, hat, 'mergeprof ask_the_questions() - missing hat') + aa[profile][hat]['profile'] = False + + #Add the includes from the other profile to the user profile + done = False + + options = [] + for inc in log_dict[aamode][profile][hat]['include'].keys(): + if not inc in aa[profile][hat]['include'].keys(): + options.append('#include <%s>' %inc) + + default_option = 1 + + q = aaui.PromptQuestion() + q.options = options + q.selected = default_option - 1 + q.headers = [_('File includes'), _('Select the ones you wish to add')] + q.functions = ['CMD_ALLOW', 'CMD_IGNORE_ENTRY', 'CMD_ABORT', 'CMD_FINISHED'] + q.default = 'CMD_ALLOW' + + while not done and options: + ans, selected = q.promptUser() + if ans == 'CMD_IGNORE_ENTRY': + done = True + elif ans == 'CMD_ALLOW': + selection = options[selected] + inc = re_match_include(selection) + deleted = apparmor.aa.delete_duplicates(aa[profile][hat], inc) + aa[profile][hat]['include'][inc] = True + options.pop(selected) + aaui.UI_Info(_('Adding %s to the file.') % selection) + if deleted: + aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) + elif ans == 'CMD_FINISHED': + return + + # check for and ask about conflicting exec modes + ask_conflict_mode(profile, hat, aa[profile][hat], log_dict[aamode][profile][hat]) for ruletype in ruletypes: for rule_obj in log_dict[aamode][profile][hat][ruletype].rules: - # XXX aa-mergeprof also has this code - if you change it, keep aa-mergeprof in sync! if is_known_rule(aa[profile][hat], ruletype, rule_obj): continue @@ -1655,7 +1713,6 @@ else: done = False - # END of code (mostly) shared with aa-mergeprof def selection_to_rule_obj(rule_obj, selection): rule_type = type(rule_obj) @@ -1726,6 +1783,39 @@ return deleted +def ask_conflict_mode(profile, hat, old_profile, merge_profile): + '''ask user about conflicting exec rules''' + for oldrule in old_profile['file'].rules: + conflictingrules = merge_profile['file'].get_exec_conflict_rules(oldrule) + + if conflictingrules.rules: + q = aaui.PromptQuestion() + q.headers = [_('Path'), oldrule.path.regex] + q.headers += [_('Select the appropriate mode'), ''] + options = [] + options.append(oldrule.get_clean()) + for rule in conflictingrules.rules: + options.append(rule.get_clean()) + q.options = options + q.functions = ['CMD_ALLOW', 'CMD_ABORT'] + done = False + while not done: + ans, selected = q.promptUser() + if ans == 'CMD_ALLOW': + if selected == 0: + pass # just keep the existing rule + elif selected > 0: + # replace existing rule with merged one + old_profile['file'].delete(oldrule) + old_profile['file'].add(conflictingrules.rules[selected - 1]) + else: + raise AppArmorException(_('Unknown selection')) + + for rule in conflictingrules.rules: + merge_profile['file'].delete(rule) # make sure aa-mergeprof doesn't ask to add conflicting rules later + + done = True + def match_includes(profile, rule_type, rule_obj): newincludes = [] for incname in include.keys(): @@ -1769,9 +1859,7 @@ def do_logprof_pass(logmark='', passno=0, pid=pid): # set up variables for this pass -# t = hasher() # transitions = hasher() -# seen = hasher() # XXX global? global log log = [] global existing_profiles @@ -1779,9 +1867,7 @@ # aa = hasher() # profile_changes = hasher() # prelog = hasher() -# log_dict = hasher() # changed = dict() -# skip = hasher() # XXX global? # filelist = hasher() aaui.UI_Info(_('Reading log entries from %s.') % logfile) @@ -1811,9 +1897,9 @@ for pid in sorted(profile_changes.keys()): set_process(pid, profile_changes[pid]) - collapse_log() + log_dict = collapse_log() - ask_the_questions() + ask_the_questions(log_dict) if aaui.UI_mode == 'yast': # To-Do @@ -2019,6 +2105,7 @@ process.close() def collapse_log(): + log_dict = hasher() for aamode in prelog.keys(): for profile in prelog[aamode].keys(): for hat in prelog[aamode][profile].keys(): @@ -2099,6 +2186,8 @@ if not is_known_rule(aa[profile][hat], 'signal', signal_event): log_dict[aamode][profile][hat]['signal'].add(signal_event) + return log_dict + def is_skippable_file(path): """Returns True if filename matches something to be skipped (rpm or dpkg backup files, hidden files etc.) The list of skippable files needs to be synced with apparmor initscript and libapparmor _aa_is_blacklisted() === modified file 'utils/test/test-libapparmor-test_multi.py' --- utils/test/test-libapparmor-test_multi.py 2016-11-01 20:40:29 +0000 +++ utils/test/test-libapparmor-test_multi.py 2017-01-19 15:52:38 +0000 @@ -214,7 +214,6 @@ apparmor.aa.log = dict() apparmor.aa.aa = apparmor.aa.hasher() apparmor.aa.prelog = apparmor.aa.hasher() - apparmor.aa.log_dict = apparmor.aa.hasher() profile = parsed_event['profile'] hat = profile @@ -229,12 +228,12 @@ for root in log: apparmor.aa.handle_children('', '', root) # interactive for exec events! - apparmor.aa.collapse_log() + 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(apparmor.aa.log_dict[aamode][profile], profile, None) + new_profile = apparmor.aa.serialize_profile(log_dict[aamode][profile], profile, None) expected_profile = read_file('%s.profile' % params) vim:ft=diff