From: sven.vermeulen@siphos.be (Sven Vermeulen) Date: Mon, 20 Nov 2017 14:29:31 +0100 Subject: [refpolicy] [PATCH v2 2/7] Update segenxml to include support for templated booleans and tunables In-Reply-To: <20171120132936.25695-1-sven.vermeulen@siphos.be> References: <20171120132936.25695-1-sven.vermeulen@siphos.be> Message-ID: <20171120132936.25695-3-sven.vermeulen@siphos.be> To: refpolicy@oss.tresys.com List-Id: refpolicy.oss.tresys.com 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. Signed-off-by: Sven Vermeulen --- support/segenxml.py | 77 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 68 insertions(+), 9 deletions(-) diff --git a/support/segenxml.py b/support/segenxml.py index e37ea041..46a4a720 100644 --- a/support/segenxml.py +++ b/support/segenxml.py @@ -44,6 +44,7 @@ INTERFACE = re.compile(r"^\s*(interface|template)\(`(\w*)'") # "gen_tunable(allow_kerberos, false)" # -> ("tunable", "allow_kerberos", "false") BOOLEAN = re.compile(r"^\s*gen_(tunable|bool)\(\s*(\w*)\s*,\s*(true|false)\s*\)") +TEMPLATE_BOOLEAN = re.compile(r"^\s*gen_(tunable|bool)\(\s*([\w\$]*)\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*\)" # -> ("") # "## 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] \n\n" % sys.argv[0]) + sys.stdout.write("usage: %s [-w] [-T ] [-mtb] \n\n" % sys.argv[0]) sys.stdout.write("-w --warn\t\t\tshow warnings\n"+\ "-m --module \t\tname of module to process\n"+\ "-t --tunable \t\tname of global tunable file to process\n"+\ - "-b --boolean \t\tname of global boolean file to process\n\n") + "-b --boolean \t\tname of global boolean file to process\n"+\ + "-T --templates \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) -- 2.13.6