aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSven Vermeulen <sven.vermeulen@siphos.be>2018-03-25 13:56:36 +0200
committerJason Zaman <jason@perfinion.com>2018-06-14 17:37:51 +0800
commitc4e04c39e6f8db851f9e3d5931df974746db9f77 (patch)
tree31021b56f13413373409c0f6c1c54e5fcbb93e77 /support/segenxml.py
parentAdd gentemplates.sh to extract template content (diff)
downloadhardened-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/segenxml.py')
-rw-r--r--support/segenxml.py79
1 files changed, 69 insertions, 10 deletions
diff --git a/support/segenxml.py b/support/segenxml.py
index e37ea041..115f2870 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)