#!/usr/bin/python -E # # Author(s): Caleb Case # # Adapted from the bash/awk scripts mkflask.sh and mkaccess_vector.sh # import getopt import os import sys import re class ParseError(Exception): def __init__(self, type, file, line): self.type = type self.file = file self.line = line def __str__(self): typeS = self.type if type(self.type) is not str: typeS = Flask.CONSTANT_S[self.type] return "Parse Error: Unexpected %s on line %d of %s." % (typeS, self.line, self.file) class DuplicateError(Exception): def __init__(self, type, file, line, symbol): self.type = type self.file = file self.line = line self.symbol = symbol def __str__(self): typeS = self.type if type(self.type) is not str: typeS = Flask.CONSTANT_S[self.type] return "Duplicate Error: Duplicate %s '%s' on line %d of %s." % (typeS, self.symbol, self.line, self.file) class UndefinedError(Exception): def __init__(self, type, file, line, symbol): self.type = type self.file = file self.line = line self.symbol = symbol def __str__(self): typeS = self.type if type(self.type) is not str: typeS = Flask.CONSTANT_S[self.type] return "Undefined Error: %s '%s' is not defined but used on line %d of %s." % (typeS, self.symbol, self.line, self.file) class UnusedError(Exception): def __init__(self, info): self.info = info def __str__(self): return "Unused Error: %s" % self.info class Flask: ''' FLASK container class with utilities for parsing definition files and creating c header files. ''' #Constants used in definitions parsing. WHITE = re.compile(r'^\s*$') COMMENT = re.compile(r'^\s*#') USERFLAG = re.compile(r'# userspace') CLASS = re.compile(r'^class (?P\w+)') COMMON = re.compile(r'^common (?P\w+)') INHERITS = re.compile(r'^inherits (?P\w+)') OPENB = re.compile(r'^{') VECTOR = re.compile(r'^\s*(?P\w+)') CLOSEB = re.compile(r'^}') SID = re.compile(r'^sid (?P\w+)') EOF = "end of file" #Constants used in header generation. USERSPACE = 0 KERNEL = 1 CONSTANT_S = { \ #parsing constants WHITE : "whitespace", \ COMMENT : "comment", \ USERFLAG : "userspace flag", \ CLASS : "class definition", \ COMMON : "common definition", \ INHERITS : "inherits definition", \ OPENB : "'{'", \ VECTOR : "access vector definition", \ CLOSEB : "'}'", \ SID : "security identifier", \ EOF : "end of file", \ #generation constants USERSPACE : "userspace mode", \ KERNEL : "kernel mode", \ } def __init__(self, warn = True): self.WARN = warn self.autogen = "/* This file is automatically generated. Do not edit. */\n" self.commons = [] self.user_commons = [] self.common = {} self.classes = [] self.vectors = [] self.vector = {} self.userspace = {} self.sids = [] self.inherits = {} def warning(self, msg): ''' Prints a warning message out to stderr if warnings are enabled. ''' if self.WARN: sys.stderr.write("Warning: %s\n" % msg) def parseClasses(self, path): ''' Parses security class definitions from the given path. ''' classes = [] input = open(path, 'r') number = 0 for line in input: number += 1 m = self.COMMENT.search(line) if m: continue m = self.WHITE.search(line) if m: continue m = self.CLASS.search(line) if m: g = m.groupdict() c = g['name'] if c in classes: raise DuplicateError, (self.CLASS, path, number, c) classes.append(c) if self.USERFLAG.search(line): self.userspace[c] = True else: self.userspace[c] = False continue raise ParseError, ("data. Was expecting either a comment, whitespace, or class definition. ", path, number) self.classes = classes return classes def parseSids(self, path): ''' Parses initial SID definitions from the given path. ''' sids = [] input = open(path, 'r') for line in input: m = self.COMMENT.search(line) if m: continue m = self.WHITE.search(line) if m: continue m = self.SID.search(line) if m: g = m.groupdict() s = g['name'] if s in sids: raise DuplicateError, (self.SID, path, number, s) sids.append(s) continue raise ParseError, ("data. Was expecting either a comment, whitespace, or security identifier. ", path, number) self.sids = sids return sids def parseVectors(self, path): ''' Parses access vector definitions from the given path. ''' vectors = [] vector = {} commons = [] common = {} inherits = {} user_commons = {} input = open(path, 'r') # states NONE = 0 COMMON = 1 CLASS = 2 INHERIT = 3 OPEN = 4 state = NONE state2 = NONE number = 0 for line in input: number += 1 m = self.COMMENT.search(line) if m: continue m = self.WHITE.search(line) if m: if state == INHERIT: state = NONE continue m = self.COMMON.search(line) if m: if state != NONE: raise ParseError, (self.COMMON, path, number) g = m.groupdict() c = g['name'] if c in commons: raise DuplicateError, (self.COMMON, path, number, c) commons.append(c) common[c] = [] user_commons[c] = True state = COMMON continue m = self.CLASS.search(line) if m: if state != NONE: raise ParseError, (self.CLASS, number) g = m.groupdict() c = g['name'] if c in vectors: raise DuplicateError, (self.CLASS, path, number, c) if c not in self.classes: raise UndefinedError, (self.CLASS, path, number, c) vectors.append(c) vector[c] = [] state = CLASS continue m = self.INHERITS.search(line) if m: if state != CLASS: raise ParseError, (self.INHERITS, number) g = m.groupdict() i = g['name'] if c in inherits: raise DuplicateError, (self.INHERITS, path, number, c) if i not in common: raise UndefinedError, (self.COMMON, path, number, i) inherits[c] = i state = INHERIT if not self.userspace[c]: user_commons[i] = False continue m = self.OPENB.search(line) if m: if (state != CLASS \ and state != INHERIT \ and state != COMMON) \ or state2 != NONE: raise ParseError, (self.OPENB, path, number) state2 = OPEN continue m = self.VECTOR.search(line) if m: if state2 != OPEN: raise ParseError, (self.VECTOR, path, number) g = m.groupdict() v = g['name'] if state == CLASS or state == INHERIT: if v in vector[c]: raise DuplicateError, (self.VECTOR, path, number, v) vector[c].append(v) elif state == COMMON: if v in common[c]: raise DuplicateError, (self.VECTOR, path, number, v) common[c].append(v) continue m = self.CLOSEB.search(line) if m: if state2 != OPEN: raise ParseError, (self.CLOSEB, path, number) state = NONE state2 = NONE c = None continue raise ParseError, ("data", path, number) if state != NONE and state2 != NONE: raise ParseError, (self.EOF, path, number) cvdiff = set(self.classes) - set(vectors) if cvdiff: raise UnusedError, "Not all security classes were used in access vectors: %s" % cvdiff # the inverse of this will be caught as an undefined class error self.commons = commons self.user_commons = user_commons self.common = common self.vectors = vectors self.vector = vector self.inherits = inherits return vector def createHeaders(self, path, mode = USERSPACE): ''' Creates the C header files in the specified MODE and outputs them to give PATH. ''' headers = { \ 'av_inherit.h' : self.createAvInheritH(mode), \ 'av_perm_to_string.h' : self.createAvPermToStringH(mode), \ 'av_permissions.h' : self.createAvPermissionsH(mode), \ 'class_to_string.h' : self.createClassToStringH(mode), \ 'common_perm_to_string.h' : self.createCommonPermToStringH(mode), \ 'flask.h' : self.createFlaskH(mode), \ 'initial_sid_to_string.h' : self.createInitialSidToStringH(mode) \ } for key, value in headers.items(): of = open(os.path.join(path, key), 'w') of.writelines(value) of.close() def createUL(self, count): fields = [1, 2, 4, 8] return "0x%08xUL" % (fields[count % 4] << 4 * (count / 4)) def createAvInheritH(self, mode = USERSPACE): ''' ''' results = [] results.append(self.autogen) for c in self.vectors: if self.inherits.has_key(c): i = self.inherits[c] count = len(self.common[i]) if not (mode == self.KERNEL and self.userspace[c]): results.append(" S_(SECCLASS_%s, %s, %s)\n" % (c.upper(), i, self.createUL(count))) return results def createAvPermToStringH(self, mode = USERSPACE): ''' ''' results = [] results.append(self.autogen) for c in self.vectors: for p in self.vector[c]: if not (mode == self.KERNEL and self.userspace[c]): results.append(" S_(SECCLASS_%s, %s__%s, \"%s\")\n" % (c.upper(), c.upper(), p.upper(), p)) return results def createAvPermissionsH(self, mode = USERSPACE): ''' ''' results = [] results.append(self.autogen) width = 57 count = 0 for common in self.commons: count = 0 shift = 0 for p in self.common[common]: if not (mode == self.KERNEL and self.user_commons[common]): columnA = "#define COMMON_%s__%s " % (common.upper(), p.upper()) columnA += "".join([" " for i in range(width - len(columnA))]) results.append("%s%s\n" % (columnA, self.createUL(count))) count += 1 width = 50 # broken for old tools whitespace for c in self.vectors: count = 0 ps = [] if self.inherits.has_key(c): ps += self.common[self.inherits[c]] ps += self.vector[c] for p in ps: columnA = "#define %s__%s " % (c.upper(), p.upper()) columnA += "".join([" " for i in range(width - len(columnA))]) if not (mode == self.KERNEL and self.userspace[c]): results.append("%s%s\n" % (columnA, self.createUL(count))) count += 1 return results def createClassToStringH(self, mode = USERSPACE): ''' ''' results = [] results.append(self.autogen) results.append("/*\n * Security object class definitions\n */\n") if mode == self.KERNEL: results.append(" S_(NULL)\n") else: results.append(" S_(\"null\")\n") for c in self.classes: if mode == self.KERNEL and self.userspace[c]: results.append(" S_(NULL)\n") else: results.append(" S_(\"%s\")\n" % c) return results def createCommonPermToStringH(self, mode = USERSPACE): ''' ''' results = [] results.append(self.autogen) for common in self.commons: if not (mode == self.KERNEL and self.user_commons[common]): results.append("TB_(common_%s_perm_to_string)\n" % common) for p in self.common[common]: results.append(" S_(\"%s\")\n" % p) results.append("TE_(common_%s_perm_to_string)\n\n" % common) return results def createFlaskH(self, mode = USERSPACE): ''' ''' results = [] results.append(self.autogen) results.append("#ifndef _SELINUX_FLASK_H_\n") results.append("#define _SELINUX_FLASK_H_\n") results.append("\n") results.append("/*\n") results.append(" * Security object class definitions\n") results.append(" */\n") count = 0 width = 57 for c in self.classes: count += 1 columnA = "#define SECCLASS_%s " % c.upper() columnA += "".join([" " for i in range(width - len(columnA))]) if not (mode == self.KERNEL and self.userspace[c]): results.append("%s%d\n" % (columnA, count)) results.append("\n") results.append("/*\n") results.append(" * Security identifier indices for initial entities\n") results.append(" */\n") count = 0 width = 56 # broken for old tools whitespace for s in self.sids: count += 1 columnA = "#define SECINITSID_%s " % s.upper() columnA += "".join([" " for i in range(width - len(columnA))]) results.append("%s%d\n" % (columnA, count)) results.append("\n") columnA = "#define SECINITSID_NUM " columnA += "".join([" " for i in range(width - len(columnA))]) results.append("%s%d\n" % (columnA, count)) results.append("\n") results.append("#endif\n") return results def createInitialSidToStringH(self, mode = USERSPACE): ''' ''' results = [] results.append(self.autogen) results.append("static char *initial_sid_to_string[] =\n") results.append("{\n") results.append(" \"null\",\n") for s in self.sids: results.append(" \"%s\",\n" % s) results.append("};\n") results.append("\n") return results def usage(): ''' Returns the usage string. ''' usage = 'Usage: %s -a ACCESS_VECTORS -i INITIAL_SIDS -s SECURITY_CLASSES -o OUTPUT_DIRECTORY -k|-u [-w]\n' % os.path.basename(sys.argv[0]) usage += '\n' usage += ' -a --access_vectors\taccess vector definitions\n' usage += ' -i --initial_sids\tinitial sid definitions\n' usage += ' -s --security_classes\tsecurity class definitions\n' usage += ' -o --output\toutput directory for generated files\n' usage += ' -k --kernel\toutput mode set to kernel (kernel headers contain empty blocks for all classes specified with # userspace in the security_classes file)\n' usage += ' -u --user\toutput mode set to userspace\n' usage += ' -w --nowarnings\tsupresses output of warning messages\n' return usage ########## MAIN ########## if __name__ == '__main__': # Parse command line args try: opts, args = getopt.getopt(sys.argv[1:], 'a:i:s:o:kuwh', ['access_vectors=', 'initial_sids=', 'security_classes=', 'output=', 'kernel', 'user', 'nowarnings', 'help']) except getopt.GetoptError: print(usage()) sys.exit(2) avec = None isid = None secc = None outd = None mode = None warn = True for o, a in opts: if o in ('-h', '--help'): print(usage()) sys.exit(0) elif o in ('-a', '--access_vectors'): avec = a elif o in ('-i', '--initial_sids'): isid = a elif o in ('-s', '--security_classes'): secc = a elif o in ('-o', '--output'): outd = a elif o in ('-k', '--kernel'): if mode != None: print(usage()) sys.exit(2) mode = Flask.KERNEL elif o in ('-u', '--user'): if mode != None: print(usage()) sys.exit(2) mode = Flask.USERSPACE elif o in ('-w', '--nowarnings'): warn = False else: print(usage()) sys.exit(2) if avec == None or \ isid == None or \ secc == None or \ outd == None: print(usage()) sys.exit(2) try: f = Flask(warn) f.parseSids(isid) f.parseClasses(secc) f.parseVectors(avec) f.createHeaders(outd, mode) except Exception, e: print(e) sys.exit(2)