blob: 35e87ef32d33ee7bb43eae245b70e930e16623a7 [file] [log] [blame]
Brad Bishopc342db32019-05-15 21:57:59 -04001import unittest
2import re
3import os
4import string
5from oeqa.runtime.case import OERuntimeTestCase
6from oeqa.core.decorator.depends import OETestDepends
7from oeqa.runtime.decorator.package import OEHasPackage
8from oeqa.core.decorator.data import skipIfNotFeature
9
10MAX_LABEL_LEN = 255
11LABEL = "a" * MAX_LABEL_LEN
12
13class SmackBasicTest(OERuntimeTestCase):
14 ''' base smack test '''
15
16 @classmethod
17 def setUpClass(cls):
18 cls.smack_path = ""
19 cls.current_label = ""
20 cls.uid = 1000
21
22 @skipIfNotFeature('smack',
23 'Test requires smack to be in DISTRO_FEATURES')
24 @OEHasPackage(['smack-test'])
25 @OETestDepends(['ssh.SSHTest.test_ssh'])
26 def test_smack_basic(self):
27 status, output = self.target.run("grep smack /proc/mounts | awk '{print $2}'")
28 self.smack_path = output
29 status,output = self.target.run("cat /proc/self/attr/current")
30 self.current_label = output.strip()
31
32class SmackAccessLabel(SmackBasicTest):
33
34 @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
35 def test_add_access_label(self):
36 ''' Test if chsmack can correctly set a SMACK label '''
37 filename = "/tmp/test_access_label"
38 self.target.run("touch %s" %filename)
39 status, output = self.target.run("chsmack -a %s %s" %(LABEL, filename))
40 self.assertEqual(
41 status, 0,
42 "Cannot set smack access label. "
43 "Status and output: %d %s" %(status, output))
44 status, output = self.target.run("chsmack %s" %filename)
45 self.target.run("rm %s" %filename)
46 m = re.search('(?<=access=")\S+(?=")', output)
47 if m is None:
48 self.fail("Did not find access attribute")
49 else:
50 label_retrieved = m .group(0)
51 self.assertEqual(
52 LABEL, label_retrieved,
53 "label not set correctly. expected and gotten: "
54 "%s %s" %(LABEL,label_retrieved))
55
56
57class SmackExecLabel(SmackBasicTest):
58
59 @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
60 def test_add_exec_label(self):
61 '''Test if chsmack can correctly set a SMACK Exec label'''
62 filename = "/tmp/test_exec_label"
63 self.target.run("touch %s" %filename)
64 status, output = self.target.run("chsmack -e %s %s" %(LABEL, filename))
65 self.assertEqual(
66 status, 0,
67 "Cannot set smack exec label. "
68 "Status and output: %d %s" %(status, output))
69 status, output = self.target.run("chsmack %s" %filename)
70 self.target.run("rm %s" %filename)
71 m= re.search('(?<=execute=")\S+(?=")', output)
72 if m is None:
73 self.fail("Did not find execute attribute")
74 else:
75 label_retrieved = m.group(0)
76 self.assertEqual(
77 LABEL, label_retrieved,
78 "label not set correctly. expected and gotten: " +
79 "%s %s" %(LABEL,label_retrieved))
80
81
82class SmackMmapLabel(SmackBasicTest):
83
84 @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
85 def test_add_mmap_label(self):
86 '''Test if chsmack can correctly set a SMACK mmap label'''
87 filename = "/tmp/test_exec_label"
88 self.target.run("touch %s" %filename)
89 status, output = self.target.run("chsmack -m %s %s" %(LABEL, filename))
90 self.assertEqual(
91 status, 0,
92 "Cannot set smack mmap label. "
93 "Status and output: %d %s" %(status, output))
94 status, output = self.target.run("chsmack %s" %filename)
95 self.target.run("rm %s" %filename)
96 m = re.search('(?<=mmap=")\S+(?=")', output)
97 if m is None:
98 self.fail("Did not find mmap attribute")
99 else:
100 label_retrieved = m.group(0)
101 self.assertEqual(
102 LABEL, label_retrieved,
103 "label not set correctly. expected and gotten: " +
104 "%s %s" %(LABEL,label_retrieved))
105
106
107class SmackTransmutable(SmackBasicTest):
108
109 @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
110 def test_add_transmutable(self):
111 '''Test if chsmack can correctly set a SMACK transmutable mode'''
112
113 directory = "~/test_transmutable"
114 self.target.run("mkdir -p %s" %directory)
115 status, output = self.target.run("chsmack -t %s" %directory)
116 self.assertEqual(status, 0, "Cannot set smack transmutable. "
117 "Status and output: %d %s" %(status, output))
118 status, output = self.target.run("chsmack %s" %directory)
119 self.target.run("rmdir %s" %directory)
120 m = re.search('(?<=transmute=")\S+(?=")', output)
121 if m is None:
122 self.fail("Did not find transmute attribute")
123 else:
124 label_retrieved = m.group(0)
125 self.assertEqual(
126 "TRUE", label_retrieved,
127 "label not set correctly. expected and gotten: " +
128 "%s %s" %(LABEL,label_retrieved))
129
130
131class SmackChangeSelfLabelPrivilege(SmackBasicTest):
132
133 @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
134 def test_privileged_change_self_label(self):
135 '''Test if privileged process (with CAP_MAC_ADMIN privilege)
136 can change its label.
137 '''
138
139 labelf = "/proc/self/attr/current"
140 command = "/bin/sh -c 'echo PRIVILEGED >%s; cat %s'" %(labelf, labelf)
141
142 status, output = self.target.run(
143 "notroot.py 0 %s %s" %(self.current_label, command))
144
145 self.assertIn("PRIVILEGED", output,
146 "Privilege process did not change label.Output: %s" %output)
147
148class SmackChangeSelfLabelUnprivilege(SmackBasicTest):
149
150 @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
151 def test_unprivileged_change_self_label(self):
152 '''Test if unprivileged process (without CAP_MAC_ADMIN privilege)
153 cannot change its label'''
154
155 command = "/bin/sh -c 'echo %s >/proc/self/attr/current'" %LABEL
156 status, output = self.target.run(
157 "notroot.py %d %s %s"
158 %(self.uid, self.current_label, command) +
159 " 2>&1 | grep 'Operation not permitted'" )
160
161 self.assertEqual(
162 status, 0,
163 "Unprivileged process should not be able to change its label")
164
165
166class SmackChangeFileLabelPrivilege(SmackBasicTest):
167
168 @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
169 def test_unprivileged_change_file_label(self):
170 '''Test if unprivileged process cannot change file labels'''
171
172 status, chsmack = self.target.run("which chsmack")
173 status, touch = self.target.run("which touch")
174 filename = "/tmp/test_unprivileged_change_file_label"
175
176 self.target.run("touch %s" % filename)
177 self.target.run("notroot.py %d %s" %(self.uid, self.current_label))
178 status, output = self.target.run(
179 "notroot.py " +
180 "%d unprivileged %s -a %s %s 2>&1 " %(self.uid, chsmack, LABEL, filename) +
181 "| grep 'Operation not permitted'" )
182
183 self.target.run("rm %s" % filename)
184 self.assertEqual( status, 0, "Unprivileged process changed label for %s" %filename)
185
186class SmackLoadRule(SmackBasicTest):
187
188 @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
189 def test_load_smack_rule(self):
190 '''Test if new smack access rules can be loaded'''
191
192 # old 23 character format requires special spaces formatting
193 # 12345678901234567890123456789012345678901234567890123
194 ruleA="TheOne TheOther rwxat"
195 ruleB="TheOne TheOther r----"
196 clean="TheOne TheOther -----"
197 modeA = "rwxat"
198 modeB = "r"
199
200 status, output = self.target.run('echo -n "%s" > %s/load' %(ruleA, self.smack_path))
201 status, output = self.target.run( 'cat %s/load | grep "^TheOne" | grep " TheOther "' %self.smack_path)
202 self.assertEqual(status, 0, "Rule A was not added")
203 mode = list(filter(bool, output.split(" ")))[2].strip()
204 self.assertEqual( mode, modeA, "Mode A was not set correctly; mode: %s" %mode)
205
206 status, output = self.target.run( 'echo -n "%s" > %s/load' %(ruleB, self.smack_path))
207 status, output = self.target.run( 'cat %s/load | grep "^TheOne" | grep " TheOther "' %self.smack_path)
208 mode = list(filter(bool, output.split(" ")))[2].strip()
209 self.assertEqual( mode, modeB, "Mode B was not set correctly; mode: %s" %mode)
210
211 self.target.run('echo -n "%s" > %s/load' %(clean, self.smack_path))
212
213
214class SmackOnlycap(SmackBasicTest):
215
216 @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
217 def test_smack_onlycap(self):
218 '''Test if smack onlycap label can be set
219
220 test needs to change the running label of the current process,
221 so whole test takes places on image
222 '''
223 status, output = self.target.run("sh /usr/sbin/test_smack_onlycap.sh")
224 self.assertEqual(status, 0, output)
225
226class SmackNetlabel(SmackBasicTest):
227
228 @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
229 def test_smack_netlabel(self):
230
231 test_label="191.191.191.191 TheOne"
232 expected_label="191.191.191.191/32 TheOne"
233
234 status, output = self.target.run( "echo -n '%s' > %s/netlabel" %(test_label, self.smack_path))
235 self.assertEqual( status, 0, "Netlabel /32 could not be set. Output: %s" %output)
236
237 status, output = self.target.run("cat %s/netlabel" %self.smack_path)
238 self.assertIn( expected_label, output, "Did not find expected label in output: %s" %output)
239
240 test_label="253.253.253.0/24 TheOther"
241 status, output = self.target.run( "echo -n '%s' > %s/netlabel" %(test_label, self.smack_path))
242 self.assertEqual( status, 0, "Netlabel /24 could not be set. Output: %s" %output)
243
244 status, output = self.target.run("cat %s/netlabel" %self.smack_path)
245 self.assertIn(
246 test_label, output,
247 "Did not find expected label in output: %s" %output)
248
249class SmackCipso(SmackBasicTest):
250
251 @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
252 def test_smack_cipso(self):
253 '''Test if smack cipso rules can be set'''
254 # 12345678901234567890123456789012345678901234567890123456
255 ruleA="TheOneA 2 0 "
256 ruleB="TheOneB 3 1 55 "
257 ruleC="TheOneC 4 2 17 33 "
258
259 status, output = self.target.run(
260 "echo -n '%s' > %s/cipso" %(ruleA, self.smack_path))
261 self.assertEqual(status, 0,
262 "Could not set cipso label A. Ouput: %s" %output)
263
264 status, output = self.target.run(
265 "cat %s/cipso | grep '^TheOneA'" %self.smack_path)
266 self.assertEqual(status, 0, "Cipso rule A was not set")
267 self.assertIn(" 2", output, "Rule A was not set correctly")
268
269 status, output = self.target.run(
270 "echo -n '%s' > %s/cipso" %(ruleB, self.smack_path))
271 self.assertEqual(status, 0,
272 "Could not set cipso label B. Ouput: %s" %output)
273
274 status, output = self.target.run(
275 "cat %s/cipso | grep '^TheOneB'" %self.smack_path)
276 self.assertEqual(status, 0, "Cipso rule B was not set")
277 self.assertIn("/55", output, "Rule B was not set correctly")
278
279 status, output = self.target.run(
280 "echo -n '%s' > %s/cipso" %(ruleC, self.smack_path))
281 self.assertEqual(
282 status, 0,
283 "Could not set cipso label C. Ouput: %s" %output)
284
285 status, output = self.target.run(
286 "cat %s/cipso | grep '^TheOneC'" %self.smack_path)
287 self.assertEqual(status, 0, "Cipso rule C was not set")
288 self.assertIn("/17,33", output, "Rule C was not set correctly")
289
290class SmackDirect(SmackBasicTest):
291
292 @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
293 def test_smack_direct(self):
294 status, initial_direct = self.target.run(
295 "cat %s/direct" %self.smack_path)
296
297 test_direct="17"
298 status, output = self.target.run(
299 "echo '%s' > %s/direct" %(test_direct, self.smack_path))
300 self.assertEqual(status, 0 ,
301 "Could not set smack direct. Output: %s" %output)
302 status, new_direct = self.target.run("cat %s/direct" %self.smack_path)
303 # initial label before checking
304 status, output = self.target.run(
305 "echo '%s' > %s/direct" %(initial_direct, self.smack_path))
306 self.assertEqual(
307 test_direct, new_direct.strip(),
308 "Smack direct label does not match.")
309
310
311class SmackAmbient(SmackBasicTest):
312
313 @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
314 def test_smack_ambient(self):
315 test_ambient = "test_ambient"
316 status, initial_ambient = self.target.run("cat %s/ambient" %self.smack_path)
317 status, output = self.target.run(
318 "echo '%s' > %s/ambient" %(test_ambient, self.smack_path))
319 self.assertEqual(status, 0,
320 "Could not set smack ambient. Output: %s" %output)
321
322 status, output = self.target.run("cat %s/ambient" %self.smack_path)
323 # Filter '\x00', which is sometimes added to the ambient label
324 new_ambient = ''.join(filter(lambda x: x in string.printable, output))
325 initial_ambient = ''.join(filter(lambda x: x in string.printable, initial_ambient))
326 status, output = self.target.run(
327 "echo '%s' > %s/ambient" %(initial_ambient, self.smack_path))
328 self.assertEqual(
329 test_ambient, new_ambient.strip(),
330 "Ambient label does not match")
331
332
333class SmackloadBinary(SmackBasicTest):
334
335 @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
336 def test_smackload(self):
337 '''Test if smackload command works'''
338 rule="testobject testsubject rwx"
339
340 status, output = self.target.run("echo -n '%s' > /tmp/rules" %rule)
341 status, output = self.target.run("smackload /tmp/rules")
342 self.assertEqual( status, 0, "Smackload failed to load rule. Output: %s" %output)
343
344 status, output = self.target.run( "cat %s/load | grep '%s'" %(self.smack_path, rule))
345 self.assertEqual(status, 0, "Smackload rule was loaded correctly")
346
347
348class SmackcipsoBinary(SmackBasicTest):
349
350 @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
351 def test_smackcipso(self):
352 '''Test if smackcipso command works'''
353 # 12345678901234567890123456789012345678901234567890123456
354 rule="cipsolabel 2 2 "
355
356 status, output = self.target.run("echo '%s' | smackcipso" %rule)
357 self.assertEqual( status, 0, "Smackcipso failed to load rule. Output: %s" %output)
358
359 status, output = self.target.run(
360 "cat %s/cipso | grep 'cipsolabel'" %self.smack_path)
361 self.assertEqual(status, 0, "smackcipso rule was loaded correctly")
362 self.assertIn( "2/2", output, "Rule was not set correctly. Got: %s" %output)
363
364
365class SmackEnforceFileAccess(SmackBasicTest):
366
367 @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
368 def test_smack_enforce_file_access(self):
369 '''Test if smack file access is enforced (rwx)
370
371 test needs to change the running label of the current process,
372 so whole test takes places on image
373 '''
374 status, output = self.target.run("sh /usr/sbin/smack_test_file_access.sh")
375 self.assertEqual(status, 0, output)
376
377
378class SmackEnforceMmap(SmackBasicTest):
379
380 @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
381 def test_smack_mmap_enforced(self):
382 '''Test if smack mmap access is enforced'''
383 raise unittest.SkipTest("Depends on mmap_test, which was removed from the layer while investigating its license.")
384
385 # 12345678901234567890123456789012345678901234567890123456
386 delr1="mmap_label mmap_test_label1 -----"
387 delr2="mmap_label mmap_test_label2 -----"
388 delr3="mmap_file_label mmap_test_label1 -----"
389 delr4="mmap_file_label mmap_test_label2 -----"
390
391 RuleA="mmap_label mmap_test_label1 rw---"
392 RuleB="mmap_label mmap_test_label2 r--at"
393 RuleC="mmap_file_label mmap_test_label1 rw---"
394 RuleD="mmap_file_label mmap_test_label2 rwxat"
395
396 mmap_label="mmap_label"
397 file_label="mmap_file_label"
398 test_file = "/usr/sbin/smack_test_mmap"
399 mmap_exe = "/tmp/mmap_test"
400 status, echo = self.target.run("which echo")
401 status, output = self.target.run(
402 "notroot.py %d %s %s 'test' > %s" \
403 %(self.uid, self.current_label, echo, test_file))
404 status, output = self.target.run("ls %s" %test_file)
405 self.assertEqual(status, 0, "Could not create mmap test file")
406 self.target.run("chsmack -m %s %s" %(file_label, test_file))
407 self.target.run("chsmack -e %s %s" %(mmap_label, mmap_exe))
408
409 # test with no rules with mmap label or exec label as subject
410 # access should be granted
411 self.target.run('echo -n "%s" > %s/load' %(delr1, self.smack_path))
412 self.target.run('echo -n "%s" > %s/load' %(delr2, self.smack_path))
413 self.target.run('echo -n "%s" > %s/load' %(delr3, self.smack_path))
414 self.target.run('echo -n "%s" > %s/load' %(delr4, self.smack_path))
415 status, output = self.target.run("%s %s 0 2" % (mmap_exe, test_file))
416 self.assertEqual(
417 status, 0,
418 "Should have mmap access without rules. Output: %s" %output)
419
420 # add rules that do not match access required
421 self.target.run('echo -n "%s" > %s/load' %(RuleA, self.smack_path))
422 self.target.run('echo -n "%s" > %s/load' %(RuleB, self.smack_path))
423 status, output = self.target.run("%s %s 0 2" % (mmap_exe, test_file))
424 self.assertNotEqual(
425 status, 0,
426 "Should not have mmap access with unmatching rules. " +
427 "Output: %s" %output)
428 self.assertIn(
429 "Permission denied", output,
430 "Mmap access should be denied with unmatching rules")
431
432 # add rule to match only partially (one way)
433 self.target.run('echo -n "%s" > %s/load' %(RuleC, self.smack_path))
434 status, output = self.target.run("%s %s 0 2" %(mmap_exe, test_file))
435 self.assertNotEqual(
436 status, 0,
437 "Should not have mmap access with partial matching rules. " +
438 "Output: %s" %output)
439 self.assertIn(
440 "Permission denied", output,
441 "Mmap access should be denied with partial matching rules")
442
443 # add rule to match fully
444 self.target.run('echo -n "%s" > %s/load' %(RuleD, self.smack_path))
445 status, output = self.target.run("%s %s 0 2" %(mmap_exe, test_file))
446 self.assertEqual(
447 status, 0,
448 "Should have mmap access with full matching rules." +
449 "Output: %s" %output)
450
451
452class SmackEnforceTransmutable(SmackBasicTest):
453
454 @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
455 def test_smack_transmute_dir(self):
456 '''Test if smack transmute attribute works
457
458 test needs to change the running label of the current process,
459 so whole test takes places on image
460 '''
461 test_dir = "/tmp/smack_transmute_dir"
462 label="transmute_label"
463 status, initial_label = self.target.run("cat /proc/self/attr/current")
464
465 self.target.run("mkdir -p %s" % test_dir)
466 self.target.run("chsmack -a %s %s" % (label, test_dir))
467 self.target.run("chsmack -t %s" % test_dir)
468 self.target.run("echo -n '%s %s rwxat' | smackload" %(initial_label, label) )
469
470 self.target.run("touch %s/test" % test_dir)
471 status, output = self.target.run("chsmack %s/test" % test_dir)
472 self.assertIn( 'access="%s"' %label, output,
473 "Did not get expected label. Output: %s" % output)
474
475
476class SmackTcpSockets(SmackBasicTest):
477
478 @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
479 def test_smack_tcp_sockets(self):
480 '''Test if smack is enforced on tcp sockets
481
482 whole test takes places on image, depends on tcp_server/tcp_client'''
483
484 status, output = self.target.run("sh /usr/sbin/test_smack_tcp_sockets.sh")
485 self.assertEqual(status, 0, output)
486
487
488class SmackUdpSockets(SmackBasicTest):
489
490 @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
491 def test_smack_udp_sockets(self):
492 '''Test if smack is enforced on udp sockets
493
494 whole test takes places on image, depends on udp_server/udp_client'''
495
496 status, output = self.target.run("sh /usr/sbin/test_smack_udp_sockets.sh")
497 self.assertEqual(status, 0, output)
498
499
500class SmackFileLabels(SmackBasicTest):
501
502 @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
503 def test_smack_labels(self):
504 '''Check for correct Smack labels.'''
505 expected = '''
506/tmp/ access="*"
507/etc/ access="System::Shared" transmute="TRUE"
508/etc/passwd access="System::Shared"
509/etc/terminfo access="System::Shared" transmute="TRUE"
510/etc/skel/ access="System::Shared" transmute="TRUE"
511/etc/skel/.profile access="System::Shared"
512/var/log/ access="System::Log" transmute="TRUE"
513/var/tmp/ access="*"
514'''
515 files = ' '.join([x.split()[0] for x in expected.split('\n') if x])
516 files_wildcard = ' '.join([x + '/*' for x in files.split()])
517 # Auxiliary information.
518 status, output = self.target.run(
519 'set -x; mount; ls -l -d %s; find %s | xargs ls -d -l; find %s | xargs chsmack' % (
520 ' '.join([x.rstrip('/') for x in files.split()]), files, files
521 )
522 )
523 msg = "File status:\n" + output
524 status, output = self.target.run('chsmack %s' % files)
525 self.assertEqual(
526 status, 0, msg="status and output: %s and %s\n%s" % (status,output, msg))
527 self.longMessage = True
528 self.maxDiff = None
529 self.assertEqual(output.strip().split('\n'), expected.strip().split('\n'), msg=msg)