1#!/usr/bin/env python
2#
3# Copyright (C) 2018 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18import logging
19import os
20import shutil
21import tempfile
22
23from vts.runners.host import asserts
24from vts.runners.host import base_test
25from vts.runners.host import const
26from vts.runners.host import keys
27from vts.runners.host import test_runner
28from vts.utils.python.android import api
29from vts.utils.python.file import target_file_utils
30
31
32class VtsTrebleSysPropTest(base_test.BaseTestClass):
33    """Test case which check compatibility of system property.
34
35    Attributes:
36        _temp_dir: The temporary directory to which necessary files are copied.
37        _PUBLIC_PROPERTY_CONTEXTS_FILE_PATH:  The path of public property
38                                              contexts file.
39        _SYSTEM_PROPERTY_CONTEXTS_FILE_PATH:  The path of system property
40                                              contexts file.
41        _PRODUCT_PROPERTY_CONTEXTS_FILE_PATH: The path of product property
42                                              contexts file.
43        _VENDOR_PROPERTY_CONTEXTS_FILE_PATH:  The path of vendor property
44                                              contexts file.
45        _ODM_PROPERTY_CONTEXTS_FILE_PATH:     The path of odm property
46                                              contexts file.
47        _VENDOR_OR_ODM_NAMESPACES: The namespaces allowed for vendor/odm
48                                   properties.
49        _VENDOR_OR_ODM_NAMESPACES_WHITELIST: The extra namespaces allowed for
50                                             vendor/odm properties.
51        _VENDOR_TYPE_PREFIX: Expected prefix for the vendor prop types
52        _ODM_TYPE_PREFIX: Expected prefix for the odm prop types
53        _SYSTEM_WHITELISTED_TYPES: System props are not allowed to start with
54            "vendor_", but these are exceptions.
55        _VENDOR_OR_ODM_WHITELISTED_TYPES: vendor/odm props must start with
56            "vendor_" or "odm_", but these are exceptions.
57    """
58
59    _PUBLIC_PROPERTY_CONTEXTS_FILE_PATH  = ("vts/testcases/security/"
60                                            "system_property/data/"
61                                            "property_contexts")
62    _SYSTEM_PROPERTY_CONTEXTS_FILE_PATH  = ("/system/etc/selinux/"
63                                            "plat_property_contexts")
64    _PRODUCT_PROPERTY_CONTEXTS_FILE_PATH = ("/product/etc/selinux/"
65                                            "product_property_contexts")
66    _VENDOR_PROPERTY_CONTEXTS_FILE_PATH  = ("/vendor/etc/selinux/"
67                                            "vendor_property_contexts")
68    _ODM_PROPERTY_CONTEXTS_FILE_PATH     = ("/odm/etc/selinux/"
69                                            "odm_property_contexts")
70    _VENDOR_OR_ODM_NAMESPACES = [
71            "ctl.odm.",
72            "ctl.vendor.",
73            "ctl.start$odm.",
74            "ctl.start$vendor.",
75            "ctl.stop$odm.",
76            "ctl.stop$vendor.",
77            "init.svc.odm.",
78            "init.svc.vendor.",
79            "ro.boot.",
80            "ro.hardware.",
81            "ro.odm.",
82            "ro.vendor.",
83            "odm.",
84            "persist.odm.",
85            "persist.vendor.",
86            "vendor."
87    ]
88
89    _VENDOR_OR_ODM_NAMESPACES_WHITELIST = [
90            "persist.camera." # b/138545066 remove this
91    ]
92
93    _VENDOR_TYPE_PREFIX = "vendor_"
94
95    _ODM_TYPE_PREFIX = "odm_"
96
97    _SYSTEM_WHITELISTED_TYPES = [
98            "vendor_default_prop",
99            "vendor_security_patch_level_prop",
100            "vendor_socket_hook_prop"
101    ]
102
103    _VENDOR_OR_ODM_WHITELISTED_TYPES = [
104    ]
105
106    def setUpClass(self):
107        """Initializes tests.
108
109        Data file path, device, remote shell instance and temporary directory
110        are initialized.
111        """
112        required_params = [keys.ConfigKeys.IKEY_DATA_FILE_PATH]
113        self.getUserParams(required_params)
114        self.dut = self.android_devices[0]
115        self.shell = self.dut.shell
116        self._temp_dir = tempfile.mkdtemp()
117
118    def tearDownClass(self):
119        """Deletes the temporary directory."""
120        logging.info("Delete %s", self._temp_dir)
121        shutil.rmtree(self._temp_dir)
122
123    def _ParsePropertyDictFromPropertyContextsFile(self,
124                                                   property_contexts_file,
125                                                   exact_only=False):
126        """Parse property contexts file to a dictionary.
127
128        Args:
129            property_contexts_file: file object of property contexts file
130            exact_only: whether parsing only properties which require exact
131                        matching
132
133        Returns:
134            dict: {property_name: property_tokens} where property_tokens[1]
135            is selinux type of the property, e.g. u:object_r:my_prop:s0
136        """
137        property_dict = dict()
138        for line in property_contexts_file.readlines():
139            tokens = line.strip().rstrip("\n").split()
140            if len(tokens) > 0 and not tokens[0].startswith("#"):
141                if not exact_only:
142                    property_dict[tokens[0]] = tokens
143                elif len(tokens) >= 4 and tokens[2] == "exact":
144                    property_dict[tokens[0]] = tokens
145
146        return property_dict
147
148    def testActionableCompatiblePropertyEnabled(self):
149        """Ensures the feature of actionable compatible property is enforced.
150
151        ro.actionable_compatible_property.enabled must be true to enforce the
152        feature of actionable compatible property.
153        """
154        asserts.assertEqual(
155            self.dut.getProp("ro.actionable_compatible_property.enabled"),
156            "true", "ro.actionable_compatible_property.enabled must be true")
157
158    def _TestVendorOrOdmPropertyNames(self, partition, contexts_path):
159        logging.info("Checking existence of %s", contexts_path)
160        target_file_utils.assertPermissionsAndExistence(
161            self.shell, contexts_path, target_file_utils.IsReadable)
162
163        # Pull property contexts file from device.
164        self.dut.adb.pull(contexts_path, self._temp_dir)
165        logging.info("Adb pull %s to %s", contexts_path, self._temp_dir)
166
167        with open(
168                os.path.join(self._temp_dir,
169                             "%s_property_contexts" % partition),
170                "r") as property_contexts_file:
171            property_dict = self._ParsePropertyDictFromPropertyContextsFile(
172                property_contexts_file)
173        logging.info("Found %d property names in %s property contexts",
174                     len(property_dict), partition)
175        violation_list = filter(
176            lambda x: not any(
177                x.startswith(prefix) for prefix in
178                self._VENDOR_OR_ODM_NAMESPACES + self._VENDOR_OR_ODM_NAMESPACES_WHITELIST),
179            property_dict.keys())
180        asserts.assertEqual(
181            len(violation_list), 0,
182            ("%s properties (%s) have wrong namespace" %
183             (partition, " ".join(sorted(violation_list)))))
184
185    def _TestPropertyTypes(self, property_contexts_file, check_function):
186        fd, downloaded = tempfile.mkstemp(dir=self._temp_dir)
187        os.close(fd)
188        self.dut.adb.pull(property_contexts_file, downloaded)
189        logging.info("adb pull %s to %s", property_contexts_file, downloaded)
190
191        with open(downloaded, "r") as f:
192            property_dict = self._ParsePropertyDictFromPropertyContextsFile(f)
193        logging.info("Found %d properties from %s",
194                     len(property_dict), property_contexts_file)
195
196        # Filter props that don't satisfy check_function.
197        # tokens[1] is something like u:object_r:my_prop:s0
198        violation_list = [(name, tokens) for name, tokens in
199                          property_dict.items()
200                          if not check_function(tokens[1].split(":")[2])]
201
202        asserts.assertEqual(
203            len(violation_list), 0,
204            "properties in %s have wrong property types:\n%s" % (
205                property_contexts_file,
206                "\n".join("name: %s, type: %s" % (name, tokens[1])
207                          for name, tokens in violation_list))
208        )
209
210    def testVendorPropertyNames(self):
211        """Ensures vendor properties have proper namespace.
212
213        Vendor or ODM properties must have their own prefix.
214        """
215        asserts.skipIf(
216            self.dut.getLaunchApiLevel() <= api.PLATFORM_API_LEVEL_P,
217            "Skip test for a device which launched first before Android Q.")
218
219        self._TestVendorOrOdmPropertyNames(
220            "vendor", self._VENDOR_PROPERTY_CONTEXTS_FILE_PATH)
221
222    def testOdmPropertyNames(self):
223        """Ensures odm properties have proper namespace.
224
225        Vendor or ODM properties must have their own prefix.
226        """
227        asserts.skipIf(
228            self.dut.getLaunchApiLevel() <= api.PLATFORM_API_LEVEL_P,
229            "Skip test for a device which launched first before Android Q.")
230
231        asserts.skipIf(
232            not target_file_utils.Exists(self._ODM_PROPERTY_CONTEXTS_FILE_PATH,
233                                         self.shell),
234            "Skip test for a device which doesn't have an odm property "
235            "contexts.")
236
237        self._TestVendorOrOdmPropertyNames(
238            "odm", self._ODM_PROPERTY_CONTEXTS_FILE_PATH)
239
240    def testProductPropertyNames(self):
241        """Ensures product properties have proper namespace.
242
243        Product properties must not have Vendor or ODM namespaces.
244        """
245        asserts.skipIf(
246            self.dut.getLaunchApiLevel() <= api.PLATFORM_API_LEVEL_P,
247            "Skip test for a device which launched first before Android Q.")
248
249        asserts.skipIf(
250            not target_file_utils.Exists(self._PRODUCT_PROPERTY_CONTEXTS_FILE_PATH,
251                                         self.shell),
252            "Skip test for a device which doesn't have an product property "
253            "contexts.")
254        logging.info("Checking existence of %s",
255                     self._PRODUCT_PROPERTY_CONTEXTS_FILE_PATH)
256        target_file_utils.assertPermissionsAndExistence(
257            self.shell, self._PRODUCT_PROPERTY_CONTEXTS_FILE_PATH,
258            target_file_utils.IsReadable)
259
260        # Pull product property contexts file from device.
261        self.dut.adb.pull(self._PRODUCT_PROPERTY_CONTEXTS_FILE_PATH,
262                          self._temp_dir)
263        logging.info("Adb pull %s to %s",
264                     self._PRODUCT_PROPERTY_CONTEXTS_FILE_PATH, self._temp_dir)
265
266        with open(os.path.join(self._temp_dir, "product_property_contexts"),
267                  "r") as property_contexts_file:
268            property_dict = self._ParsePropertyDictFromPropertyContextsFile(
269                property_contexts_file, True)
270        logging.info(
271            "Found %d property names in product property contexts",
272            len(property_dict))
273
274        violation_list = filter(
275            lambda x: any(
276                x.startswith(prefix)
277                for prefix in self._VENDOR_OR_ODM_NAMESPACES),
278            property_dict.keys())
279        asserts.assertEqual(
280            len(violation_list), 0,
281            ("product propertes (%s) have wrong namespace" %
282             " ".join(sorted(violation_list))))
283
284    def testPlatformPropertyTypes(self):
285        """Ensures properties in the system partition have valid types"""
286
287        asserts.skipIf(
288            self.dut.getLaunchApiLevel() <= api.PLATFORM_API_LEVEL_Q,
289            "Skip test for a device which launched first before Android R.")
290
291        self._TestPropertyTypes(
292            self._SYSTEM_PROPERTY_CONTEXTS_FILE_PATH,
293            lambda typename: (
294                not typename.startswith(self._VENDOR_TYPE_PREFIX) and
295                not typename.startswith(self._ODM_TYPE_PREFIX) and
296                typename not in self._VENDOR_OR_ODM_WHITELISTED_TYPES
297            ) or typename in self._SYSTEM_WHITELISTED_TYPES)
298
299    def testVendorPropertyTypes(self):
300        """Ensures properties in the vendor partion have valid types"""
301
302        asserts.skipIf(
303            self.dut.getLaunchApiLevel() <= api.PLATFORM_API_LEVEL_Q,
304            "Skip test for a device which launched first before Android R.")
305
306        self._TestPropertyTypes(
307            self._VENDOR_PROPERTY_CONTEXTS_FILE_PATH,
308            lambda typename: typename.startswith(self._VENDOR_TYPE_PREFIX) or
309            typename in self._VENDOR_OR_ODM_WHITELISTED_TYPES)
310
311    def testOdmPropertyTypes(self):
312        """Ensures properties in the odm partition have valid types"""
313
314        asserts.skipIf(
315            self.dut.getLaunchApiLevel() <= api.PLATFORM_API_LEVEL_Q,
316            "Skip test for a device which launched first before Android R.")
317
318        asserts.skipIf(
319            not target_file_utils.Exists(self._ODM_PROPERTY_CONTEXTS_FILE_PATH,
320                                         self.shell),
321            "Skip test for a device which doesn't have an odm property "
322            "contexts.")
323
324        self._TestPropertyTypes(
325            self._ODM_PROPERTY_CONTEXTS_FILE_PATH,
326            lambda typename: typename.startswith(self._VENDOR_TYPE_PREFIX) or
327            typename.startswith(self._ODM_TYPE_PREFIX) or
328            typename in self._VENDOR_OR_ODM_WHITELISTED_TYPES)
329
330    def testExportedPlatformPropertyIntegrity(self):
331        """Ensures public property contexts isn't modified at all.
332
333        Public property contexts must not be modified.
334        """
335        logging.info("Checking existence of %s",
336                     self._SYSTEM_PROPERTY_CONTEXTS_FILE_PATH)
337        target_file_utils.assertPermissionsAndExistence(
338            self.shell, self._SYSTEM_PROPERTY_CONTEXTS_FILE_PATH,
339            target_file_utils.IsReadable)
340
341        # Pull system property contexts file from device.
342        self.dut.adb.pull(self._SYSTEM_PROPERTY_CONTEXTS_FILE_PATH,
343                          self._temp_dir)
344        logging.info("Adb pull %s to %s",
345                     self._SYSTEM_PROPERTY_CONTEXTS_FILE_PATH, self._temp_dir)
346
347        with open(os.path.join(self._temp_dir, "plat_property_contexts"),
348                  "r") as property_contexts_file:
349            sys_property_dict = self._ParsePropertyDictFromPropertyContextsFile(
350                property_contexts_file, True)
351        logging.info(
352            "Found %d exact-matching properties "
353            "in system property contexts", len(sys_property_dict))
354
355        pub_property_contexts_file_path = os.path.join(
356            self.data_file_path, self._PUBLIC_PROPERTY_CONTEXTS_FILE_PATH)
357        with open(pub_property_contexts_file_path,
358                  "r") as property_contexts_file:
359            pub_property_dict = self._ParsePropertyDictFromPropertyContextsFile(
360                property_contexts_file, True)
361
362        for name in pub_property_dict:
363            public_tokens = pub_property_dict[name]
364            asserts.assertTrue(name in sys_property_dict,
365                               "Exported property (%s) doesn't exist" % name)
366            system_tokens = sys_property_dict[name]
367            asserts.assertEqual(public_tokens, system_tokens,
368                                "Exported property (%s) is modified" % name)
369
370
371if __name__ == "__main__":
372    test_runner.main()
373