1#!/usr/bin/env python3
2"""Unit test suite for the fs_config_genertor.py tool."""
3
4import tempfile
5import textwrap
6import unittest
7
8from fs_config_generator import AID
9from fs_config_generator import AIDHeaderParser
10from fs_config_generator import FSConfigFileParser
11from fs_config_generator import FSConfig
12from fs_config_generator import Utils
13
14
15# Disable protected access so we can test class internal
16# methods. Also, disable invalid-name as some of the
17# class method names are over length.
18# pylint: disable=protected-access,invalid-name
19class Tests(unittest.TestCase):
20    """Test class for unit tests"""
21
22    def test_is_overlap(self):
23        """Test overlap detection helper"""
24
25        self.assertTrue(AIDHeaderParser._is_overlap((0, 1), (1, 2)))
26
27        self.assertTrue(AIDHeaderParser._is_overlap((0, 100), (90, 200)))
28
29        self.assertTrue(AIDHeaderParser._is_overlap((20, 50), (1, 101)))
30
31        self.assertFalse(AIDHeaderParser._is_overlap((0, 100), (101, 200)))
32
33        self.assertFalse(AIDHeaderParser._is_overlap((-10, 0), (10, 20)))
34
35    def test_in_any_range(self):
36        """Test if value in range"""
37
38        self.assertFalse(Utils.in_any_range(50, [(100, 200), (1, 2), (1, 1)]))
39        self.assertFalse(Utils.in_any_range(250, [(100, 200), (1, 2), (1, 1)]))
40
41        self.assertTrue(Utils.in_any_range(100, [(100, 200), (1, 2), (1, 1)]))
42        self.assertTrue(Utils.in_any_range(200, [(100, 200), (1, 2), (1, 1)]))
43        self.assertTrue(Utils.in_any_range(150, [(100, 200)]))
44
45    def test_aid(self):
46        """Test AID class constructor"""
47
48        aid = AID('AID_FOO_BAR', '0xFF', 'myfakefile', '/system/bin/sh')
49        self.assertEqual(aid.identifier, 'AID_FOO_BAR')
50        self.assertEqual(aid.value, '0xFF')
51        self.assertEqual(aid.found, 'myfakefile')
52        self.assertEqual(aid.normalized_value, '255')
53        self.assertEqual(aid.friendly, 'foo_bar')
54        self.assertEqual(aid.login_shell, '/system/bin/sh')
55
56        aid = AID('AID_MEDIA_EX', '1234', 'myfakefile', '/vendor/bin/sh')
57        self.assertEqual(aid.identifier, 'AID_MEDIA_EX')
58        self.assertEqual(aid.value, '1234')
59        self.assertEqual(aid.found, 'myfakefile')
60        self.assertEqual(aid.normalized_value, '1234')
61        self.assertEqual(aid.friendly, 'mediaex')
62        self.assertEqual(aid.login_shell, '/vendor/bin/sh')
63
64    def test_aid_header_parser_good(self):
65        """Test AID Header Parser good input file"""
66
67        with tempfile.NamedTemporaryFile(mode='w') as temp_file:
68            temp_file.write(
69                textwrap.dedent("""
70                #define AID_FOO 1000
71                #define AID_BAR 1001
72                #define SOMETHING "something"
73                #define AID_OEM_RESERVED_START 2900
74                #define AID_OEM_RESERVED_END   2999
75                #define AID_OEM_RESERVED_1_START  7000
76                #define AID_OEM_RESERVED_1_END    8000
77            """))
78            temp_file.flush()
79
80            parser = AIDHeaderParser(temp_file.name)
81            ranges = parser.ranges
82            aids = parser.aids
83
84            self.assertTrue((2900, 2999) in ranges["vendor"])
85            self.assertFalse((5000, 6000) in ranges["vendor"])
86
87            for aid in aids:
88                self.assertTrue(aid.normalized_value in ['1000', '1001'])
89                self.assertFalse(aid.normalized_value in ['1', '2', '3'])
90
91    def test_aid_header_parser_good_unordered(self):
92        """Test AID Header Parser good unordered input file"""
93
94        with tempfile.NamedTemporaryFile(mode='w') as temp_file:
95            temp_file.write(
96                textwrap.dedent("""
97                #define AID_FOO 1000
98                #define AID_OEM_RESERVED_1_END    8000
99                #define AID_BAR 1001
100                #define SOMETHING "something"
101                #define AID_OEM_RESERVED_END   2999
102                #define AID_OEM_RESERVED_1_START  7000
103                #define AID_OEM_RESERVED_START 2900
104            """))
105            temp_file.flush()
106
107            parser = AIDHeaderParser(temp_file.name)
108            ranges = parser.ranges
109            aids = parser.aids
110
111            self.assertTrue((2900, 2999) in ranges["vendor"])
112            self.assertFalse((5000, 6000) in ranges["vendor"])
113
114            for aid in aids:
115                self.assertTrue(aid.normalized_value in ['1000', '1001'])
116                self.assertFalse(aid.normalized_value in ['1', '2', '3'])
117
118    def test_aid_header_parser_bad_aid(self):
119        """Test AID Header Parser bad aid input file"""
120
121        with tempfile.NamedTemporaryFile(mode='w') as temp_file:
122            temp_file.write(
123                textwrap.dedent("""
124                #define AID_FOO "bad"
125            """))
126            temp_file.flush()
127
128            with self.assertRaises(SystemExit):
129                AIDHeaderParser(temp_file.name)
130
131    def test_aid_header_parser_bad_oem_range(self):
132        """Test AID Header Parser bad oem range input file"""
133
134        with tempfile.NamedTemporaryFile(mode='w') as temp_file:
135            temp_file.write(
136                textwrap.dedent("""
137                #define AID_OEM_RESERVED_START 2900
138                #define AID_OEM_RESERVED_END   1800
139            """))
140            temp_file.flush()
141
142            with self.assertRaises(SystemExit):
143                AIDHeaderParser(temp_file.name)
144
145    def test_aid_header_parser_bad_oem_range_no_end(self):
146        """Test AID Header Parser bad oem range (no end) input file"""
147
148        with tempfile.NamedTemporaryFile(mode='w') as temp_file:
149            temp_file.write(
150                textwrap.dedent("""
151                #define AID_OEM_RESERVED_START 2900
152            """))
153            temp_file.flush()
154
155            with self.assertRaises(SystemExit):
156                AIDHeaderParser(temp_file.name)
157
158    def test_aid_header_parser_bad_oem_range_no_start(self):
159        """Test AID Header Parser bad oem range (no start) input file"""
160
161        with tempfile.NamedTemporaryFile(mode='w') as temp_file:
162            temp_file.write(
163                textwrap.dedent("""
164                #define AID_OEM_RESERVED_END 2900
165            """))
166            temp_file.flush()
167
168            with self.assertRaises(SystemExit):
169                AIDHeaderParser(temp_file.name)
170
171    def test_aid_header_parser_bad_oem_range_duplicated(self):
172        """Test AID Header Parser bad oem range (no start) input file"""
173
174        with tempfile.NamedTemporaryFile(mode='w') as temp_file:
175            temp_file.write(
176                textwrap.dedent("""
177                #define AID_OEM_RESERVED_START 2000
178                #define AID_OEM_RESERVED_END 2900
179                #define AID_OEM_RESERVED_START 3000
180                #define AID_OEM_RESERVED_END 3900
181            """))
182            temp_file.flush()
183
184            with self.assertRaises(SystemExit):
185                AIDHeaderParser(temp_file.name)
186
187    def test_aid_header_parser_bad_oem_range_mismatch_start_end(self):
188        """Test AID Header Parser bad oem range mismatched input file"""
189
190        with tempfile.NamedTemporaryFile(mode='w') as temp_file:
191            temp_file.write(
192                textwrap.dedent("""
193                #define AID_OEM_RESERVED_START 2900
194                #define AID_OEM_RESERVED_2_END 2900
195            """))
196            temp_file.flush()
197
198            with self.assertRaises(SystemExit):
199                AIDHeaderParser(temp_file.name)
200
201    def test_aid_header_parser_bad_duplicate_ranges(self):
202        """Test AID Header Parser exits cleanly on duplicate AIDs"""
203
204        with tempfile.NamedTemporaryFile(mode='w') as temp_file:
205            temp_file.write(
206                textwrap.dedent("""
207                #define AID_FOO 100
208                #define AID_BAR 100
209            """))
210            temp_file.flush()
211
212            with self.assertRaises(SystemExit):
213                AIDHeaderParser(temp_file.name)
214
215    def test_aid_header_parser_no_bad_aids(self):
216        """Test AID Header Parser that it doesn't contain:
217        Ranges, ie things the end with "_START" or "_END"
218        AID_APP
219        AID_USER
220        For more details see:
221          - https://android-review.googlesource.com/#/c/313024
222          - https://android-review.googlesource.com/#/c/313169
223        """
224
225        with tempfile.NamedTemporaryFile(mode='w') as temp_file:
226            temp_file.write(
227                textwrap.dedent("""
228                #define AID_APP              10000 /* TODO: switch users over to AID_APP_START */
229                #define AID_APP_START        10000 /* first app user */
230                #define AID_APP_END          19999 /* last app user */
231
232                #define AID_CACHE_GID_START  20000 /* start of gids for apps to mark cached data */
233                #define AID_CACHE_GID_END    29999 /* end of gids for apps to mark cached data */
234
235                #define AID_SHARED_GID_START 50000 /* start of gids for apps in each user to share */
236                #define AID_SHARED_GID_END   59999 /* end of gids for apps in each user to share */
237
238                #define AID_ISOLATED_START   99000 /* start of uids for fully isolated sandboxed processes */
239                #define AID_ISOLATED_END     99999 /* end of uids for fully isolated sandboxed processes */
240
241                #define AID_USER            100000 /* TODO: switch users over to AID_USER_OFFSET */
242                #define AID_USER_OFFSET     100000 /* offset for uid ranges for each user */
243            """))
244            temp_file.flush()
245
246            parser = AIDHeaderParser(temp_file.name)
247            aids = parser.aids
248
249            bad_aids = ['_START', '_END', 'AID_APP', 'AID_USER']
250
251            for aid in aids:
252                self.assertFalse(
253                    any(bad in aid.identifier for bad in bad_aids),
254                    'Not expecting keywords "%s" in aids "%s"' %
255                    (str(bad_aids), str([tmp.identifier for tmp in aids])))
256
257    def test_fs_config_file_parser_good(self):
258        """Test FSConfig Parser good input file"""
259
260        with tempfile.NamedTemporaryFile(mode='w') as temp_file:
261            temp_file.write(
262                textwrap.dedent("""
263                [/system/bin/file]
264                user: AID_FOO
265                group: AID_SYSTEM
266                mode: 0777
267                caps: BLOCK_SUSPEND
268
269                [/vendor/path/dir/]
270                user: AID_FOO
271                group: AID_SYSTEM
272                mode: 0777
273                caps: 0
274
275                [AID_OEM1]
276                # 5001 in base16
277                value: 0x1389
278            """))
279            temp_file.flush()
280
281            parser = FSConfigFileParser([temp_file.name], {"oem1": [(5000, 5999)]})
282            files = parser.files
283            dirs = parser.dirs
284            aids = parser.aids
285
286            self.assertEqual(len(files), 1)
287            self.assertEqual(len(dirs), 1)
288            self.assertEqual(len(aids), 1)
289
290            aid = aids[0]
291            fcap = files[0]
292            dcap = dirs[0]
293
294            self.assertEqual(fcap,
295                             FSConfig('0777', 'AID_FOO', 'AID_SYSTEM',
296                                      'CAP_BLOCK_SUSPEND',
297                                      '/system/bin/file', temp_file.name))
298
299            self.assertEqual(dcap,
300                             FSConfig('0777', 'AID_FOO', 'AID_SYSTEM', '0',
301                                      '/vendor/path/dir/', temp_file.name))
302
303            self.assertEqual(aid, AID('AID_OEM1', '0x1389', temp_file.name, '/bin/sh'))
304
305    def test_fs_config_file_parser_bad(self):
306        """Test FSConfig Parser bad input file"""
307
308        with tempfile.NamedTemporaryFile(mode='w') as temp_file:
309            temp_file.write(
310                textwrap.dedent("""
311                [/system/bin/file]
312                caps: BLOCK_SUSPEND
313            """))
314            temp_file.flush()
315
316            with self.assertRaises(SystemExit):
317                FSConfigFileParser([temp_file.name], {})
318
319    def test_fs_config_file_parser_bad_aid_range(self):
320        """Test FSConfig Parser bad aid range value input file"""
321
322        with tempfile.NamedTemporaryFile(mode='w') as temp_file:
323            temp_file.write(
324                textwrap.dedent("""
325                [AID_OEM1]
326                value: 25
327            """))
328            temp_file.flush()
329
330            with self.assertRaises(SystemExit):
331                FSConfigFileParser([temp_file.name], {"oem1": [(5000, 5999)]})
332
333if __name__ == "__main__":
334    unittest.main()
335