1#!/usr/bin/env python
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() 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            oem_ranges = parser.oem_ranges
82            aids = parser.aids
83
84            self.assertTrue((2900, 2999) in oem_ranges)
85            self.assertFalse((5000, 6000) in oem_ranges)
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() 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            oem_ranges = parser.oem_ranges
109            aids = parser.aids
110
111            self.assertTrue((2900, 2999) in oem_ranges)
112            self.assertFalse((5000, 6000) in oem_ranges)
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() 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() 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() 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() 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_mismatch_start_end(self):
172        """Test AID Header Parser bad oem range mismatched input file"""
173
174        with tempfile.NamedTemporaryFile() as temp_file:
175            temp_file.write(
176                textwrap.dedent("""
177                #define AID_OEM_RESERVED_START 2900
178                #define AID_OEM_RESERVED_2_END 2900
179            """))
180            temp_file.flush()
181
182            with self.assertRaises(SystemExit):
183                AIDHeaderParser(temp_file.name)
184
185    def test_aid_header_parser_bad_duplicate_ranges(self):
186        """Test AID Header Parser exits cleanly on duplicate AIDs"""
187
188        with tempfile.NamedTemporaryFile() as temp_file:
189            temp_file.write(
190                textwrap.dedent("""
191                #define AID_FOO 100
192                #define AID_BAR 100
193            """))
194            temp_file.flush()
195
196            with self.assertRaises(SystemExit):
197                AIDHeaderParser(temp_file.name)
198
199    def test_aid_header_parser_no_bad_aids(self):
200        """Test AID Header Parser that it doesn't contain:
201        Ranges, ie things the end with "_START" or "_END"
202        AID_APP
203        AID_USER
204        For more details see:
205          - https://android-review.googlesource.com/#/c/313024
206          - https://android-review.googlesource.com/#/c/313169
207        """
208
209        with tempfile.NamedTemporaryFile() as temp_file:
210            temp_file.write(
211                textwrap.dedent("""
212                #define AID_APP              10000 /* TODO: switch users over to AID_APP_START */
213                #define AID_APP_START        10000 /* first app user */
214                #define AID_APP_END          19999 /* last app user */
215
216                #define AID_CACHE_GID_START  20000 /* start of gids for apps to mark cached data */
217                #define AID_CACHE_GID_END    29999 /* end of gids for apps to mark cached data */
218
219                #define AID_SHARED_GID_START 50000 /* start of gids for apps in each user to share */
220                #define AID_SHARED_GID_END   59999 /* end of gids for apps in each user to share */
221
222                #define AID_ISOLATED_START   99000 /* start of uids for fully isolated sandboxed processes */
223                #define AID_ISOLATED_END     99999 /* end of uids for fully isolated sandboxed processes */
224
225                #define AID_USER            100000 /* TODO: switch users over to AID_USER_OFFSET */
226                #define AID_USER_OFFSET     100000 /* offset for uid ranges for each user */
227            """))
228            temp_file.flush()
229
230            parser = AIDHeaderParser(temp_file.name)
231            aids = parser.aids
232
233            bad_aids = ['_START', '_END', 'AID_APP', 'AID_USER']
234
235            for aid in aids:
236                self.assertFalse(
237                    any(bad in aid.identifier for bad in bad_aids),
238                    'Not expecting keywords "%s" in aids "%s"' %
239                    (str(bad_aids), str([tmp.identifier for tmp in aids])))
240
241    def test_fs_config_file_parser_good(self):
242        """Test FSConfig Parser good input file"""
243
244        with tempfile.NamedTemporaryFile() as temp_file:
245            temp_file.write(
246                textwrap.dedent("""
247                [/system/bin/file]
248                user: AID_FOO
249                group: AID_SYSTEM
250                mode: 0777
251                caps: BLOCK_SUSPEND
252
253                [/vendor/path/dir/]
254                user: AID_FOO
255                group: AID_SYSTEM
256                mode: 0777
257                caps: 0
258
259                [AID_OEM1]
260                # 5001 in base16
261                value: 0x1389
262            """))
263            temp_file.flush()
264
265            parser = FSConfigFileParser([temp_file.name], [(5000, 5999)])
266            files = parser.files
267            dirs = parser.dirs
268            aids = parser.aids
269
270            self.assertEqual(len(files), 1)
271            self.assertEqual(len(dirs), 1)
272            self.assertEqual(len(aids), 1)
273
274            aid = aids[0]
275            fcap = files[0]
276            dcap = dirs[0]
277
278            self.assertEqual(fcap,
279                             FSConfig('0777', 'AID_FOO', 'AID_SYSTEM',
280                                      'CAP_BLOCK_SUSPEND',
281                                      '/system/bin/file', temp_file.name))
282
283            self.assertEqual(dcap,
284                             FSConfig('0777', 'AID_FOO', 'AID_SYSTEM', '0',
285                                      '/vendor/path/dir/', temp_file.name))
286
287            self.assertEqual(aid, AID('AID_OEM1', '0x1389', temp_file.name, '/vendor/bin/sh'))
288
289    def test_fs_config_file_parser_bad(self):
290        """Test FSConfig Parser bad input file"""
291
292        with tempfile.NamedTemporaryFile() as temp_file:
293            temp_file.write(
294                textwrap.dedent("""
295                [/system/bin/file]
296                caps: BLOCK_SUSPEND
297            """))
298            temp_file.flush()
299
300            with self.assertRaises(SystemExit):
301                FSConfigFileParser([temp_file.name], [(5000, 5999)])
302
303    def test_fs_config_file_parser_bad_aid_range(self):
304        """Test FSConfig Parser bad aid range value input file"""
305
306        with tempfile.NamedTemporaryFile() as temp_file:
307            temp_file.write(
308                textwrap.dedent("""
309                [AID_OEM1]
310                value: 25
311            """))
312            temp_file.flush()
313
314            with self.assertRaises(SystemExit):
315                FSConfigFileParser([temp_file.name], [(5000, 5999)])
316