# Copyright 2014-2015, Tresys Technology, LLC
#
# This file is part of SETools.
#
# SETools is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation, either version 2.1 of
# the License, or (at your option) any later version.
#
# SETools is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with SETools. If not, see
# <http://www.gnu.org/licenses/>.
#
import sys
import logging
from errno import ENOENT
from . import exception
from . import policyrep
class PermissionMap(object):
"""Permission Map for information flow analysis."""
valid_infoflow_directions = ["r", "w", "b", "n", "u"]
min_weight = 1
max_weight = 10
def __init__(self, permmapfile=None):
"""
Parameter:
permmapfile The path to the permission map to load.
"""
self.log = logging.getLogger(self.__class__.__name__)
if permmapfile:
self.load(permmapfile)
else:
for path in ["data/", sys.prefix + "/share/setools/"]:
try:
self.load(path + "perm_map")
break
except (IOError, OSError) as err:
if err.errno != ENOENT:
raise
else:
raise RuntimeError("Unable to load default permission map.")
def load(self, permmapfile):
"""
Parameter:
permmapfile The path to the permission map to load.
"""
self.log.info("Opening permission map \"{0}\"".format(permmapfile))
# state machine
# 1 = read number of classes
# 2 = read class name and number of perms
# 3 = read perms
with open(permmapfile, "r") as mapfile:
class_count = 0
num_classes = 0
state = 1
self.permmap = dict()
for line_num, line in enumerate(mapfile, start=1):
entry = line.split()
if len(entry) == 0 or entry[0][0] == '#':
continue
if state == 1:
try:
num_classes = int(entry[0])
except ValueError:
raise exception.PermissionMapParseError(
"{0}:{1}:Invalid number of classes: {2}".
format(permmapfile, line_num, entry[0]))
if num_classes < 1:
raise exception.PermissionMapParseError(
"{0}:{1}:Number of classes must be positive: {2}".
format(permmapfile, line_num, entry[0]))
state = 2
elif state == 2:
if len(entry) != 3 or entry[0] != "class":
raise exception.PermissionMapParseError(
"{0}:{1}:Invalid class declaration: {2}".
format(permmapfile, line_num, entry))
class_name = str(entry[1])
try:
num_perms = int(entry[2])
except ValueError:
raise exception.PermissionMapParseError(
"{0}:{1}:Invalid number of permissions: {2}".
format(permmapfile, line_num, entry[2]))
if num_perms < 1:
raise exception.PermissionMapParseError(
"{0}:{1}:Number of permissions must be positive: {2}".
format(permmapfile, line_num, entry[2]))
class_count += 1
if class_count > num_classes:
raise exception.PermissionMapParseError(
"{0}:{1}:Extra class found: {2}".
format(permmapfile, line_num, class_name))
self.permmap[class_name] = dict()
perm_count = 0
state = 3
elif state == 3:
perm_name = str(entry[0])
flow_direction = str(entry[1])
if flow_direction not in self.valid_infoflow_directions:
raise exception.PermissionMapParseError(
"{0}:{1}:Invalid information flow direction: {2}".
format(permmapfile, line_num, entry[1]))
try:
weight = int(entry[2])
except ValueError:
raise exception.PermissionMapParseError(
"{0}:{1}:Invalid permission weight: {2}".
format(permmapfile, line_num, entry[2]))
if not self.min_weight <= weight <= self.max_weight:
raise exception.PermissionMapParseError(
"{0}:{1}:Permission weight must be {3}-{4}: {2}".
format(permmapfile, line_num, entry[2],
self.min_weight, self.max_weight))
self.permmap[class_name][perm_name] = {'direction': flow_direction,
'weight': weight,
'enabled': True}
perm_count += 1
if perm_count >= num_perms:
state = 2
def exclude_class(self, class_):
"""
Exclude all permissions in an object class for calculating rule weights.
Parameter:
class_ The object class to exclude.
Exceptions:
UnmappedClass The specified object class is not mapped.
"""
classname = str(class_)
try:
for perm in self.permmap[classname]:
self.permmap[classname][perm]['enabled'] = False
except KeyError:
raise exception.UnmappedClass("{0} is not mapped.".format(classname))
def exclude_permission(self, class_, permission):
"""
Exclude a permission for calculating rule weights.
Parameter:
class_ The object class of the permission.
permission The permission name to exclude.
Exceptions:
UnmappedClass The specified object class is not mapped.
UnmappedPermission The specified permission is not mapped for the object class.
"""
classname = str(class_)
if classname not in self.permmap:
raise exception.UnmappedClass("{0} is not mapped.".format(classname))
try:
self.permmap[classname][permission]['enabled'] = False
except KeyError:
raise exception.UnmappedPermission("{0}:{1} is not mapped.".
format(classname, permission))
def include_class(self, class_):
"""
Include all permissions in an object class for calculating rule weights.
Parameter:
class_ The object class to include.
Exceptions:
UnmappedClass The specified object class is not mapped.
"""
classname = str(class_)
try:
for perm in self.permmap[classname]:
self.permmap[classname][perm]['enabled'] = True
except KeyError:
raise exception.UnmappedClass("{0} is not mapped.".format(classname))
def include_permission(self, class_, permission):
"""
Include a permission for calculating rule weights.
Parameter:
class_ The object class of the permission.
permission The permission name to include.
Exceptions:
UnmappedClass The specified object class is not mapped.
UnmappedPermission The specified permission is not mapped for the object class.
"""
classname = str(class_)
if classname not in self.permmap:
raise exception.UnmappedClass("{0} is not mapped.".format(classname))
try:
self.permmap[classname][permission]['enabled'] = True
except KeyError:
raise exception.UnmappedPermission("{0}:{1} is not mapped.".
format(classname, permission))
def map_policy(self, policy):
"""Create mappings for all classes and permissions in the specified policy."""
for class_ in policy.classes():
class_name = str(class_)
if class_name not in self.permmap:
self.log.info("Adding unmapped class {0} from {1}".format(class_name, policy))
self.permmap[class_name] = dict()
perms = class_.perms
try:
perms |= class_.common.perms
except policyrep.exception.NoCommon:
pass
for perm_name in perms:
if perm_name not in self.permmap[class_name]:
self.log.info("Adding unmapped permission {0} in {1} from {2}".
format(perm_name, class_name, policy))
self.permmap[class_name][perm_name] = {'direction': 'u',
'weight': 1,
'enabled': True}
def rule_weight(self, rule):
"""
Get the type enforcement rule's information flow read and write weights.
Parameter:
rule A type enforcement rule.
Return: Tuple(read_weight, write_weight)
read_weight The type enforcement rule's read weight.
write_weight The type enforcement rule's write weight.
"""
write_weight = 0
read_weight = 0
class_name = str(rule.tclass)
if rule.ruletype != 'allow':
raise exception.RuleTypeError("{0} rules cannot be used for calculating a weight".
format(rule.ruletype))
if class_name not in self.permmap:
raise exception.UnmappedClass("{0} is not mapped.".format(class_name))
# iterate over the permissions and determine the
# weight of the rule in each direction. The result
# is the largest-weight permission in each direction
for perm_name in rule.perms:
try:
mapping = self.permmap[class_name][perm_name]
except KeyError:
raise exception.UnmappedPermission("{0}:{1} is not mapped.".
format(class_name, perm_name))
if not mapping['enabled']:
continue
if mapping['direction'] == "r":
read_weight = max(read_weight, mapping['weight'])
elif mapping['direction'] == "w":
write_weight = max(write_weight, mapping['weight'])
elif mapping['direction'] == "b":
read_weight = max(read_weight, mapping['weight'])
write_weight = max(write_weight, mapping['weight'])
return (read_weight, write_weight)
def set_direction(self, class_, permission, direction):
"""
Set the information flow direction of a permission.
Parameter:
class_ The object class of the permission.
permission The permission name.
direction The information flow direction the permission (r/w/b/n).
Exceptions:
UnmappedClass The specified object class is not mapped.
UnmappedPermission The specified permission is not mapped for the object class.
"""
if direction not in self.valid_infoflow_directions:
raise ValueError("Invalid information flow direction: {0}".format(direction))
classname = str(class_)
if classname not in self.permmap:
raise exception.UnmappedClass("{0} is not mapped.".format(classname))
try:
self.permmap[classname][permission]['direction'] = direction
except KeyError:
raise exception.UnmappedPermission("{0}:{1} is not mapped.".
format(classname, permission))
def set_weight(self, class_, permission, weight):
"""
Set the weight of a permission.
Parameter:
class_ The object class of the permission.
permission The permission name.
weight The weight of the permission (1-10).
Exceptions:
UnmappedClass The specified object class is not mapped.
UnmappedPermission The specified permission is not mapped for the object class.
"""
if not self.min_weight <= weight <= self.max_weight:
raise ValueError("Permission weights must be 1-10: {0}".format(weight))
classname = str(class_)
if classname not in self.permmap:
raise exception.UnmappedClass("{0} is not mapped.".format(classname))
try:
self.permmap[classname][permission]['weight'] = weight
except KeyError:
raise exception.UnmappedPermission("{0}:{1} is not mapped.".
format(classname, permission))