summaryrefslogtreecommitdiff
path: root/lib/python2.7/site-packages/setools/permmap.py
blob: 54cd9f9a3290a12ade56dbe4389b0536af63d2e9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
# 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))