70
74
mounts = write_file(self.tmpdir, 'mounts', self.MOUNTS_WITH_SECURITYFS % self.tmpdir)
71
75
self.assertEqual('%s/security/apparmor' % self.tmpdir, check_for_apparmor(filesystems, mounts))
77
class AaTest_get_profile_flags(AaTestWithTempdir):
78
def _test_get_flags(self, profile_header, expected_flags):
79
file = write_file(self.tmpdir, 'profile', '%s {\n}\n' % profile_header)
80
flags = get_profile_flags(file, '/foo')
81
self.assertEqual(flags, expected_flags)
83
def test_get_flags_01(self):
84
self._test_get_flags('/foo', None)
85
def test_get_flags_02(self):
86
self._test_get_flags('/foo ( complain )', ' complain ')
87
def test_get_flags_04(self):
88
self._test_get_flags('/foo (complain)', 'complain')
89
def test_get_flags_05(self):
90
self._test_get_flags('/foo flags=(complain)', 'complain')
91
def test_get_flags_06(self):
92
self._test_get_flags('/foo flags=(complain, audit)', 'complain, audit')
94
def test_get_flags_invalid_01(self):
95
with self.assertRaises(AppArmorException):
96
self._test_get_flags('/foo ()', None)
97
def test_get_flags_invalid_02(self):
98
with self.assertRaises(AppArmorException):
99
self._test_get_flags('/foo flags=()', None)
100
def test_get_flags_invalid_03(self):
101
with self.assertRaises(AppArmorException):
102
self._test_get_flags('/foo ( )', ' ')
104
def test_get_flags_other_profile(self):
105
with self.assertRaises(AppArmorException):
106
self._test_get_flags('/no-such-profile flags=(complain)', 'complain')
108
class AaTest_set_profile_flags(AaTestWithTempdir):
109
def _test_set_flags(self, profile, old_flags, new_flags, whitespace='', comment='', more_rules='',
110
expected_flags='@-@-@', check_new_flags=True, profile_name='/foo'):
112
old_flags = ' %s' % old_flags
114
if expected_flags == '@-@-@':
115
expected_flags = new_flags
118
expected_flags = ' flags=(%s)' % (expected_flags)
123
comment = ' %s' % comment
125
dummy_profile_content = ' #include <abstractions/base>\n capability chown,\n /bar r,'
126
prof_template = '%s%s%s {%s\n%s\n%s\n}\n'
127
old_prof = prof_template % (whitespace, profile, old_flags, comment, more_rules, dummy_profile_content)
128
new_prof = prof_template % (whitespace, profile, expected_flags, comment, more_rules, dummy_profile_content)
130
self.file = write_file(self.tmpdir, 'profile', old_prof)
131
set_profile_flags(self.file, profile_name, new_flags)
133
real_new_prof = read_file(self.file)
134
self.assertEqual(new_prof, real_new_prof)
136
# tests that actually don't change the flags
137
def test_set_flags_nochange_01(self):
138
self._test_set_flags('/foo', '', '')
139
def test_set_flags_nochange_02(self):
140
self._test_set_flags('/foo', '( complain )', ' complain ', whitespace=' ')
141
def test_set_flags_nochange_03(self):
142
self._test_set_flags('/foo', '(complain)', 'complain')
143
def test_set_flags_nochange_04(self):
144
self._test_set_flags('/foo', 'flags=(complain)', 'complain')
145
def test_set_flags_nochange_05(self):
146
self._test_set_flags('/foo', 'flags=(complain, audit)', 'complain, audit', whitespace=' ')
147
def test_set_flags_nochange_06(self):
148
self._test_set_flags('/foo', 'flags=(complain, audit)', 'complain, audit', whitespace=' ', comment='# a comment')
149
def test_set_flags_nochange_07(self):
150
self._test_set_flags('/foo', 'flags=(complain, audit)', 'complain, audit', whitespace=' ', more_rules=' # a comment\n#another comment')
151
def test_set_flags_nochange_08(self):
152
self._test_set_flags('profile /foo', 'flags=(complain)', 'complain')
153
def test_set_flags_nochange_09(self):
154
self._test_set_flags('profile xy /foo', 'flags=(complain)', 'complain', profile_name='xy')
155
def test_set_flags_nochange_10(self):
156
self._test_set_flags('profile "/foo bar"', 'flags=(complain)', 'complain', profile_name='/foo bar')
157
def test_set_flags_nochange_11(self):
158
self._test_set_flags('/foo', '(complain)', 'complain', profile_name=None)
159
#def test_set_flags_nochange_12(self):
160
# XXX changes the flags for the child profile (which happens to have the same profile name) to 'complain'
161
# self._test_set_flags('/foo', 'flags=(complain)', 'complain', more_rules=' profile /foo {\n}')
163
# tests that change the flags
164
def test_set_flags_01(self):
165
self._test_set_flags('/foo', '', 'audit')
166
def test_set_flags_02(self):
167
self._test_set_flags('/foo', '( complain )', 'audit ', whitespace=' ')
168
def test_set_flags_04(self):
169
self._test_set_flags('/foo', '(complain)', 'audit')
170
def test_set_flags_05(self):
171
self._test_set_flags('/foo', 'flags=(complain)', 'audit')
172
def test_set_flags_06(self):
173
self._test_set_flags('/foo', 'flags=(complain, audit)', None, whitespace=' ')
174
def test_set_flags_07(self):
175
self._test_set_flags('/foo', 'flags=(complain, audit)', '', expected_flags=None)
176
def test_set_flags_08(self):
177
self._test_set_flags('/foo', '( complain )', 'audit ', whitespace=' ', profile_name=None)
178
def test_set_flags_09(self):
179
self._test_set_flags('profile /foo', 'flags=(complain)', 'audit')
180
def test_set_flags_10(self):
181
self._test_set_flags('profile xy /foo', 'flags=(complain)', 'audit', profile_name='xy')
182
def test_set_flags_11(self):
183
self._test_set_flags('profile "/foo bar"', 'flags=(complain)', 'audit', profile_name='/foo bar')
184
def test_set_flags_12(self):
185
self._test_set_flags('profile xy "/foo bar"', 'flags=(complain)', 'audit', profile_name='xy')
188
# XXX regex_hat_flag in set_profile_flags() is totally broken - it matches for ' ' and ' X ', but doesn't match for ' ^foo {'
189
# oh, it matches on a line like 'dbus' and changes it to 'dbus flags=(...)' if there's no leading whitespace (and no comma)
190
#def test_set_flags_hat_01(self):
191
# self._test_set_flags(' ^hat', '', 'audit')
194
def test_set_flags_invalid_01(self):
195
with self.assertRaises(AppArmorBug):
196
self._test_set_flags('/foo', '()', None, check_new_flags=False)
197
def test_set_flags_invalid_02(self):
198
with self.assertRaises(AppArmorBug):
199
self._test_set_flags('/foo', 'flags=()', None, check_new_flags=False)
200
def test_set_flags_invalid_03(self):
201
with self.assertRaises(AppArmorException):
202
self._test_set_flags('/foo', '( )', '', check_new_flags=False)
203
def test_set_flags_invalid_04(self):
204
with self.assertRaises(AppArmorBug):
205
self._test_set_flags('/foo', 'flags=(complain, audit)', ' ', check_new_flags=False) # whitespace-only newflags
207
def test_set_flags_other_profile(self):
208
# test behaviour if the file doesn't contain the specified /foo profile
209
orig_prof = '/no-such-profile flags=(complain) {\n}'
210
self.file = write_file(self.tmpdir, 'profile', orig_prof)
212
with self.assertRaises(AppArmorBug):
213
set_profile_flags(self.file, '/foo', 'audit')
215
# the file should not be changed
216
real_new_prof = read_file(self.file)
217
self.assertEqual(orig_prof, real_new_prof)
219
def test_set_flags_no_profile_found(self):
220
# test behaviour if the file doesn't contain any profile
221
orig_prof = '# /comment flags=(complain) {\n# }'
222
self.file = write_file(self.tmpdir, 'profile', orig_prof)
224
with self.assertRaises(AppArmorBug):
225
set_profile_flags(self.file, None, 'audit')
227
# the file should not be changed
228
real_new_prof = read_file(self.file)
229
self.assertEqual(orig_prof, real_new_prof)
231
def test_set_flags_file_not_found(self):
232
with self.assertRaises(IOError):
233
set_profile_flags('%s/file-not-found' % self.tmpdir, '/foo', 'audit')
236
class AaTest_is_skippable_file(AATest):
237
def test_not_skippable_01(self):
238
self.assertFalse(is_skippable_file('bin.ping'))
239
def test_not_skippable_02(self):
240
self.assertFalse(is_skippable_file('usr.lib.dovecot.anvil'))
241
def test_not_skippable_03(self):
242
self.assertFalse(is_skippable_file('bin.~ping'))
243
def test_not_skippable_04(self):
244
self.assertFalse(is_skippable_file('bin.rpmsave.ping'))
245
def test_not_skippable_05(self):
246
# normally is_skippable_file should be called without directory, but it shouldn't hurt too much
247
self.assertFalse(is_skippable_file('/etc/apparmor.d/bin.ping'))
248
def test_not_skippable_06(self):
249
self.assertFalse(is_skippable_file('bin.pingrej'))
251
def test_skippable_01(self):
252
self.assertTrue(is_skippable_file('bin.ping.dpkg-new'))
253
def test_skippable_02(self):
254
self.assertTrue(is_skippable_file('bin.ping.dpkg-old'))
255
def test_skippable_03(self):
256
self.assertTrue(is_skippable_file('bin.ping..dpkg-dist'))
257
def test_skippable_04(self):
258
self.assertTrue(is_skippable_file('bin.ping..dpkg-bak'))
259
def test_skippable_05(self):
260
self.assertTrue(is_skippable_file('bin.ping.rpmnew'))
261
def test_skippable_06(self):
262
self.assertTrue(is_skippable_file('bin.ping.rpmsave'))
263
def test_skippable_07(self):
264
self.assertTrue(is_skippable_file('bin.ping.orig'))
265
def test_skippable_08(self):
266
self.assertTrue(is_skippable_file('bin.ping.rej'))
267
def test_skippable_09(self):
268
self.assertTrue(is_skippable_file('bin.ping~'))
269
def test_skippable_10(self):
270
self.assertTrue(is_skippable_file('.bin.ping'))
271
def test_skippable_11(self):
272
self.assertTrue(is_skippable_file('')) # empty filename
273
def test_skippable_12(self):
274
self.assertTrue(is_skippable_file('/etc/apparmor.d/')) # directory without filename
275
def test_skippable_13(self):
276
self.assertTrue(is_skippable_file('README'))
279
class AaTest_is_skippable_dir(AATest):
284
('force-complain', True),
285
('/etc/apparmor.d/cache', True),
286
('/etc/apparmor.d/lxc/', True),
288
('dont_disable', False),
289
('/etc/apparmor.d/cache_foo', False),
290
('abstractions', False),
291
('apache2.d', False),
292
('/etc/apparmor.d/apache2.d', False),
294
('/etc/apparmor.d/local/', False),
296
('/etc/apparmor.d/tunables', False),
297
('/etc/apparmor.d/tunables/multiarch.d', False),
298
('/etc/apparmor.d/tunables/xdg-user-dirs.d', False),
299
('/etc/apparmor.d/tunables/home.d', False),
300
('/etc/apparmor.d/abstractions', False),
301
('/etc/apparmor.d/abstractions/ubuntu-browsers.d', False),
302
('/etc/apparmor.d/abstractions/apparmor_api', False),
305
def _run_test(self, params, expected):
306
self.assertEqual(is_skippable_dir(params), expected)
308
class AaTest_parse_profile_start(AATest):
309
def _parse(self, line, profile, hat):
310
return parse_profile_start(line, 'somefile', 1, profile, hat)
311
# (profile, hat, flags, in_contained_hat, pps_set_profile, pps_set_hat_external)
313
def test_parse_profile_start_01(self):
314
result = self._parse('/foo {', None, None)
315
expected = ('/foo', '/foo', None, None, False, False, False)
316
self.assertEqual(result, expected)
318
def test_parse_profile_start_02(self):
319
result = self._parse('/foo (complain) {', None, None)
320
expected = ('/foo', '/foo', None, 'complain', False, False, False)
321
self.assertEqual(result, expected)
323
def test_parse_profile_start_03(self):
324
result = self._parse('profile foo /foo {', None, None) # named profile
325
expected = ('foo', 'foo', '/foo', None, False, False, False)
326
self.assertEqual(result, expected)
328
def test_parse_profile_start_04(self):
329
result = self._parse('profile /foo {', '/bar', '/bar') # child profile
330
expected = ('/bar', '/foo', None, None, True, True, False)
331
self.assertEqual(result, expected)
333
def test_parse_profile_start_05(self):
334
result = self._parse('/foo//bar {', None, None) # external hat
335
expected = ('/foo', 'bar', None, None, False, False, True)
336
self.assertEqual(result, expected)
338
def test_parse_profile_start_06(self):
339
result = self._parse('profile "/foo" (complain) {', None, None)
340
expected = ('/foo', '/foo', None, 'complain', False, False, False)
341
self.assertEqual(result, expected)
344
def test_parse_profile_start_invalid_01(self):
345
with self.assertRaises(AppArmorException):
346
self._parse('/foo {', '/bar', '/bar') # child profile without profile keyword
348
def test_parse_profile_start_invalid_02(self):
349
with self.assertRaises(AppArmorBug):
350
self._parse('xy', '/bar', '/bar') # not a profile start
352
class AaTest_separate_vars(AATest):
356
(' foo bar' , {'foo', 'bar' }),
357
('foo " ' , {'foo' }), # XXX " is ignored
358
(' " foo ' , {' "', 'foo' }), # XXX really?
359
(' foo bar ' , {'foo', 'bar' }),
360
(' foo bar # comment' , {'foo', 'bar', 'comment' }), # XXX should comments be stripped?
362
('"foo" "bar baz"' , {'foo', 'bar baz' }),
363
('foo "bar baz" xy' , {'foo', 'bar baz', 'xy' }),
364
('foo "bar baz ' , {'foo', 'bar', 'baz' }), # half-quoted
365
(' " foo" bar' , {' foo', 'bar' }),
368
def _run_test(self, params, expected):
369
result = separate_vars(params)
370
self.assertEqual(result, expected)
373
class AaTest_store_list_var(AATest):
375
# old var value operation expected (False for exception)
376
([ {} , 'foo' , '=' ], {'foo'} ), # set
377
([ {} , 'foo bar' , '=' ], {'foo', 'bar'} ), # set multi
378
([ {'@{var}': {'foo'}} , 'bar' , '=' ], False ), # redefine var
379
([ {} , 'bar' , '+=' ], False ), # add to undefined var
380
([ {'@{var}': {'foo'}} , 'bar' , '+=' ], {'foo', 'bar'} ), # add
381
([ {'@{var}': {'foo'}} , 'bar baz' , '+=' ], {'foo', 'bar', 'baz'} ), # add multi
382
([ {'@{var}': {'foo', 'xy'}} , 'bar baz' , '+=' ], {'foo', 'xy', 'bar', 'baz'} ), # add multi to multi
383
([ {} , 'foo' , '-=' ], False ), # unknown operation
386
def _run_test(self, params, expected):
389
operation = params[2]
392
with self.assertRaises(AppArmorException):
393
store_list_var(var, '@{var}', value, operation, 'somefile')
396
# dumy value that must not be changed
397
var['@{foo}'] = {'one', 'two'}
400
'@{foo}': {'one', 'two'},
404
store_list_var(var, '@{var}', value, operation, 'somefile')
406
self.assertEqual(var.keys(), exp_var.keys())
409
self.assertEqual(var[key], exp_var[key])
412
class AaTest_write_header(AATest):
414
# name embedded_hat write_flags depth flags attachment prof.keyw. comment expected
415
(['/foo', False, True, 1, 'complain', None, None, None ], ' /foo flags=(complain) {'),
416
(['/foo', True, True, 1, 'complain', None, None, None ], ' profile /foo flags=(complain) {'),
417
(['/foo sp', False, False, 2, 'complain', None, None, None ], ' "/foo sp" {'),
418
(['/foo' ,False, False, 2, 'complain', None, None, None ], ' /foo {'),
419
(['/foo', True, False, 2, 'complain', None, None, None ], ' profile /foo {'),
420
(['/foo', False, True, 0, None, None, None, None ], '/foo {'),
421
(['/foo', True, True, 0, None, None, None, None ], 'profile /foo {'),
422
(['/foo', False, False, 0, None, None, None, None ], '/foo {'),
423
(['/foo', True, False, 0, None, None, None, None ], 'profile /foo {'),
424
(['bar', False, True, 1, 'complain', None, None, None ], ' profile bar flags=(complain) {'),
425
(['bar', False, True, 1, 'complain', '/foo', None, None ], ' profile bar /foo flags=(complain) {'),
426
(['bar', True, True, 1, 'complain', '/foo', None, None ], ' profile bar /foo flags=(complain) {'),
427
(['bar baz', False, True, 1, None, '/foo', None, None ], ' profile "bar baz" /foo {'),
428
(['bar', True, True, 1, None, '/foo', None, None ], ' profile bar /foo {'),
429
(['bar baz', False, True, 1, 'complain', '/foo sp', None, None ], ' profile "bar baz" "/foo sp" flags=(complain) {'),
430
(['^foo', False, True, 1, 'complain', None, None, None ], ' profile ^foo flags=(complain) {'),
431
(['^foo', True, True, 1, 'complain', None, None, None ], ' ^foo flags=(complain) {'),
432
(['^foo', True, True, 1.5, 'complain', None, None, None ], ' ^foo flags=(complain) {'),
433
(['^foo', True, True, 1.3, 'complain', None, None, None ], ' ^foo flags=(complain) {'),
434
(['/foo', False, True, 1, 'complain', None, 'profile', None ], ' profile /foo flags=(complain) {'),
435
(['/foo', True, True, 1, 'complain', None, 'profile', None ], ' profile /foo flags=(complain) {'),
436
(['/foo', False, True, 1, 'complain', None, None, '# x' ], ' /foo flags=(complain) { # x'),
437
(['/foo', True, True, 1, None, None, None, '# x' ], ' profile /foo { # x'),
438
(['/foo', False, True, 1, None, None, 'profile', '# x' ], ' profile /foo { # x'),
439
(['/foo', True, True, 1, 'complain', None, 'profile', '# x' ], ' profile /foo flags=(complain) { # x'),
442
def _run_test(self, params, expected):
444
embedded_hat = params[1]
445
write_flags = params[2]
447
prof_data = { 'flags': params[4], 'attachment': params[5], 'profile_keyword': params[6], 'header_comment': params[7] }
449
result = write_header(prof_data, depth, name, embedded_hat, write_flags)
450
self.assertEqual(result, [expected])
452
class AaTest_serialize_parse_profile_start(AATest):
453
def _parse(self, line, profile, hat, prof_data_profile, prof_data_external):
454
# 'correct' is always True in the code that uses serialize_parse_profile_start() (set some lines above the function call)
455
return serialize_parse_profile_start(line, 'somefile', 1, profile, hat, prof_data_profile, prof_data_external, True)
457
def test_serialize_parse_profile_start_01(self):
458
result = self._parse('/foo {', None, None, False, False)
459
expected = ('/foo', '/foo', None, None, False, True)
460
self.assertEqual(result, expected)
462
def test_serialize_parse_profile_start_02(self):
463
result = self._parse('/foo (complain) {', None, None, False, False)
464
expected = ('/foo', '/foo', None, 'complain', False, True)
465
self.assertEqual(result, expected)
467
def test_serialize_parse_profile_start_03(self):
468
result = self._parse('profile foo /foo {', None, None, False, False) # named profile
469
expected = ('foo', 'foo', '/foo', None, False, True)
470
self.assertEqual(result, expected)
472
def test_serialize_parse_profile_start_04(self):
473
result = self._parse('profile /foo {', '/bar', '/bar', False, False) # child profile
474
expected = ('/bar', '/foo', None, None, True, True)
475
self.assertEqual(result, expected)
477
def test_serialize_parse_profile_start_05(self):
478
result = self._parse('/foo//bar {', None, None, False, False) # external hat
479
expected = ('/foo', 'bar', None, None, False, False) # note correct == False here
480
self.assertEqual(result, expected)
482
def test_serialize_parse_profile_start_06(self):
483
result = self._parse('profile "/foo" (complain) {', None, None, False, False)
484
expected = ('/foo', '/foo', None, 'complain', False, True)
485
self.assertEqual(result, expected)
487
def test_serialize_parse_profile_start_07(self):
488
result = self._parse('/foo {', None, None, True, False)
489
expected = ('/foo', '/foo', None, None, False, True)
490
self.assertEqual(result, expected)
492
def test_serialize_parse_profile_start_08(self):
493
result = self._parse('/foo {', None, None, False, True)
494
expected = ('/foo', '/foo', None, None, False, True)
495
self.assertEqual(result, expected)
497
def test_serialize_parse_profile_start_09(self):
498
result = self._parse('/foo {', None, None, True, True)
499
expected = ('/foo', '/foo', None, None, False, True)
500
self.assertEqual(result, expected)
502
def test_serialize_parse_profile_start_10(self):
503
result = self._parse('profile /foo {', '/bar', '/bar', True, False) # child profile
504
expected = ('/bar', '/foo', None, None, True, True)
505
self.assertEqual(result, expected)
507
def test_serialize_parse_profile_start_11(self):
508
result = self._parse('profile /foo {', '/bar', '/bar', False, True) # child profile
509
expected = ('/bar', '/foo', None, None, True, True)
510
self.assertEqual(result, expected)
512
def test_serialize_parse_profile_start_12(self):
513
result = self._parse('profile /foo {', '/bar', '/bar', True, True) # child profile
514
expected = ('/bar', '/foo', None, None, True, True)
515
self.assertEqual(result, expected)
517
class AaTestInvalid_serialize_parse_profile_start(AATest):
519
# line profile hat p_d_profile p_d_external expected
520
(['/foo {', '/bar', '/bar', False, False ], AppArmorException), # child profile without 'profile' keyword
521
(['profile /foo {', '/bar', '/xy', False, False ], AppArmorException), # already inside a child profile - nesting level reached
522
(['/ext//hat {', '/bar', '/bar', True, True ], AppArmorException), # external hat inside a profile
523
(['/ext//hat {', '/bar', '/bar', True, False ], AppArmorException), # external hat inside a profile
524
(['xy', '/bar', '/bar', False, False ], AppArmorBug ), # not a profile start
527
def _run_test(self, params, expected):
531
prof_data_profile = params[3]
532
prof_data_external = params[4]
534
with self.assertRaises(expected):
535
# 'correct' is always True in the code that uses serialize_parse_profile_start() (set some lines above the function call)
536
serialize_parse_profile_start(line, 'somefile', 1, profile, hat, prof_data_profile, prof_data_external, True)
538
setup_all_loops(__name__)
74
539
if __name__ == '__main__':
75
540
unittest.main(verbosity=2)