Hello, Change aa.py to use NetworkRule and NetworkRuleset instead of a sub-hasher to store, check and write network rules. In detail: - drop profile_known_network() and use is_known_rule() instead - replace match_net_includes() usage with match_includes() calls - drop delete_net_duplicates(), use the code in NetworkRule(set) instead - make match_net_includes() (still used by aa-mergeprof) a wrapper for match_includes() - drop all the network rule parsing from parse_profile_data() and serialize_profile_from_old_profile() - instead, just call NetworkRule.parse - now that write_net_rules() got fixed, drop it ;-) - change write_netdomain to use NetworkRuleset - drop netrules_access_check() - that's is_covered() now - use 'network' instead of 'netdomain' as storage keyword (log events still use 'netdomain')
Also update cleanprofile.py to use the NetworkRuleset class. This also means to delete the (now superfluous) delete_net_duplicates() function. Finally, there are some changes in regex.py: - change RE_PROFILE_NETWORK in regex.py to named matches and to use RE_COMMA_EOL (not only RE_EOL) - drop the no longer needed RE_NETWORK_FAMILY and RE_NETWORK_FAMILY_TYPE (rule/network.py has regexes that check against the list of available keywords) Note: Some parts of this patch will only apply if you apply my other pending patches first. Diffstat for all 3 patches: apparmor/aa.py | 224 +++--------------------- apparmor/cleanprofile.py | 38 ---- apparmor/regex.py | 4 apparmor/rule/network.py | 210 +++++++++++++++++++++++ test/test-network.py | 428 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 673 insertions(+), 231 deletions(-) [ 46-convert-to-use-NetworkRule.diff ] === modified file 'utils/apparmor/aa.py' --- utils/apparmor/aa.py 2015-04-11 00:20:31 +0000 +++ utils/apparmor/aa.py 2015-04-11 18:04:51 +0000 @@ -45,7 +45,7 @@ RE_PROFILE_BOOLEAN, RE_PROFILE_VARIABLE, RE_PROFILE_CONDITIONAL, RE_PROFILE_CONDITIONAL_VARIABLE, RE_PROFILE_CONDITIONAL_BOOLEAN, RE_PROFILE_BARE_FILE_ENTRY, RE_PROFILE_PATH_ENTRY, RE_PROFILE_NETWORK, - RE_NETWORK_FAMILY_TYPE, RE_NETWORK_FAMILY, RE_PROFILE_CHANGE_HAT, + RE_PROFILE_CHANGE_HAT, RE_PROFILE_HAT_DEF, RE_PROFILE_DBUS, RE_PROFILE_MOUNT, RE_PROFILE_SIGNAL, RE_PROFILE_PTRACE, RE_PROFILE_PIVOT_ROOT, RE_PROFILE_UNIX, RE_RULE_HAS_COMMA, RE_HAS_COMMENT_SPLIT, @@ -54,6 +54,7 @@ import apparmor.rules as aarules from apparmor.rule.capability import CapabilityRuleset, CapabilityRule +from apparmor.rule.network import NetworkRuleset, NetworkRule from apparmor.rule import parse_modifiers from apparmor.yasti import SendDataToYast, GetDataFromYast, shutdown_yast @@ -1450,8 +1451,6 @@ if stub_profile[hat][hat].get('include', False): aa[profile][hat]['include'] = stub_profile[hat][hat]['include'] - aa[profile][hat]['allow']['netdomain'] = hasher() - file_name = aa[profile][profile]['filename'] filelist[file_name]['profiles'][profile][hat] = True @@ -1958,11 +1957,12 @@ for family in sorted(log_dict[aamode][profile][hat]['netdomain'].keys()): # severity handling for net toggles goes here for sock_type in sorted(log_dict[aamode][profile][hat]['netdomain'][family].keys()): - if profile_known_network(aa[profile][hat], family, sock_type): + network_obj = NetworkRule(family, sock_type) + if is_known_rule(aa[profile][hat], 'network', network_obj): continue default_option = 1 options = [] - newincludes = match_net_includes(aa[profile][hat], family, sock_type) + newincludes = match_includes(aa[profile][hat], 'network', network_obj) q = aaui.PromptQuestion() if newincludes: options += list(map(lambda s: '#include <%s>' % s, sorted(set(newincludes)))) @@ -2031,8 +2031,7 @@ aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) else: - aa[profile][hat]['allow']['netdomain']['audit'][family][sock_type] = audit_toggle - aa[profile][hat]['allow']['netdomain']['rule'][family][sock_type] = True + aa[profile][hat]['network'].add(NetworkRule(family, sock_type, audit=audit_toggle)) changed[profile] = True @@ -2040,7 +2039,7 @@ elif ans == 'CMD_DENY': done = True - aa[profile][hat]['deny']['netdomain']['rule'][family][sock_type] = True + aa[profile][hat]['network'].add(NetworkRule(family, sock_type, audit=audit_toggle, deny=True)) changed[profile] = True aaui.UI_Info(_('Denying network access %(family)s %(type)s to profile') % { 'family': family, 'type': sock_type }) @@ -2103,31 +2102,6 @@ newpath = re.sub('/[^/]+(\.[^/]+)$', '/*' + match.groups()[0], newpath) return newpath -def delete_net_duplicates(netrules, incnetrules): - deleted = 0 - hasher_obj = hasher() - copy_netrules = deepcopy(netrules) - if incnetrules and netrules: - incnetglob = False - # Delete matching rules from abstractions - if incnetrules.get('all', False): - incnetglob = True - for fam in copy_netrules['rule'].keys(): - if incnetglob or (type(incnetrules['rule'][fam]) != type(hasher_obj) and incnetrules['rule'][fam]): - if type(netrules['rule'][fam]) == type(hasher_obj): - deleted += len(netrules['rule'][fam].keys()) - else: - deleted += 1 - netrules['rule'].pop(fam) - elif type(netrules['rule'][fam]) != type(hasher_obj) and netrules['rule'][fam]: - continue - else: - for socket_type in copy_netrules['rule'][fam].keys(): - if incnetrules['rule'][fam].get(socket_type, False): - netrules['rule'][fam].pop(socket_type) - deleted += 1 - return deleted - def delete_path_duplicates(profile, incname, allow): deleted = [] for entry in profile[allow]['path'].keys(): @@ -2150,20 +2124,14 @@ # only a subset allow rules may actually be denied if include.get(incname, False): - deleted += delete_net_duplicates(profile['allow']['netdomain'], include[incname][incname]['allow']['netdomain']) - - deleted += delete_net_duplicates(profile['deny']['netdomain'], include[incname][incname]['deny']['netdomain']) - + deleted += profile['network'].delete_duplicates(include[incname][incname]['network']) deleted += profile['capability'].delete_duplicates(include[incname][incname]['capability']) deleted += delete_path_duplicates(profile, incname, 'allow') deleted += delete_path_duplicates(profile, incname, 'deny') elif filelist.get(incname, False): - deleted += delete_net_duplicates(profile['allow']['netdomain'], filelist[incname][incname]['allow']['netdomain']) - - deleted += delete_net_duplicates(profile['deny']['netdomain'], filelist[incname][incname]['deny']['netdomain']) - + deleted += profile['network'].delete_duplicates(filelist[incname][incname]['network']) deleted += profile['capability'].delete_duplicates(filelist[incname][incname]['capability']) deleted += delete_path_duplicates(profile, incname, 'allow') @@ -2172,25 +2140,10 @@ return deleted def match_net_include(incname, family, type): - includelist = [incname] - checked = [] - name = None - if includelist: - name = includelist.pop(0) - while name: - checked.append(name) - if netrules_access_check(include[name][name]['allow']['netdomain'], family, type): - return True - - if include[name][name]['include'].keys() and name not in checked: - includelist += include[name][name]['include'].keys() - - if len(includelist): - name = includelist.pop(0) - else: - name = False - - return False + # still used by aa-mergeprof + network_obj = NetworkRule(family, type) + return match_includes(incname, 'network', network_obj) + def match_cap_includes(profile, capability): # still used by aa-mergeprof @@ -2537,7 +2490,7 @@ 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): + if not is_known_rule(aa[profile][hat], 'network', NetworkRule(family, sock_type)): log_dict[aamode][profile][hat]['netdomain'][family][sock_type] = True @@ -2713,7 +2666,7 @@ profile_data[profile][hat]['flags'] = flags - profile_data[profile][hat]['allow']['netdomain'] = hasher() + profile_data[profile][hat]['network'] = NetworkRuleset() profile_data[profile][hat]['allow']['path'] = hasher() profile_data[profile][hat]['allow']['dbus'] = list() profile_data[profile][hat]['allow']['mount'] = list() @@ -2963,34 +2916,14 @@ load_include(include_name) elif RE_PROFILE_NETWORK.search(line): - matches = RE_PROFILE_NETWORK.search(line).groups() - if not profile: raise AppArmorException(_('Syntax Error: Unexpected network entry found in file: %(file)s line: %(line)s') % { 'file': file, 'line': lineno + 1 }) - audit = False - if matches[0]: - audit = True - allow = 'allow' - if matches[1] and matches[1].strip() == 'deny': - allow = 'deny' - network = matches[2] + # init rule class (if not done yet) + if not profile_data[profile][hat].get('network', False): + profile_data[profile][hat]['network'] = CapabilityRuleset() - if RE_NETWORK_FAMILY_TYPE.search(network): - nmatch = RE_NETWORK_FAMILY_TYPE.search(network).groups() - fam, typ = nmatch[:2] - ##Simply ignore any type subrules if family has True (seperately for allow and deny) - ##This will lead to those type specific rules being lost when written - #if type(profile_data[profile][hat][allow]['netdomain']['rule'].get(fam, False)) == dict: - profile_data[profile][hat][allow]['netdomain']['rule'][fam][typ] = 1 - profile_data[profile][hat][allow]['netdomain']['audit'][fam][typ] = audit - elif RE_NETWORK_FAMILY.search(network): - fam = RE_NETWORK_FAMILY.search(network).groups()[0] - profile_data[profile][hat][allow]['netdomain']['rule'][fam] = True - profile_data[profile][hat][allow]['netdomain']['audit'][fam] = audit - else: - profile_data[profile][hat][allow]['netdomain']['rule']['all'] = True - profile_data[profile][hat][allow]['netdomain']['audit']['all'] = audit # True + profile_data[profile][hat]['network'].add(NetworkRule.parse(line)) elif RE_PROFILE_DBUS.search(line): matches = RE_PROFILE_DBUS.search(line).groups() @@ -3386,39 +3319,10 @@ data = prof_data['capability'].get_clean(depth) return data -def write_net_rules(prof_data, depth, allow): - pre = ' ' * depth - data = [] - allowstr = set_allow_str(allow) - audit = '' - if prof_data[allow].get('netdomain', False): - if prof_data[allow]['netdomain'].get('rule', False) == 'all': - if prof_data[allow]['netdomain']['audit'].get('all', False): - audit = 'audit ' - data.append('%s%snetwork,' % (pre, audit)) - else: - for fam in sorted(prof_data[allow]['netdomain']['rule'].keys()): - audit = '' - if prof_data[allow]['netdomain']['rule'][fam] is True: - if prof_data[allow]['netdomain']['audit'][fam]: - audit = 'audit ' - if fam == 'all': - data.append('%s%s%snetwork,' % (pre, audit, allowstr)) - else: - data.append('%s%s%snetwork %s,' % (pre, audit, allowstr, fam)) - else: - for typ in sorted(prof_data[allow]['netdomain']['rule'][fam].keys()): - if prof_data[allow]['netdomain']['audit'][fam].get(typ, False): - audit = 'audit ' - data.append('%s%s%snetwork %s %s,' % (pre, audit, allowstr, fam, typ)) - if prof_data[allow].get('netdomain', False): - data.append('') - - return data - def write_netdomain(prof_data, depth): - data = write_net_rules(prof_data, depth, 'deny') - data += write_net_rules(prof_data, depth, 'allow') + data = [] + if prof_data.get('network', False): + data = prof_data['network'].get_clean(depth) return data def write_dbus_rules(prof_data, depth, allow): @@ -3776,7 +3680,7 @@ 'include': write_includes, 'rlimit': write_rlimits, 'capability': write_capabilities, - 'netdomain': write_netdomain, + 'network': write_netdomain, 'dbus': write_dbus, 'mount': write_mount, 'signal': write_signal, @@ -3791,7 +3695,7 @@ 'include', 'rlimit', 'capability', - 'netdomain', + 'network', 'dbus', 'mount', 'signal', @@ -3807,7 +3711,7 @@ 'include': False, 'rlimit': False, 'capability': False, - 'netdomain': False, + 'network': False, 'dbus': False, 'mount': True, # not handled otherwise yet 'signal': True, # not handled otherwise yet @@ -4158,44 +4062,13 @@ data.append(line) elif RE_PROFILE_NETWORK.search(line): - matches = RE_PROFILE_NETWORK.search(line).groups() - audit = False - if matches[0]: - audit = True - allow = 'allow' - if matches[1] and matches[1].strip() == 'deny': - allow = 'deny' - network = matches[2] - if RE_NETWORK_FAMILY_TYPE.search(network): - nmatch = RE_NETWORK_FAMILY_TYPE.search(network).groups() - fam, typ = nmatch[:2] - if write_prof_data[hat][allow]['netdomain']['rule'][fam][typ] and write_prof_data[hat][allow]['netdomain']['audit'][fam][typ] == audit: - write_prof_data[hat][allow]['netdomain']['rule'][fam].pop(typ) - write_prof_data[hat][allow]['netdomain']['audit'][fam].pop(typ) - data.append(line) - else: - correct = False - - elif RE_NETWORK_FAMILY.search(network): - fam = RE_NETWORK_FAMILY.search(network).groups()[0] - if write_prof_data[hat][allow]['netdomain']['rule'][fam] and write_prof_data[hat][allow]['netdomain']['audit'][fam] == audit: - write_prof_data[hat][allow]['netdomain']['rule'].pop(fam) - write_prof_data[hat][allow]['netdomain']['audit'].pop(fam) - data.append(line) - else: - correct = False - else: - if write_prof_data[hat][allow]['netdomain']['rule']['all'] and write_prof_data[hat][allow]['netdomain']['audit']['all'] == audit: - write_prof_data[hat][allow]['netdomain']['rule'].pop('all') - write_prof_data[hat][allow]['netdomain']['audit'].pop('all') - data.append(line) - else: - correct = False - - if correct: - if not segments['netdomain'] and True in segments.values(): + network_obj = NetworkRule.parse(line) + if write_prof_data[hat]['network'].is_covered(network_obj, True, True): + if not segments['network'] and True in segments.values(): data += write_prior_segments(write_prof_data[name], segments, line) - segments['netdomain'] = True + segments['network'] = True + write_prof_data[hat]['network'].delete(network_obj) + data.append(line) elif RE_PROFILE_CHANGE_HAT.search(line): matches = RE_PROFILE_CHANGE_HAT.search(line).groups() @@ -4321,41 +4194,6 @@ return False -def profile_known_network(profile, family, sock_type): - if netrules_access_check(profile['deny']['netdomain'], family, sock_type): - return -1 - if netrules_access_check(profile['allow']['netdomain'], family, sock_type): - return 1 - - for incname in profile['include'].keys(): - if netrules_access_check(include[incname][incname]['deny']['netdomain'], family, sock_type): - return -1 - if netrules_access_check(include[incname][incname]['allow']['netdomain'], family, sock_type): - return 1 - - return 0 - -def netrules_access_check(netrules, family, sock_type): - if not netrules: - return 0 - all_net = False - all_net_family = False - net_family_sock = False - if netrules['rule'].get('all', False): - all_net = True - if netrules['rule'].get(family, False) is True: - all_net_family = True - if (netrules['rule'].get(family, False) and - type(netrules['rule'][family]) == type(hasher()) and - sock_type in netrules['rule'][family].keys() and - netrules['rule'][family][sock_type]): - net_family_sock = True - - if all_net or all_net_family or net_family_sock: - return True - else: - return False - def reload_base(bin_path): if not check_for_apparmor(): return None === modified file 'utils/apparmor/cleanprofile.py' --- utils/apparmor/cleanprofile.py 2014-12-16 22:13:25 +0000 +++ utils/apparmor/cleanprofile.py 2015-04-11 22:35:00 +0000 @@ -64,20 +64,18 @@ apparmor.aa.load_include(inc) deleted += apparmor.aa.delete_duplicates(self.other.aa[program][hat], inc) - #Clean the duplicates of caps in other profile + #Clean duplicate rules in other profile if not self.same_file: deleted += self.other.aa[program][hat]['capability'].delete_duplicates(self.profile.aa[program][hat]['capability']) + deleted += self.other.aa[program][hat]['network'].delete_duplicates(self.profile.aa[program][hat]['network']) else: deleted += self.other.aa[program][hat]['capability'].delete_duplicates(None) + deleted += self.other.aa[program][hat]['network'].delete_duplicates(None) #Clean the duplicates of path in other profile deleted += delete_path_duplicates(self.profile.aa[program][hat], self.other.aa[program][hat], 'allow', self.same_file) deleted += delete_path_duplicates(self.profile.aa[program][hat], self.other.aa[program][hat], 'deny', self.same_file) - #Clean the duplicates of net rules in other profile - deleted += delete_net_duplicates(self.profile.aa[program][hat]['allow']['netdomain'], self.other.aa[program][hat]['allow']['netdomain'], self.same_file) - deleted += delete_net_duplicates(self.profile.aa[program][hat]['deny']['netdomain'], self.other.aa[program][hat]['deny']['netdomain'], self.same_file) - return deleted def delete_path_duplicates(profile, profile_other, allow, same_profile=True): @@ -108,33 +106,3 @@ return len(deleted) -def delete_net_duplicates(netrules, netrules_other, same_profile=True): - deleted = 0 - hasher_obj = apparmor.aa.hasher() - if netrules_other and netrules: - netglob = False - # Delete matching rules - if netrules.get('all', False): - netglob = True - # Iterate over a copy of the rules in the other profile - for fam in list(netrules_other['rule'].keys()): - if netglob or (type(netrules['rule'][fam]) != type(hasher_obj) and netrules['rule'][fam]): - if not same_profile: - if type(netrules_other['rule'][fam]) == type(hasher_obj): - deleted += len(netrules_other['rule'][fam].keys()) - else: - deleted += 1 - netrules_other['rule'].pop(fam) - elif type(netrules_other['rule'][fam]) != type(hasher_obj) and netrules_other['rule'][fam]: - if type(netrules['rule'][fam]) != type(hasher_obj) and netrules['rule'][fam]: - if not same_profile: - netrules_other['rule'].pop(fam) - deleted += 1 - else: - for sock_type in list(netrules_other['rule'][fam].keys()): - if netrules['rule'].get(fam, False): - if netrules['rule'][fam].get(sock_type, False): - if not same_profile: - netrules_other['rule'][fam].pop(sock_type) - deleted += 1 - return deleted === modified file 'utils/apparmor/regex.py' --- utils/apparmor/regex.py 2015-04-03 15:28:03 +0000 +++ utils/apparmor/regex.py 2015-04-12 00:33:41 +0000 @@ -39,9 +39,7 @@ RE_PROFILE_CONDITIONAL_BOOLEAN = re.compile('^\s*if\s+(not\s+)?defined\s+(\$\{?\w+\}?)\s*\{\s*(#.*)?$') RE_PROFILE_BARE_FILE_ENTRY = re.compile(RE_AUDIT_DENY + RE_OWNER + 'file' + RE_COMMA_EOL) RE_PROFILE_PATH_ENTRY = re.compile(RE_AUDIT_DENY + RE_OWNER + '(file\s+)?([\"@/].*?)\s+(\S+)(\s+->\s*(.*?))?' + RE_COMMA_EOL) -RE_PROFILE_NETWORK = re.compile(RE_AUDIT_DENY + 'network(.*)' + RE_EOL) -RE_NETWORK_FAMILY_TYPE = re.compile('\s+(\S+)\s+(\S+)\s*,$') -RE_NETWORK_FAMILY = re.compile('\s+(\S+)\s*,$') +RE_PROFILE_NETWORK = re.compile(RE_AUDIT_DENY + 'network(?P<details>\s+.*)?' + RE_COMMA_EOL) RE_PROFILE_CHANGE_HAT = re.compile('^\s*\^(\"??.+?\"??)' + RE_COMMA_EOL) RE_PROFILE_HAT_DEF = re.compile('^\s*(\^|hat\s+)(?P<hat>\"??.+?\"??)\s+((flags=)?\((?P<flags>.+)\)\s+)*\{' + RE_EOL) RE_PROFILE_DBUS = re.compile(RE_AUDIT_DENY + '(dbus\s*,|dbus\s+[^#]*\s*,)' + RE_EOL) Regards, Christian Boltz -- > (Beschwerden bitte an die Verbrecher des jeweiligen Programms) :) Die KDE4-Entwickler sind vermutlich eh noch eingebunkert... [> Karl Thomas Schmidt und Helga Fischer in opensuse-de] -- AppArmor mailing list AppArmor@lists.ubuntu.com Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/apparmor