1import 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.current_label  = ""
19        cls.uid = 1000
20        status, output = cls.tc.target.run("grep smack /proc/mounts | awk '{print $2}'")
21        cls.smack_path = output
22
23    @skipIfNotFeature('smack',
24        'Test requires smack to be in DISTRO_FEATURES')
25    @OEHasPackage(['smack-test'])
26    @OETestDepends(['ssh.SSHTest.test_ssh'])
27    def test_smack_basic(self):
28        status,output = self.target.run("cat /proc/self/attr/current")
29        self.current_label = output.strip()
30
31    @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
32    def test_add_access_label(self):
33        ''' Test if chsmack can correctly set a SMACK label '''
34        filename = "/tmp/test_access_label"
35        self.target.run("touch %s" %filename)
36        status, output = self.target.run("chsmack -a %s %s" %(LABEL, filename))
37        self.assertEqual(
38            status, 0,
39            "Cannot set smack access label. "
40            "Status and output: %d %s" %(status, output))
41        status, output = self.target.run("chsmack %s" %filename)
42        self.target.run("rm %s" %filename)
43        m = re.search('(access=")\S+(?=")', output)
44        if m is None:
45            self.fail("Did not find access attribute")
46        else:
47            label_retrieved = re.split("access=\"", output)[1][:-1]
48            self.assertEqual(
49                LABEL, label_retrieved,
50                "label not set correctly. expected and gotten: "
51                "%s %s" %(LABEL,label_retrieved))
52
53
54    @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
55    def test_add_exec_label(self):
56        '''Test if chsmack can correctly set a SMACK Exec label'''
57        filename = "/tmp/test_exec_label"
58        self.target.run("touch %s" %filename)
59        status, output = self.target.run("chsmack -e %s %s" %(LABEL, filename))
60        self.assertEqual(
61            status, 0,
62            "Cannot set smack exec label. "
63            "Status and output: %d %s" %(status, output))
64        status, output = self.target.run("chsmack %s" %filename)
65        self.target.run("rm %s" %filename)
66        m= re.search('(execute=")\S+(?=")', output)
67        if m is None:
68            self.fail("Did not find execute attribute")
69        else:
70            label_retrieved = re.split("execute=\"", output)[1][:-1]
71            self.assertEqual(
72                LABEL, label_retrieved,
73                "label not set correctly. expected and gotten: " +
74                "%s %s" %(LABEL,label_retrieved))
75
76
77    @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
78    def test_add_mmap_label(self):
79        '''Test if chsmack can correctly set a SMACK mmap label'''
80        filename = "/tmp/test_exec_label"
81        self.target.run("touch %s" %filename)
82        status, output = self.target.run("chsmack -m %s %s" %(LABEL, filename))
83        self.assertEqual(
84            status, 0,
85            "Cannot set smack mmap label. "
86            "Status and output: %d %s" %(status, output))
87        status, output = self.target.run("chsmack %s" %filename)
88        self.target.run("rm %s" %filename)
89        m = re.search('(mmap=")\S+(?=")', output)
90        if m is None:
91            self.fail("Did not find mmap attribute")
92        else:
93            label_retrieved = re.split("mmap=\"", output)[1][:-1]
94            self.assertEqual(
95                LABEL, label_retrieved,
96                "label not set correctly. expected and gotten: " +
97                "%s %s" %(LABEL,label_retrieved))
98
99
100    @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
101    def test_add_transmutable(self):
102        '''Test if chsmack can correctly set a SMACK transmutable mode'''
103
104        directory = "~/test_transmutable"
105        self.target.run("mkdir -p %s" %directory)
106        status, output = self.target.run("chsmack -t %s" %directory)
107        self.assertEqual(status, 0, "Cannot set smack transmutable. "
108                        "Status and output: %d %s" %(status, output))
109        status, output = self.target.run("chsmack %s" %directory)
110        self.target.run("rmdir %s" %directory)
111        m = re.search('(transmute=")\S+(?=")', output)
112        if m is None:
113            self.fail("Did not find transmute attribute")
114        else:
115            label_retrieved = re.split("transmute=\"", output)[1][:-1]
116            self.assertEqual(
117                "TRUE", label_retrieved,
118                "label not set correctly. expected and gotten: " +
119                "%s %s" %(LABEL,label_retrieved))
120
121
122    @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
123    def test_privileged_change_self_label(self):
124        '''Test if privileged process (with CAP_MAC_ADMIN privilege)
125        can change its label.
126        '''
127
128        labelf = "/proc/self/attr/current"
129        command = "/bin/sh -c 'echo PRIVILEGED >%s'; cat %s" %(labelf, labelf)
130
131        status, output = self.target.run(
132            "/usr/sbin/notroot.py 0 %s %s" %(self.current_label, command))
133
134        self.assertIn("PRIVILEGED", output,
135                    "Privilege process did not change label.Output: %s" %output)
136
137    @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
138    def test_unprivileged_change_self_label(self):
139        '''Test if unprivileged process (without CAP_MAC_ADMIN privilege)
140        cannot change its label'''
141
142        command = "/bin/sh -c 'echo %s >/proc/self/attr/current'" %LABEL
143        status, output = self.target.run(
144            "/usr/sbin/notroot.py %d %s %s"
145            %(self.uid, self.current_label, command) +
146            " 2>&1 | grep 'Operation not permitted'" )
147
148        self.assertEqual(
149            status, 0,
150            "Unprivileged process should not be able to change its label")
151
152
153    @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
154    def test_unprivileged_change_file_label(self):
155        '''Test if unprivileged process cannot change file labels'''
156
157        status, chsmack = self.target.run("which chsmack")
158        status, touch = self.target.run("which touch")
159        filename = "/tmp/test_unprivileged_change_file_label"
160
161        self.target.run("touch %s" % filename)
162        self.target.run("/usr/sbin/notroot.py %d %s" %(self.uid, self.current_label))
163        status, output = self.target.run(
164            "/usr/sbin/notroot.py " +
165            "%d unprivileged %s -a %s %s 2>&1 " %(self.uid, chsmack, LABEL, filename) +
166            "| grep 'Operation not permitted'"  )
167
168        self.target.run("rm %s" % filename)
169        self.assertEqual( status, 0, "Unprivileged process changed label for %s" %filename)
170
171    @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
172    def test_load_smack_rule(self):
173        '''Test if new smack access rules can be loaded'''
174
175        # old 23 character format requires special spaces formatting
176        #      12345678901234567890123456789012345678901234567890123
177        ruleA="TheOne                  TheOther                rwxat"
178        ruleB="TheOne                  TheOther                r----"
179        clean="TheOne                  TheOther                -----"
180        modeA = "rwxat"
181        modeB = "r"
182
183        status, output = self.target.run('echo -n "%s" > %s/load' %(ruleA, self.smack_path))
184        status, output = self.target.run( 'cat %s/load | grep "^TheOne" | grep " TheOther "' %self.smack_path)
185        self.assertEqual(status, 0, "Rule A was not added")
186        mode = list(filter(bool, output.split(" ")))[2].strip()
187        self.assertEqual( mode, modeA, "Mode A was not set correctly; mode: %s" %mode)
188
189        status, output = self.target.run( 'echo -n "%s" > %s/load' %(ruleB, self.smack_path))
190        status, output = self.target.run( 'cat %s/load | grep "^TheOne" | grep " TheOther "' %self.smack_path)
191        mode = list(filter(bool, output.split(" ")))[2].strip()
192        self.assertEqual( mode, modeB, "Mode B was not set correctly; mode: %s" %mode)
193
194        self.target.run('echo -n "%s" > %s/load' %(clean, self.smack_path))
195
196
197    @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
198    def test_smack_onlycap(self):
199        '''Test if smack onlycap label can be set
200
201        test needs to change the running label of the current process,
202        so whole test takes places on image
203        '''
204        status, output = self.target.run("sh /usr/sbin/test_smack_onlycap.sh")
205        self.assertEqual(status, 0, output)
206
207
208    @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
209    def test_smack_netlabel(self):
210
211        test_label="191.191.191.191 TheOne"
212        expected_label="191.191.191.191/32 TheOne"
213
214        status, output = self.target.run( "echo -n '%s' > %s/netlabel" %(test_label, self.smack_path))
215        self.assertEqual( status, 0, "Netlabel /32 could not be set. Output: %s" %output)
216
217        status, output = self.target.run("cat %s/netlabel" %self.smack_path)
218        self.assertIn( expected_label, output, "Did not find expected label in output: %s" %output)
219
220        test_label="253.253.253.0/24 TheOther"
221        status, output = self.target.run( "echo -n '%s' > %s/netlabel" %(test_label, self.smack_path))
222        self.assertEqual( status, 0, "Netlabel /24 could not be set. Output: %s" %output)
223
224        status, output = self.target.run("cat %s/netlabel" %self.smack_path)
225        self.assertIn(
226            test_label, output,
227            "Did not find expected label in output: %s" %output)
228
229
230    @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
231    def test_smack_cipso(self):
232        '''Test if smack cipso rules can be set'''
233        #      12345678901234567890123456789012345678901234567890123456
234        ruleA="TheOneA                 2   0   "
235        ruleB="TheOneB                 3   1   55  "
236        ruleC="TheOneC                 4   2   17  33  "
237
238        status, output = self.target.run(
239            "echo -n '%s' > %s/cipso" %(ruleA, self.smack_path))
240        self.assertEqual(status, 0,
241            "Could not set cipso label A. Ouput: %s" %output)
242
243        status, output = self.target.run(
244            "cat %s/cipso | grep '^TheOneA'" %self.smack_path)
245        self.assertEqual(status, 0, "Cipso rule A was not set")
246        self.assertIn(" 2", output, "Rule A was not set correctly")
247
248        status, output = self.target.run(
249            "echo -n '%s' > %s/cipso" %(ruleB, self.smack_path))
250        self.assertEqual(status, 0,
251            "Could not set cipso label B. Ouput: %s" %output)
252
253        status, output = self.target.run(
254            "cat %s/cipso | grep '^TheOneB'" %self.smack_path)
255        self.assertEqual(status, 0, "Cipso rule B was not set")
256        self.assertIn("/55", output, "Rule B was not set correctly")
257
258        status, output = self.target.run(
259            "echo -n '%s' > %s/cipso" %(ruleC, self.smack_path))
260        self.assertEqual(
261            status, 0,
262            "Could not set cipso label C. Ouput: %s" %output)
263
264        status, output = self.target.run(
265            "cat %s/cipso | grep '^TheOneC'" %self.smack_path)
266        self.assertEqual(status, 0, "Cipso rule C was not set")
267        self.assertIn("/17,33", output, "Rule C was not set correctly")
268
269
270    @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
271    def test_smack_direct(self):
272        status, initial_direct = self.target.run(
273            "cat %s/direct" %self.smack_path)
274
275        test_direct="17"
276        status, output = self.target.run(
277            "echo '%s' > %s/direct" %(test_direct, self.smack_path))
278        self.assertEqual(status, 0 ,
279            "Could not set smack direct. Output: %s" %output)
280        status, new_direct = self.target.run("cat %s/direct" %self.smack_path)
281        # initial label before checking
282        status, output = self.target.run(
283            "echo '%s' > %s/direct" %(initial_direct, self.smack_path))
284        self.assertEqual(
285            test_direct, new_direct.strip(),
286            "Smack direct label does not match.")
287
288
289    @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
290    def test_smack_ambient(self):
291        test_ambient = "test_ambient"
292        status, initial_ambient = self.target.run("cat %s/ambient" %self.smack_path)
293        status, output = self.target.run(
294            "echo '%s' > %s/ambient" %(test_ambient, self.smack_path))
295        self.assertEqual(status, 0,
296            "Could not set smack ambient. Output: %s" %output)
297
298        status, output = self.target.run("cat %s/ambient" %self.smack_path)
299        # Filter '\x00', which is sometimes added to the ambient label
300        new_ambient = ''.join(filter(lambda x: x in string.printable, output))
301        initial_ambient = ''.join(filter(lambda x: x in string.printable, initial_ambient))
302        status, output = self.target.run(
303            "echo '%s' > %s/ambient" %(initial_ambient, self.smack_path))
304        self.assertEqual(
305            test_ambient, new_ambient.strip(),
306            "Ambient label does not match")
307
308
309    @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
310    def test_smackload(self):
311        '''Test if smackload command works'''
312        rule="testobject testsubject rwx"
313
314        status, output = self.target.run("echo -n '%s' > /tmp/rules" %rule)
315        status, output = self.target.run("smackload /tmp/rules")
316        self.assertEqual( status, 0, "Smackload failed to load rule. Output: %s" %output)
317
318        status, output = self.target.run( "cat %s/load | grep '%s'" %(self.smack_path, rule))
319        self.assertEqual(status, 0, "Smackload rule was loaded correctly")
320
321
322    @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
323    def test_smackcipso(self):
324        '''Test if smackcipso command works'''
325        #     12345678901234567890123456789012345678901234567890123456
326        rule="cipsolabel                  2   2   "
327
328        status, output = self.target.run("echo '%s' | smackcipso" %rule)
329        self.assertEqual( status, 0, "Smackcipso failed to load rule. Output: %s" %output)
330
331        status, output = self.target.run(
332            "cat %s/cipso | grep 'cipsolabel'" %self.smack_path)
333        self.assertEqual(status, 0, "smackcipso rule was loaded correctly")
334        self.assertIn( "2/2", output, "Rule was not set correctly. Got: %s" %output)
335
336
337    @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
338    def test_smack_enforce_file_access(self):
339        '''Test if smack file access is enforced (rwx)
340
341        test needs to change the running label of the current process,
342        so whole test takes places on image
343        '''
344        status, output = self.target.run("sh /usr/sbin/smack_test_file_access.sh")
345        self.assertEqual(status, 0, output)
346
347
348    @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
349    def test_smack_transmute_dir(self):
350        '''Test if smack transmute attribute works
351
352        test needs to change the running label of the current process,
353        so whole test takes places on image
354        '''
355        test_dir = "/tmp/smack_transmute_dir"
356        label="transmute_label"
357        status, initial_label = self.target.run("cat /proc/self/attr/current")
358
359        self.target.run("mkdir -p %s" % test_dir)
360        self.target.run("chsmack -a %s %s" % (label, test_dir))
361        self.target.run("chsmack -t %s" % test_dir)
362        self.target.run("echo -n '%s %s rwxat' | smackload" %(initial_label, label) )
363
364        self.target.run("touch %s/test" % test_dir)
365        status, output = self.target.run("chsmack %s/test" % test_dir)
366        self.assertIn( 'access="%s"' %label, output,
367            "Did not get expected label. Output: %s" % output)
368
369
370    @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
371    def test_smack_tcp_sockets(self):
372        '''Test if smack is enforced on tcp sockets
373
374        whole test takes places on image, depends on tcp_server/tcp_client'''
375
376        status, output = self.target.run("sh /usr/sbin/test_smack_tcp_sockets.sh")
377        self.assertEqual(status, 0, output)
378
379
380    @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
381    def test_smack_udp_sockets(self):
382        '''Test if smack is enforced on udp sockets
383
384        whole test takes places on image, depends on udp_server/udp_client'''
385
386        status, output = self.target.run("sh /usr/sbin/test_smack_udp_sockets.sh")
387        self.assertEqual(status, 0, output)
388
389
390    @OETestDepends(['smack.SmackBasicTest.test_smack_basic'])
391    def test_smack_labels(self):
392        '''Check for correct Smack labels.'''
393        expected = '''
394/tmp/ access="*"
395/etc/ access="System::Shared" transmute="TRUE"
396/etc/passwd access="System::Shared"
397/etc/terminfo access="System::Shared" transmute="TRUE"
398/etc/skel/ access="System::Shared" transmute="TRUE"
399/etc/skel/.profile access="System::Shared"
400/var/log/ access="System::Log" transmute="TRUE"
401/var/tmp/ access="*"
402'''
403        files = ' '.join([x.split()[0] for x in expected.split('\n') if x])
404        files_wildcard = ' '.join([x + '/*' for x in files.split()])
405        # Auxiliary information.
406        status, output = self.target.run(
407            'set -x; mount; ls -l -d %s; find %s | xargs ls -d -l; find %s | xargs chsmack' % (
408                ' '.join([x.rstrip('/') for x in files.split()]), files, files
409            )
410        )
411        msg = "File status:\n" + output
412        status, output = self.target.run('chsmack %s' % files)
413        self.assertEqual(
414            status, 0, msg="status and output: %s and %s\n%s" % (status,output, msg))
415        self.longMessage = True
416        self.maxDiff = None
417        self.assertEqual(output.strip().split('\n'), expected.strip().split('\n'), msg=msg)
418