diff --git sepolgen-1.2.3/ChangeLog sepolgen-1.2.3/ChangeLog index 7cc0a18..bda7a2e 100644 --- sepolgen-1.2.3/ChangeLog +++ sepolgen-1.2.3/ChangeLog @@ -1,3 +1,6 @@ + * Remove additional files when cleaning, from Nicolas Iooss. + * Add support for TYPEBOUNDS statement in INTERFACE policy files, from Miroslav Grepl. + 1.2.3 2016-02-23 * Support latest refpolicy interfaces, from Nicolas Iooss. * Make sepolgen-ifgen output deterministic with Python>=3.3, from Nicolas Iooss. diff --git sepolgen-1.2.3/src/sepolgen/Makefile sepolgen-1.2.3/src/sepolgen/Makefile index 9ac7651..d3aa771 100644 --- sepolgen-1.2.3/src/sepolgen/Makefile +++ sepolgen-1.2.3/src/sepolgen/Makefile @@ -11,5 +11,4 @@ install: all clean: rm -f parser.out parsetab.py rm -f *~ *.pyc - - + rm -rf __pycache__ diff --git sepolgen-1.2.3/src/sepolgen/access.py sepolgen-1.2.3/src/sepolgen/access.py index a5d8698..7606561 100644 --- sepolgen-1.2.3/src/sepolgen/access.py +++ sepolgen-1.2.3/src/sepolgen/access.py @@ -90,6 +90,8 @@ class AccessVector(util.Comparison): self.audit_msgs = [] self.type = audit2why.TERULE self.data = [] + self.obj_path = None + self.base_type = None # when implementing __eq__ also __hash__ is needed on py2 # if object is muttable __hash__ should be None self.__hash__ = None @@ -138,6 +140,29 @@ class AccessVector(util.Comparison): return "allow %s %s:%s %s;" % (self.src_type, self.tgt_type, self.obj_class, self.perms.to_space_str()) + def base_file_type(self): + base_type_array = [] + base_type_array = [self.base_type, self.tgt_type, self.src_type] + return base_type_array + + def __cmp__(self, other): + if self.src_type != other.src_type: + return cmp(self.src_type, other.src_type) + if self.tgt_type != other.tgt_type: + return cmp(self.tgt_type, other.tgt_type) + if self.obj_class != self.obj_class: + return cmp(self.obj_class, other.obj_class) + if len(self.perms) != len(other.perms): + return cmp(len(self.perms), len(other.perms)) + x = list(self.perms) + x.sort() + y = list(other.perms) + y.sort() + for pa, pb in zip(x, y): + if pa != pb: + return cmp(pa, pb) + return 0 + def _compare(self, other, method): try: x = list(self.perms) @@ -257,7 +282,8 @@ class AccessVectorSet: for av in l: self.add_av(AccessVector(av)) - def add(self, src_type, tgt_type, obj_class, perms, audit_msg=None, avc_type=audit2why.TERULE, data=[]): + def add(self, src_type, tgt_type, obj_class, perms, obj_path=None, + base_type=None, audit_msg=None, avc_type=audit2why.TERULE, data=[]): """Add an access vector to the set. """ tgt = self.src.setdefault(src_type, { }) @@ -270,7 +296,9 @@ class AccessVectorSet: access.src_type = src_type access.tgt_type = tgt_type access.obj_class = obj_class + access.obj_path = obj_path access.data = data + access.base_type = base_type access.type = avc_type cls[obj_class, avc_type] = access diff --git sepolgen-1.2.3/src/sepolgen/audit.py sepolgen-1.2.3/src/sepolgen/audit.py index 724d3ea..ef1f6cc 100644 --- sepolgen-1.2.3/src/sepolgen/audit.py +++ sepolgen-1.2.3/src/sepolgen/audit.py @@ -176,6 +176,7 @@ class AVCMessage(AuditMessage): self.exe = "" self.path = "" self.name = "" + self.ino = "" self.accesses = [] self.denial = True self.type = audit2why.TERULE @@ -237,6 +238,10 @@ class AVCMessage(AuditMessage): self.exe = fields[1][1:-1] elif fields[0] == "name": self.name = fields[1][1:-1] + elif fields[0] == "path": + self.path = fields[1][1:-1] + elif fields[0] == "ino": + self.ino = fields[1] if not found_src or not found_tgt or not found_class or not found_access: raise ValueError("AVC message in invalid format [%s]\n" % self.message) @@ -361,7 +366,9 @@ class AuditParser: self.path_msgs = [] self.by_header = { } self.check_input_file = False - + self.inode_dict = { } + self.__store_base_types() + # Low-level parsing function - tries to determine if this audit # message is an SELinux related message and then parses it into # the appropriate AuditMessage subclass. This function deliberately @@ -376,7 +383,9 @@ class AuditParser: # AuditMessage (or subclass) - object representing a parsed # and valid audit message. def __parse_line(self, line): - rec = line.split() + # strip("\x1c\x1d\x1e\x85") is only needed for python2 + # since str.split() in python3 already does this + rec = [x.strip("\x1c\x1d\x1e\x85") for x in line.split()] for i in rec: found = False if i == "avc:" or i == "message=avc:" or i == "msg='avc:": @@ -499,6 +508,61 @@ class AuditParser: return role_types + def __restore_path(self, name, inode): + import subprocess + import os + path = "" + # Optimizing + if name == "" or inode == "": + return path + for d in self.inode_dict: + if d == inode and self.inode_dict[d] == name: + return path + if d == inode and self.inode_dict[d] != name: + return self.inode_dict[d] + if inode not in self.inode_dict.keys(): + self.inode_dict[inode] = name + + command = "locate -b '\%s'" % name + try: + output = subprocess.check_output(command, + stderr=subprocess.STDOUT, + shell=True, + universal_newlines=True) + try: + ino = int(inode) + except ValueError: + pass + for file in output.split("\n"): + try: + if int(os.lstat(file).st_ino) == ino: + self.inode_dict[inode] = path = file + return path + except: + pass + except subprocess.CalledProcessError as e: + pass + return path + + def __store_base_types(self): + import sepolicy + self.base_types = sepolicy.get_types_from_attribute("base_file_type") + + def __get_base_type(self, tcontext, scontext): + import sepolicy + # Prevent unnecessary searching + if (self.old_scontext == scontext and + self.old_tcontext == tcontext): + return + self.old_scontext = scontext + self.old_tcontext = tcontext + for btype in self.base_types: + if btype == tcontext: + for writable in sepolicy.get_writable_files(scontext): + if writable.endswith(tcontext) and writable.startswith(scontext.rstrip("_t")): + return writable + return 0 + def to_access(self, avc_filter=None, only_denials=True): """Convert the audit logs access into a an access vector set. @@ -517,16 +581,23 @@ class AuditParser: audit logs parsed by this object. """ av_set = access.AccessVectorSet() + self.old_scontext = "" + self.old_tcontext = "" for avc in self.avc_msgs: if avc.denial != True and only_denials: continue + base_type = self.__get_base_type(avc.tcontext.type, avc.scontext.type) + if avc.path == "": + avc.path = self.__restore_path(avc.name, avc.ino) if avc_filter: if avc_filter.filter(avc): av_set.add(avc.scontext.type, avc.tcontext.type, avc.tclass, - avc.accesses, avc, avc_type=avc.type, data=avc.data) + avc.accesses, avc.path, base_type, avc, + avc_type=avc.type, data=avc.data) else: av_set.add(avc.scontext.type, avc.tcontext.type, avc.tclass, - avc.accesses, avc, avc_type=avc.type, data=avc.data) + avc.accesses, avc.path, base_type, avc, + avc_type=avc.type, data=avc.data) return av_set class AVCTypeFilter: diff --git sepolgen-1.2.3/src/sepolgen/policygen.py sepolgen-1.2.3/src/sepolgen/policygen.py index 34c8401..f374132 100644 --- sepolgen-1.2.3/src/sepolgen/policygen.py +++ sepolgen-1.2.3/src/sepolgen/policygen.py @@ -82,8 +82,9 @@ class PolicyGenerator: self.module = refpolicy.Module() self.dontaudit = False - + self.mislabled = None self.domains = None + def set_gen_refpol(self, if_set=None, perm_maps=None): """Set whether reference policy interfaces are generated. @@ -153,6 +154,18 @@ class PolicyGenerator: """Return the generated module""" return self.module + def __restore_label(self, av): + import selinux + try: + context = selinux.matchpathcon(av.obj_path, 0) + split = context[1].split(":")[2] + if split != av.tgt_type: + self.mislabled = split + return + except OSError: + pass + self.mislabled = None + def __add_allow_rules(self, avs): for av in avs: rule = refpolicy.AVRule(av) @@ -161,6 +174,34 @@ class PolicyGenerator: rule.comment = "" if self.explain: rule.comment = str(refpolicy.Comment(explain_access(av, verbosity=self.explain))) + # base_type[0] == 0 means there exists a base type but not the path + # base_type[0] == None means user isn't using base type + # base_type[1] contains the target context + # base_type[2] contains the source type + base_type = av.base_file_type() + if base_type[0] == 0 and av.type != audit2why.ALLOW: + rule.comment += "\n#!!!! WARNING: '%s' is a base type." % "".join(base_type[1]) + for perm in av.perms: + if perm == "write" or perm == "create": + permission = True + break + else: + permission = False + + # Catch perms 'write' and 'create' for base types + if (base_type[0] is not None and base_type[0] != 0 + and permission and av.type != audit2why.ALLOW): + if av.obj_class == dir: + comp = "(/.*?)" + else: + comp = "" + rule.comment += "\n#!!!! WARNING '%s' is not allowed to write or create to %s. Change the label to %s." % ("".join(base_type[2]), "".join(base_type[1]), "".join(base_type[0])) + if av.obj_path != "": + rule.comment += "\n#!!!! $ semanage fcontext -a -t %s %s%s \n#!!!! $ restorecon -R -v %s" % ("".join(base_type[0]), "".join(av.obj_path), "".join(comp) ,"".join(av.obj_path)) + + self.__restore_label(av) + if self.mislabled is not None and av.type != audit2why.ALLOW: + rule.comment += "\n#!!!! The file '%s' is mislabeled on your system. \n#!!!! Fix with $ restorecon -R -v %s" % ("".join(av.obj_path), "".join(av.obj_path)) if av.type == audit2why.ALLOW: rule.comment += "\n#!!!! This avc is allowed in the current policy" if av.type == audit2why.DONTAUDIT: diff --git sepolgen-1.2.3/src/sepolgen/refparser.py sepolgen-1.2.3/src/sepolgen/refparser.py index 9b1d0c8..2cef8e8 100644 --- sepolgen-1.2.3/src/sepolgen/refparser.py +++ sepolgen-1.2.3/src/sepolgen/refparser.py @@ -113,6 +113,7 @@ tokens = ( 'AUDITALLOW', 'NEVERALLOW', 'PERMISSIVE', + 'TYPEBOUNDS', 'TYPE_TRANSITION', 'TYPE_CHANGE', 'TYPE_MEMBER', @@ -178,6 +179,7 @@ reserved = { 'auditallow' : 'AUDITALLOW', 'neverallow' : 'NEVERALLOW', 'permissive' : 'PERMISSIVE', + 'typebounds' : 'TYPEBOUNDS', 'type_transition' : 'TYPE_TRANSITION', 'type_change' : 'TYPE_CHANGE', 'type_member' : 'TYPE_MEMBER', @@ -502,6 +504,7 @@ def p_policy_stmt(p): '''policy_stmt : gen_require | avrule_def | typerule_def + | typebound_def | typeattribute_def | roleattribute_def | interface_call @@ -823,6 +826,13 @@ def p_typerule_def(p): t.file_name = p[7] p[0] = t +def p_typebound_def(p): + '''typebound_def : TYPEBOUNDS IDENTIFIER comma_list SEMI''' + t = refpolicy.TypeBound() + t.type = p[2] + t.tgt_types.update(p[3]) + p[0] = t + def p_bool(p): '''bool : BOOL IDENTIFIER TRUE SEMI | BOOL IDENTIFIER FALSE SEMI''' diff --git sepolgen-1.2.3/src/sepolgen/refpolicy.py sepolgen-1.2.3/src/sepolgen/refpolicy.py index 31b40d8..352b187 100644 --- sepolgen-1.2.3/src/sepolgen/refpolicy.py +++ sepolgen-1.2.3/src/sepolgen/refpolicy.py @@ -112,6 +112,9 @@ class Node(PolicyBase): def typerules(self): return filter(lambda x: isinstance(x, TypeRule), walktree(self)) + def typebounds(self): + return filter(lambda x: isinstance(x, TypeBound), walktree(self)) + def typeattributes(self): """Iterate over all of the TypeAttribute children of this Interface.""" return filter(lambda x: isinstance(x, TypeAttribute), walktree(self)) @@ -281,6 +284,11 @@ class SecurityContext(Leaf): Raises ValueError if the string is not parsable as a security context. """ + # try to translate the context string to raw form + raw = selinux.selinux_trans_to_raw_context(context) + if raw[0] == 0: + context = raw[1] + fields = context.split(":") if len(fields) < 3: raise ValueError("context string [%s] not in a valid format" % context) @@ -522,6 +530,19 @@ class TypeRule(Leaf): self.tgt_types.to_space_str(), self.obj_classes.to_space_str(), self.dest_type) +class TypeBound(Leaf): + """SElinux typebound statement. + + This class represents a typebound statement. + """ + def __init__(self, parent=None): + Leaf.__init__(self, parent) + self.type = "" + self.tgt_types = IdSet() + + def to_string(self): + return "typebounds %s %s;" % (self.type, self.tgt_types.to_comma_str()) + class RoleAllow(Leaf): def __init__(self, parent=None): diff --git sepolgen-1.2.3/tests/.gitignore sepolgen-1.2.3/tests/.gitignore new file mode 100644 index 0000000..c120af8 --- /dev/null +++ sepolgen-1.2.3/tests/.gitignore @@ -0,0 +1,4 @@ +module_compile_test.fc +module_compile_test.if +output +tmp/ diff --git sepolgen-1.2.3/tests/Makefile sepolgen-1.2.3/tests/Makefile index 924a9be..e17eef2 100644 --- sepolgen-1.2.3/tests/Makefile +++ sepolgen-1.2.3/tests/Makefile @@ -4,8 +4,11 @@ clean: rm -f *~ *.pyc rm -f parser.out parsetab.py rm -f out.txt + rm -f module_compile_test.fc + rm -f module_compile_test.if rm -f module_compile_test.pp rm -f output + rm -rf __pycache__ tmp test: $(PYTHON) run-tests.py diff --git sepolgen-1.2.3/tests/module_compile_test.te sepolgen-1.2.3/tests/module_compile_test.te index 446c8dc..b365448 100644 --- sepolgen-1.2.3/tests/module_compile_test.te +++ sepolgen-1.2.3/tests/module_compile_test.te @@ -1,8 +1,8 @@ -module foo 1.0; +module module_compile_test 1.0; require { type foo, bar; class file { read write }; } -allow foo bar : file { read write }; \ No newline at end of file +allow foo bar : file { read write };