--- a/utils/apparmor/aa.py +++ b/utils/apparmor/aa.py @@ -2636,20 +2636,17 @@ RE_RULE_HAS_COMMA = re.compile('^' + __r RE_HAS_COMMENT_SPLIT = re.compile('^(?P' + __re_no_or_quoted_hash + ')' + # store in 'not_comment' group '(?P#.*)$') # match trailing comment and store in 'comment' group -def parse_profile_data(data, file, do_include): - profile_data = hasher() - profile = None - hat = None +def parse_profile_data(data_lines, file, do_include, allow_undefined_vars=False, profile_data=None, profile=None, hat=None): + if profile_data is None: + profile_data = hasher() + # TODO: propagate most of this state to load_include(): in_contained_hat = None repo_data = None parsed_profiles = [] initial_comment = '' lastline = None - if do_include: - profile = file - hat = file - for lineno, line in enumerate(data): + for lineno, line in enumerate(data_lines): line = line.strip() if not line: continue @@ -2836,11 +2833,11 @@ def parse_profile_data(data, file, do_in if profile: if not profile_data[profile][hat].get('lvar', False): profile_data[profile][hat]['lvar'][list_var] = [] - store_list_var(profile_data[profile]['lvar'], list_var, value, var_operation) + store_list_var(profile_data[profile]['lvar'], list_var, value, var_operation, allow_undefined_vars) else: if not filelist[file].get('lvar', False): filelist[file]['lvar'][list_var] = [] - store_list_var(filelist[file]['lvar'], list_var, value, var_operation) + store_list_var(filelist[file]['lvar'], list_var, value, var_operation, allow_undefined_vars) elif RE_PROFILE_CONDITIONAL.search(line): # Conditional Boolean @@ -2979,6 +2976,11 @@ def parse_profile_data(data, file, do_in filelist[file] = hasher() filelist[file]['include'][include_name] = True # If include is a directory + # TODO: combine with dir logic in load_include, or explain why not. + # - should load_include also attend to is_skippable_file()? + # - should this code recurse? + # - if it does, should is_skippable_file be consulted for files in + # nested subdirectories (it currently is not checked)? if os.path.isdir(profile_dir + '/' + include_name): for path in os.listdir(profile_dir + '/' + include_name): path = path.strip() @@ -2987,11 +2989,9 @@ def parse_profile_data(data, file, do_in if os.path.isfile(profile_dir + '/' + include_name + '/' + path): file_name = include_name + '/' + path file_name = file_name.replace(profile_dir + '/', '') - if not include.get(file_name, False): - load_include(file_name) + load_include(file_name, profile_data=profile_data, profile=profile, hat=hat) else: - if not include.get(include_name, False): - load_include(include_name) + load_include(include_name, profile_data=profile_data, profile=profile, hat=hat) elif RE_PROFILE_NETWORK.search(line): matches = RE_PROFILE_NETWORK.search(line).groups() @@ -3263,7 +3263,7 @@ def is_active_profile(pname): else: return False -def store_list_var(var, list_var, value, var_operation): +def store_list_var(var, list_var, value, var_operation, allow_undefined=False): """Store(add new variable or add values to variable) the variables encountered in the given list_var""" vlist = separate_vars(value) if var_operation == '=': @@ -3274,9 +3274,11 @@ def store_list_var(var, list_var, value, raise AppArmorException(_('An existing variable redefined: %s') % list_var) elif var_operation == '+=': if var.get(list_var, False): - var[list_var] = set(var[list_var] + vlist) - else: + var[list_var] = set(var[list_var]) | set(vlist) + elif not allow_undefined: raise AppArmorException(_('Values added to a non-existing variable: %s') % list_var) + else: + pass # ignore, we're just traversing includes else: raise AppArmorException(_('Unknown variable operation: %s') % var_operation) @@ -4113,11 +4115,11 @@ def serialize_profile_from_old_profile(p value = strip_quotes(matches[2]) var_set = hasher() if profile: - store_list_var(var_set, list_var, value, var_operation) + store_list_var(var_set, list_var, value, var_operation, allow_undefined_vars) if not var_set[list_var] == write_prof_data['lvar'].get(list_var, False): correct = False else: - store_list_var(var_set, list_var, value, var_operation) + store_list_var(var_set, list_var, value, var_operation, allow_undefined_vars) if not var_set[list_var] == write_filelist['lvar'].get(list_var, False): correct = False @@ -4509,7 +4511,7 @@ def get_include_data(filename): raise AppArmorException(_('File Not Found: %s') % filename) return data -def load_include(incname): +def load_include(incname, profile_data=None, profile=None, hat=None): load_includeslist = [incname] if include.get(incname, {}).get(incname, False): return 0 @@ -4517,14 +4519,23 @@ def load_include(incname): incfile = load_includeslist.pop(0) if os.path.isfile(profile_dir + '/' + incfile): data = get_include_data(incfile) - incdata = parse_profile_data(data, incfile, True) - #print(incdata) - if not incdata: - # If include is empty, simply push in a placeholder for it - # because other profiles may mention them - incdata = hasher() - incdata[incname] = hasher() - attach_profile_data(include, incdata) + inc_profile = profile or incfile + incdata = parse_profile_data(data, incfile, True, + allow_undefined_vars=(profile_data is None), + profile_data=profile_data, + profile=inc_profile, + hat=hat) + if profile_data is None: + # we're just scanning includes: + # (otherwise, we have to re-parse) + print(incdata) + if not incdata: + # If include is empty, simply push in a placeholder for it + # because other profiles may mention them + incdata = hasher() + incdata[incname] = hasher() + attach_profile_data(include, incdata) + #If the include is a directory means include all subfiles elif os.path.isdir(profile_dir + '/' + incfile): load_includeslist += list(map(lambda x: incfile + '/' + x, os.listdir(profile_dir + '/' + incfile)))