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