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