1#!/usr/bin/python
2import sys
3sys.path.append('../src')
4import unittest
5import SELinux_CTS
6from SELinux_CTS import SELinuxPolicy
7
8policy_file_name = 'policy_test.conf'
9types = set([
10        'bluetooth',
11        'healthd',
12        'healthd_exec',
13        'testTYPE' ])  #testTYPE added for neverallow rule to make sense
14attributes = {
15    'domain': set(['bluetooth', 'healthd', 'testTYPE']),
16    'unconfineddomain': set(['bluetooth']),
17    'appdomain': set(['bluetooth', 'testTYPE']),
18    'file_type': set(['healthd_exec']),
19    'exec_type': set(['healthd_exec']) }
20common_classes = {
21    'file': set([
22            'ioctl',
23            'read',
24            'write',
25            'create',
26            'getattr',
27            'setattr',
28            'lock',
29            'relabelfrom',
30            'relabelto',
31            'append',
32            'unlink',
33            'link',
34            'rename',
35            'execute',
36            'swapon',
37            'quotaon',
38            'mounton' ]) }
39classes = {
40    'capability': set([
41            'chown',
42            'dac_override',
43            'dac_read_search',
44            'fowner',
45            'fsetid',
46            'kill',
47            'setgid',
48            'setuid',
49            'setpcap',
50            'linux_immutable',
51            'net_bind_service',
52            'net_broadcast',
53            'net_admin',
54            'net_raw',
55            'ipc_lock',
56            'ipc_owner',
57            'sys_module',
58            'sys_rawio',
59            'sys_chroot',
60            'sys_ptrace',
61            'sys_pacct',
62            'sys_admin',
63            'sys_boot',
64            'sys_nice',
65            'sys_resource',
66            'sys_time',
67            'sys_tty_config',
68            'mknod',
69            'lease',
70            'audit_write',
71            'audit_control',
72            'setfcap' ]),
73    'file': (set([
74                'execute_no_trans',
75                'entrypoint',
76                'execmod',
77                'open',
78                'audit_access' ]) | common_classes['file']) }
79
80# allow healthd healthd_exec:file { entrypoint read execute };
81allow_rules = [
82    { 'source_types': {
83        'set': set([
84                'healthd']),
85        'flags': { 'complement': False } },
86      'target_types': {
87        'set': set([
88                'healthd_exec']),
89        'flags': { 'complement': False } },
90      'classes': {
91        'set': set([
92                'file']),
93        'flags': { 'complement': False } },
94      'permissions': {
95        'set': set([
96                'entrypoint',
97                'read',
98                'execute' ]),
99        'flags': { 'complement': False } } } ]
100
101# neverallow { appdomain -unconfineddomain -bluetooth } self:capability *;
102neverallow_rules = [
103    { 'source_types': {
104        'set': set([
105                'appdomain',
106                '-unconfineddomain',
107                '-bluetooth' ]),
108        'flags': { 'complement': False } },
109      'target_types': {
110        'set': set([
111                'self']),
112        'flags': { 'complement': False } },
113      'classes': {
114        'set': set([
115                'capability']),
116        'flags': { 'complement': False } },
117      'permissions': {
118        'set': set([
119                '*' ]),
120        'flags': { 'complement': False } } } ]
121
122expected_final_allow_list = [
123        [ ('healthd', 'healthd_exec', 'file', 'entrypoint'),
124                ('healthd', 'healthd_exec', 'file', 'read'),
125                ('healthd', 'healthd_exec', 'file', 'execute') ] ]
126
127expected_final_neverallow_list = [
128        [ ('testTYPE', 'testTYPE', 'capability', 'chown'),
129                ('testTYPE', 'testTYPE', 'capability', 'dac_override'),
130                ('testTYPE', 'testTYPE', 'capability', 'dac_read_search'),
131                ('testTYPE', 'testTYPE', 'capability', 'fowner'),
132                ('testTYPE', 'testTYPE', 'capability', 'fsetid'),
133                ('testTYPE', 'testTYPE', 'capability', 'kill'),
134                ('testTYPE', 'testTYPE', 'capability', 'setgid'),
135                ('testTYPE', 'testTYPE', 'capability', 'setuid'),
136                ('testTYPE', 'testTYPE', 'capability', 'setpcap'),
137                ('testTYPE', 'testTYPE', 'capability', 'linux_immutable'),
138                ('testTYPE', 'testTYPE', 'capability', 'net_bind_service'),
139                ('testTYPE', 'testTYPE', 'capability', 'net_broadcast'),
140                ('testTYPE', 'testTYPE', 'capability', 'net_admin'),
141                ('testTYPE', 'testTYPE', 'capability', 'net_raw'),
142                ('testTYPE', 'testTYPE', 'capability', 'ipc_lock'),
143                ('testTYPE', 'testTYPE', 'capability', 'ipc_owner'),
144                ('testTYPE', 'testTYPE', 'capability', 'sys_module'),
145                ('testTYPE', 'testTYPE', 'capability', 'sys_rawio'),
146                ('testTYPE', 'testTYPE', 'capability', 'sys_chroot'),
147                ('testTYPE', 'testTYPE', 'capability', 'sys_ptrace'),
148                ('testTYPE', 'testTYPE', 'capability', 'sys_pacct'),
149                ('testTYPE', 'testTYPE', 'capability', 'sys_admin'),
150                ('testTYPE', 'testTYPE', 'capability', 'sys_boot'),
151                ('testTYPE', 'testTYPE', 'capability', 'sys_nice'),
152                ('testTYPE', 'testTYPE', 'capability', 'sys_resource'),
153                ('testTYPE', 'testTYPE', 'capability', 'sys_time'),
154                ('testTYPE', 'testTYPE', 'capability', 'sys_tty_config'),
155                ('testTYPE', 'testTYPE', 'capability', 'mknod'),
156                ('testTYPE', 'testTYPE', 'capability', 'lease'),
157                ('testTYPE', 'testTYPE', 'capability', 'audit_write'),
158                ('testTYPE', 'testTYPE', 'capability', 'audit_control'),
159                ('testTYPE', 'testTYPE', 'capability', 'setfcap') ] ]
160
161
162class SELinuxPolicyTests(unittest.TestCase):
163
164
165    def setUp(self):
166        self.test_policy = SELinuxPolicy()
167        self.test_file = open(policy_file_name, 'r')
168        self.test_policy.types = types
169        self.test_policy.attributes = attributes
170        self.test_policy.common_classes = common_classes
171        self.test_policy.classes = classes
172        self.test_policy.allow_rules = allow_rules
173        self.test_policy.neverallow_rules = neverallow_rules
174        return
175
176    def testExpandAvcRule(self):
177        #TODO: add more examples here to cover different cases
178        expanded_allow_list = SELinux_CTS.expand_avc_rule(self.test_policy, self.test_policy.allow_rules[0])
179        for a in expected_final_allow_list[0]:
180            self.failUnless(a in expanded_allow_list)
181        expanded_neverallow_list = SELinux_CTS.expand_avc_rule(self.test_policy, self.test_policy.neverallow_rules[0])
182        for n in expected_final_neverallow_list[0]:
183            self.failUnless(n in expanded_neverallow_list)
184
185    def testExpandBrackets(self):
186        #test position without bracket:
187        self.test_file.seek(279)
188        self.failIf(SELinux_CTS.expand_brackets(self.test_file))
189
190        #test position with bracket:
191        self.test_file.seek(26123)
192        self.failUnless(SELinux_CTS.expand_brackets(self.test_file) == " entrypoint read execute ")
193
194        #test position with nested brackets:
195        self.test_file.seek(26873)
196        self.failUnless(SELinux_CTS.expand_brackets(self.test_file)
197               == " dir   chr_file blk_file   file lnk_file sock_file fifo_file   ")
198
199    def testGetAvcRuleComponent(self):
200        #test against normal ('allow healthd healthd_exec:file ...)
201        self.test_file.seek(26096)
202        normal_src = { 'flags': { 'complement': False },
203                'set': set(['healthd']) }
204        normal_tgt = { 'flags': { 'complement': False },
205                'set': set(['healthd_exec']) }
206        normal_class = { 'flags': { 'complement': False },
207                'set': set(['file']) }
208        normal_perm = { 'flags': { 'complement': False },
209                'set': set(['entrypoint', 'read', 'execute']) }
210        self.failUnless(SELinux_CTS.get_avc_rule_component(self.test_file)
211            == normal_src)
212        self.failUnless(SELinux_CTS.get_avc_rule_component(self.test_file)
213            == normal_tgt)
214        c = SELinux_CTS.advance_past_whitespace(self.test_file)
215        if c == ':':
216            self.test_file.read(1)
217        self.failUnless(SELinux_CTS.get_avc_rule_component(self.test_file)
218            == normal_class)
219        self.failUnless(SELinux_CTS.get_avc_rule_component(self.test_file)
220            == normal_perm)
221
222        #test against 'hard' ('init {fs_type  ...' )
223        self.test_file.seek(26838)
224        hard_src = { 'flags': { 'complement': False },
225                'set': set(['init']) }
226        hard_tgt = { 'flags': { 'complement': False },
227                'set': set(['fs_type', 'dev_type', 'file_type']) }
228        hard_class = { 'flags': { 'complement': False },
229                'set': set(['dir', 'chr_file', 'blk_file', 'file', 'lnk_file', 'sock_file', 'fifo_file']) }
230        hard_perm = { 'flags': { 'complement': False },
231                'set': set(['relabelto']) }
232        self.failUnless(SELinux_CTS.get_avc_rule_component(self.test_file)
233            == hard_src)
234        self.failUnless(SELinux_CTS.get_avc_rule_component(self.test_file)
235            == hard_tgt)
236        #mimic ':' check:
237        c = SELinux_CTS.advance_past_whitespace(self.test_file)
238        if c == ':':
239            self.test_file.read(1)
240        self.failUnless(SELinux_CTS.get_avc_rule_component(self.test_file)
241            == hard_class)
242        self.failUnless(SELinux_CTS.get_avc_rule_component(self.test_file)
243            == hard_perm)
244
245        #test against 'multi-line' ('init {fs_type  ...' )
246        self.test_file.seek(26967)
247        multi_src = { 'flags': { 'complement': False },
248                'set': set(['appdomain', '-unconfineddomain']) }
249        multi_tgt = { 'flags': { 'complement': False },
250                'set': set(['audio_device', 'camera_device', 'dm_device', 'radio_device', 'gps_device', 'rpmsg_device']) }
251        multi_class = { 'flags': { 'complement': False },
252                'set': set(['chr_file']) }
253        multi_perm = { 'flags': { 'complement': False },
254                'set': set(['read', 'write']) }
255        self.failUnless(SELinux_CTS.get_avc_rule_component(self.test_file)
256            == multi_src)
257        self.failUnless(SELinux_CTS.get_avc_rule_component(self.test_file)
258            == multi_tgt)
259        c = SELinux_CTS.advance_past_whitespace(self.test_file)
260        if c == ':':
261            self.test_file.read(1)
262        self.failUnless(SELinux_CTS.get_avc_rule_component(self.test_file)
263            == multi_class)
264        self.failUnless(SELinux_CTS.get_avc_rule_component(self.test_file)
265            == multi_perm)
266
267        #test against 'complement'
268        self.test_file.seek(26806)
269        complement = { 'flags': { 'complement': True },
270                'set': set(['entrypoint', 'relabelto']) }
271        self.failUnless(SELinux_CTS.get_avc_rule_component(self.test_file)
272            == complement)
273
274    def testGetLineType(self):
275        self.failUnless(SELinux_CTS.get_line_type('type bluetooth, domain;')
276                == SELinux_CTS.TYPE)
277        self.failUnless(SELinux_CTS.get_line_type('attribute unconfineddomain;')
278                == SELinux_CTS.ATTRIBUTE)
279        self.failUnless(SELinux_CTS.get_line_type('typeattribute bluetooth appdomain;')
280                == SELinux_CTS.TYPEATTRIBUTE)
281        self.failUnless(SELinux_CTS.get_line_type('class file')
282                == SELinux_CTS.CLASS)
283        self.failUnless(SELinux_CTS.get_line_type('common file')
284                == SELinux_CTS.COMMON)
285        self.failUnless(SELinux_CTS.get_line_type('allow healthd healthd_exec:file { entrypoint read execute };')
286                == SELinux_CTS.ALLOW_RULE)
287        self.failUnless(SELinux_CTS.get_line_type('neverallow { appdomain -unconfineddomain -bluetooth } self:capability *;')
288                == SELinux_CTS.NEVERALLOW_RULE)
289        self.failUnless(SELinux_CTS.get_line_type('# FLASK')
290                == SELinux_CTS.OTHER)
291
292    def testIsMultiLine(self):
293        self.failIf(SELinux_CTS.is_multi_line(SELinux_CTS.TYPE))
294        self.failIf(SELinux_CTS.is_multi_line(SELinux_CTS.ATTRIBUTE))
295        self.failIf(SELinux_CTS.is_multi_line(SELinux_CTS.TYPEATTRIBUTE))
296        self.failUnless(SELinux_CTS.is_multi_line(SELinux_CTS.CLASS))
297        self.failUnless(SELinux_CTS.is_multi_line(SELinux_CTS.COMMON))
298        self.failUnless(SELinux_CTS.is_multi_line(SELinux_CTS.ALLOW_RULE))
299        self.failUnless(SELinux_CTS.is_multi_line(SELinux_CTS.NEVERALLOW_RULE))
300        self.failIf(SELinux_CTS.is_multi_line(SELinux_CTS.OTHER))
301
302    def testProcessInheritsSegment(self):
303        inherit_offset = 448 # needs changing if file changes
304        self.test_file.seek(inherit_offset, 0)
305        inherit_result = SELinux_CTS.process_inherits_segment(self.test_file)
306        self.failUnless(inherit_result == 'file')
307        return
308
309    def testFromFileName(self):
310        #using a special file, since the test_file has some lines which don't 'jive'
311        clean_policy_file = 'policy_clean_test.conf'
312        from_file_policy = SELinuxPolicy()
313        from_file_policy.from_file_name(clean_policy_file)
314        self.failUnless(from_file_policy.types == self.test_policy.types)
315        self.failUnless(from_file_policy.attributes == self.test_policy.attributes)
316        self.failUnless(from_file_policy.classes == self.test_policy.classes)
317        self.failUnless(from_file_policy.common_classes == self.test_policy.common_classes)
318        self.failUnless(from_file_policy.allow_rules == self.test_policy.allow_rules)
319        self.failUnless(from_file_policy.neverallow_rules == self.test_policy.neverallow_rules)
320
321    def testExpandPermissions(self):
322        #test general case
323        test_class_obj = 'file'
324        general_set = set(['read', 'write', 'execute'])
325        expanded_general_set = general_set
326        self.failUnless(self.test_policy.expand_permissions(test_class_obj, general_set)
327                == general_set)
328        star_set = set(['*'])
329        expanded_star_set = self.test_policy.classes['file'] #everything in the class
330        self.failUnless(self.test_policy.expand_permissions(test_class_obj, star_set)
331                == expanded_star_set)
332        complement_set = set(['*', '-open'])
333        expanded_complement_set = self.test_policy.classes['file'] - set(['open'])
334        self.failUnless(self.test_policy.expand_permissions(test_class_obj, complement_set)
335                == expanded_complement_set)
336
337    def testExpandTypes(self):
338
339        #test general case and '-' handling
340        test_source_set = set([
341                'domain',
342                '-bluetooth' ])
343        expanded_test_source_set = set([
344                'healthd', 'testTYPE' ])
345        self.failUnless(self.test_policy.expand_types(test_source_set) == expanded_test_source_set)
346
347        #test '*' handling
348        test_source_set = set([ '*' ])
349        expanded_test_source_set = set([
350                'bluetooth', 'healthd', 'testTYPE' ])
351        self.failUnless(self.test_policy.expand_types(test_source_set) == types)
352        #test - handling
353        test_source_set = set([
354                '*',
355                '-bluetooth'])
356        expanded_test_source_set = set([
357                'healthd', 'healthd_exec', 'testTYPE' ])
358        self.failUnless(self.test_policy.expand_types(test_source_set) == expanded_test_source_set)
359
360    def testProcessAttributeLine(self):
361        attribute_policy = SELinuxPolicy()
362        #test with 'normal input'
363        test_normal_string = 'attribute TEST_att;'
364        test_attribute = 'TEST_att'
365        attribute_policy.process_attribute_line(test_normal_string)
366        self.failUnless( test_attribute in attribute_policy.attributes)
367        #TODO: test on bogus inputs
368
369    def testProcessClassLine(self):
370        class_policy = SELinuxPolicy()
371        #offsets need changing if test file changes
372        common_offset  = 279
373        class_initial_offset  = 212
374        class_perm_offset = 437
375        self.test_file.seek(common_offset, 0)
376        line = self.test_file.readline()
377        class_policy.process_common_line(line, self.test_file)
378        self.test_file.seek(class_initial_offset, 0)
379        line = self.test_file.readline()
380        class_policy.process_class_line(line, self.test_file)
381        self.failUnless('file' in class_policy.classes)
382        self.test_file.seek(class_perm_offset, 0)
383        line = self.test_file.readline()
384        class_policy.process_class_line(line, self.test_file)
385        self.failUnless(class_policy.classes['file'] == classes['file'])
386
387    def testProcessCommonLine(self):
388        common_policy = SELinuxPolicy()
389        common_offset  = 279 # needs changing if file changes
390        self.test_file.seek(common_offset, 0)
391        line = self.test_file.readline()
392        common_policy.process_common_line(line, self.test_file)
393        self.failUnless('file' in common_policy.common_classes )
394        self.failUnless(common_policy.common_classes['file'] == common_classes['file'])
395
396    def testProcessAvcRuleLine(self):
397        avc_policy = SELinuxPolicy()
398        allow_offset  =  26091 # needs changing if file changes
399        neverallow_offset  = 26311  # needs changing if file changes
400        self.test_file.seek(allow_offset, 0)
401        line = self.test_file.readline()
402        avc_policy.process_avc_rule_line(line, self.test_file)
403        self.failUnless(avc_policy.allow_rules[0] == allow_rules[0] ) # always '0'?
404        self.test_file.seek(neverallow_offset, 0)
405        line = self.test_file.readline()
406        avc_policy.process_avc_rule_line(line, self.test_file)
407        self.failUnless(avc_policy.neverallow_rules[0] == neverallow_rules[0] ) # always '0'?
408
409    def testProcessTypeLine(self):
410        type_policy = SELinuxPolicy()
411        test_normal_string = 'type TEST_type, TEST_att1, TEST_att2;'
412        test_type = 'TEST_type'
413        test_atts = ['TEST_att1', 'TEST_att2']
414        #test with 'normal input'
415        type_policy.process_type_line(test_normal_string)
416        self.failUnless(test_type in type_policy.types)
417        for a in test_atts:
418            self.failUnless(a in type_policy.attributes)
419            self.failUnless(test_type in type_policy.attributes[a])
420        #TODO: test with domain only, no attributes
421        # and test on bogus inputs
422
423    def testProcessTypeattributeLine(self):
424        typ_att_policy = SELinuxPolicy()
425        test_normal_string = 'typeattribute TEST_type TEST_att1, TEST_att2;'
426        test_type = 'TEST_type'
427        test_atts = ['TEST_att1', 'TEST_att2']
428        #test with 'normal input' (type should already be declared)
429        typ_att_policy.process_type_line('type ' + test_type + ';')
430        typ_att_policy.process_typeattribute_line(test_normal_string)
431        self.failUnless(test_type in typ_att_policy.types)
432        for a in test_atts:
433            self.failUnless(a in typ_att_policy.attributes)
434            self.failUnless(test_type in typ_att_policy.attributes[a])
435        #TODO: test with domain only, no attributes
436        # and test on bogus inputs
437
438def main():
439    unittest.main()
440
441if __name__ == '__main__':
442    main()
443