diff options
author | Sven Vermeulen <sven.vermeulen@siphos.be> | 2018-03-25 13:56:36 +0200 |
---|---|---|
committer | Jason Zaman <jason@perfinion.com> | 2018-06-14 17:37:51 +0800 |
commit | c4e04c39e6f8db851f9e3d5931df974746db9f77 (patch) | |
tree | 31021b56f13413373409c0f6c1c54e5fcbb93e77 /support | |
parent | Add gentemplates.sh to extract template content (diff) | |
download | hardened-refpolicy-c4e04c39e6f8db851f9e3d5931df974746db9f77.tar.gz hardened-refpolicy-c4e04c39e6f8db851f9e3d5931df974746db9f77.tar.bz2 hardened-refpolicy-c4e04c39e6f8db851f9e3d5931df974746db9f77.zip |
Update segenxml to include support for templated booleans and tunables
The segenxml tool is used to generate documentation regarding the policy
definitions. Its output is an XML file that contains the in-line
comments associated with boolean generation as well as interface
definitions.
With booleans also generated inside templates, this information was
(until now) ignored. Templates such as apache's apache_content_template
which created new booleans were not properly documented, as the
in-template comments were ignored.
In this patch, we will go over module code first and seek template
calls. When a template call is matched, the module code is updated
(expanded) with the template content (while substituting the arguments
to get a proper code listing). Only after all templates have been
expanded we seek the necessary boolean definitions.
Changes since v2:
- Fix BOOLEAN statements to match backtick (`) and tick (') usages as
well
- Fix match for arguments to also include multiple entries ( { ... } )
Changes since v1:
- Also apply the regexp on BOOLEAN to allow generating templated
boolean/tunable documentation
Signed-off-by: Sven Vermeulen <sven.vermeulen@siphos.be>
Diffstat (limited to 'support')
-rw-r--r-- | support/segenxml.py | 79 |
1 files changed, 69 insertions, 10 deletions
diff --git a/support/segenxml.py b/support/segenxml.py index e37ea041e..115f28700 100644 --- a/support/segenxml.py +++ b/support/segenxml.py @@ -43,7 +43,8 @@ INTERFACE = re.compile(r"^\s*(interface|template)\(`(\w*)'") # -> ("bool", "secure_mode", "false") # "gen_tunable(allow_kerberos, false)" # -> ("tunable", "allow_kerberos", "false") -BOOLEAN = re.compile(r"^\s*gen_(tunable|bool)\(\s*(\w*)\s*,\s*(true|false)\s*\)") +BOOLEAN = re.compile(r"^\s*gen_(tunable|bool)\(\s*\`?\s*(\w*)\s*\'?\s*,\s*(true|false)\s*\)") +TEMPLATE_BOOLEAN = re.compile(r"^\s*gen_(tunable|bool)\(\s*\`?\s*([\w\$]*)\s*\'?\s*,\s*(true|false)\s*\)") # Matches a XML comment in the policy, which is defined as any line starting # with two # and at least one character of white space. Will give the single @@ -54,8 +55,16 @@ BOOLEAN = re.compile(r"^\s*gen_(tunable|bool)\(\s*(\w*)\s*,\s*(true|false)\s*\)" # -> ("<summary>") # "## The domain allowed access. " # -> ("The domain allowed access.") -XML_COMMENT = re.compile(r"^##\s+(.*?)\s*$") +XML_COMMENT = re.compile(r"^\s*##\s+(.*?)\s*$") +# Matches a template call in the policy, which is defined as any line having +# a function call like structure, being a string, followed by a set of +# arguments between an opening and closing bracket. Regexp cannot deal with +# unknown number of arguments, so we will split arguments in the code later on. +# Some examples: +# "userdom_user_access_template(gpg, gpg_t)" +# "zarafa_domain_template(gateway)" +TEMPLATE_CALL = re.compile(r"^\s*(\w*_template)\(\s*(\w*)\s*(?:,\s*(?:[^,)]*)\s*)*\)") # FUNCTIONS def getModuleXML(file_name): @@ -164,7 +173,13 @@ def getModuleXML(file_name): interface = None continue - + # If the line is a boolean/tunable definition, ignore it for now (these + # lines are processed later on) and dismiss the XML comment received + # thus far as it is otherwise attributed to an interface. + tunable = TEMPLATE_BOOLEAN.match(line) + if tunable: + temp_buf = [] + continue # If the file just had a header, add the comments to the module buffer. if finding_header: @@ -197,6 +212,49 @@ def getTunableXML(file_name, kind): tunable_buf = [] temp_buf = [] + tunable_processed_code = [] + + # We first go through the code and substitute template calls with the + # complete template content. This needs to happen iteratively, because + # a template can call another template. In order to ensure no cyclic + # template calls keep us busy, we max out at 9999 substitutions + has_changed = True + subst_threshold = 9999 + while (has_changed and (subst_threshold > 0)): + has_changed = False + for line in tunable_code: + # Get the template call match + template_call = TEMPLATE_CALL.match(line) + # If we reach a template call, read in the template data + # from the template directory, but substitute all $1 with + # the second match, $2 with the third match, etc. + if template_call: + # Read template file based on template_call.group(1) + try: + template_file = open(templatedir + "/" + template_call.group(1) + ".iftemplate", "r") + template_code = template_file.readlines() + template_file.close() + except OSError: + warning("cannot open file %s for read, bailing out" % templatedir + "/" + template_call.group(1) + ".iftemplate") + return [] + # Substitute content (i.e. $1 for argument 1, $2 for argument 2, etc.) + template_split = re.findall(r"[\w\" {}]+", line.strip()) + for index, item in enumerate(template_code): + for group in range(1, len(template_split)): + template_code[index] = template_code[index].replace("$" + str(group), template_split[group].strip()) + # Now 'inject' the code in the tunable_code variable + tunable_processed_code.extend(template_code) + has_changed = True + subst_threshold -= 1 + else: + tunable_processed_code.append(line) + # It is a bad practice to try and update lists while in a loop, so we + # created an intermediate one and are now assigning it back + tunable_code = tunable_processed_code + tunable_processed_code = [] + # If subst_threshold is 0 or less we want to know + if (subst_threshold <= 0): + warning("Detected a possible loop in policy code and template usage") # Find tunables and booleans line by line and use the comments above # them. @@ -251,14 +309,15 @@ def usage(): Displays a message describing the proper usage of this script. """ - sys.stdout.write("usage: %s [-w] [-mtb] <file>\n\n" % sys.argv[0]) + sys.stdout.write("usage: %s [-w] [-T <templatedir>] [-mtb] <file>\n\n" % sys.argv[0]) sys.stdout.write("-w --warn\t\t\tshow warnings\n"+\ "-m --module <file>\t\tname of module to process\n"+\ "-t --tunable <file>\t\tname of global tunable file to process\n"+\ - "-b --boolean <file>\t\tname of global boolean file to process\n\n") + "-b --boolean <file>\t\tname of global boolean file to process\n"+\ + "-T --templates <dir>\t\tname of template directory to use\n\n") sys.stdout.write("examples:\n") - sys.stdout.write("> %s -w -m policy/modules/apache\n" % sys.argv[0]) + sys.stdout.write("> %s -w -T tmp/templates -m policy/modules/apache\n" % sys.argv[0]) sys.stdout.write("> %s -t policy/global_tunables\n" % sys.argv[0]) def warning(description): @@ -289,6 +348,7 @@ warn = False module = False tunable = False boolean = False +templatedir = '' # Check that there are command line arguments. if len(sys.argv) <= 1: @@ -297,7 +357,7 @@ if len(sys.argv) <= 1: # Parse command line args try: - opts, args = getopt.getopt(sys.argv[1:], 'whm:t:b:', ['warn', 'help', 'module=', 'tunable=', 'boolean=']) + opts, args = getopt.getopt(sys.argv[1:], 'whm:t:b:T:', ['warn', 'help', 'module=', 'tunable=', 'boolean=', 'templates=']) except getopt.GetoptError: usage() sys.exit(2) @@ -309,13 +369,12 @@ for o, a in opts: sys.exit(0) elif o in ('-m', '--module'): module = a - break elif o in ('-t', '--tunable'): tunable = a - break elif o in ('-b', '--boolean'): boolean = a - break + elif o in ('-T', '--templates'): + templatedir = a else: usage() sys.exit(2) |