1#!/usr/bin/env python3 2# 3# Copyright (C) 2020 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 collections 19import logging 20import os 21import posixpath as target_path_module 22import re 23import shutil 24import sys 25import tempfile 26import unittest 27 28from vts.testcases.vndk import utils 29from vts.testcases.vndk.golden import vndk_data 30from vts.utils.python.library import elf_parser 31from vts.utils.python.vndk import vndk_utils 32 33 34class VtsVndkDependencyTest(unittest.TestCase): 35 """A test case to verify vendor library dependency. 36 37 Attributes: 38 _dut: The AndroidDevice under test. 39 _temp_dir: The temporary directory to which the odm and vendor 40 partitions are copied. 41 _ll_ndk: Set of strings. The names of low-level NDK libraries in 42 /system/lib[64]. 43 _sp_hal: List of patterns. The names of the same-process HAL libraries 44 expected to be in /vendor/lib[64]. 45 _vndk: Set of strings. The names of VNDK-core libraries. 46 _vndk_sp: Set of strings. The names of VNDK-SP libraries. 47 _SP_HAL_LINK_PATHS: Format strings of same-process HAL's default link 48 paths. 49 _VENDOR_LINK_PATHS: Format strings of vendor processes' default link 50 paths. 51 _VENDOR_PERMITTED_PATHS: Same-process HAL and vendor processes' 52 permitted link paths. 53 _VENDOR_APP_DIRS: The app directories in vendor partitions. 54 """ 55 _TARGET_DIR_SEP = "/" 56 _TARGET_ROOT_DIR = "/" 57 _TARGET_ODM_DIR = "/odm" 58 _TARGET_VENDOR_DIR = "/vendor" 59 60 _SP_HAL_LINK_PATHS = [ 61 "/odm/{LIB}/egl", "/odm/{LIB}/hw", "/odm/{LIB}", 62 "/vendor/{LIB}/egl", "/vendor/{LIB}/hw", "/vendor/{LIB}" 63 ] 64 _VENDOR_LINK_PATHS = [ 65 "/odm/{LIB}/hw", "/odm/{LIB}/egl", "/odm/{LIB}", 66 "/vendor/{LIB}/hw", "/vendor/{LIB}/egl", "/vendor/{LIB}" 67 ] 68 _VENDOR_PERMITTED_PATHS = [ 69 "/odm", "/vendor" 70 ] 71 _VENDOR_APP_DIRS = [ 72 "/vendor/app", "/vendor/priv-app", "/odm/app", "/odm/priv-app" 73 ] 74 _DEFAULT_PROGRAM_INTERPRETERS = [ 75 "/system/bin/linker", "/system/bin/linker64" 76 ] 77 78 class ElfObject(object): 79 """Contains dependencies of an ELF file on target device. 80 81 Attributes: 82 target_path: String. The path to the ELF file on target. 83 name: String. File name of the ELF. 84 target_dir: String. The directory containing the ELF file on 85 target. 86 bitness: Integer. Bitness of the ELF. 87 deps: List of strings. The names of the depended libraries. 88 runpaths: List of strings. The library search paths. 89 custom_link_paths: List of strings. The library search paths. 90 """ 91 92 def __init__(self, target_path, bitness, deps, runpaths, 93 custom_link_paths): 94 self.target_path = target_path 95 self.name = target_path_module.basename(target_path) 96 self.target_dir = target_path_module.dirname(target_path) 97 self.bitness = bitness 98 self.deps = deps 99 # Format runpaths 100 self.runpaths = [] 101 lib_dir_name = "lib64" if bitness == 64 else "lib" 102 for runpath in runpaths: 103 path = runpath.replace("${LIB}", lib_dir_name) 104 path = path.replace("$LIB", lib_dir_name) 105 path = path.replace("${ORIGIN}", self.target_dir) 106 path = path.replace("$ORIGIN", self.target_dir) 107 self.runpaths.append(path) 108 self.custom_link_paths = custom_link_paths 109 110 def setUp(self): 111 """Initializes device, temporary directory, and VNDK lists.""" 112 serial_number = os.environ.get("ANDROID_SERIAL") 113 self.assertTrue(serial_number, "$ANDROID_SERIAL is empty.") 114 self._dut = utils.AndroidDevice(serial_number) 115 self.assertTrue(self._dut.IsRoot(), "This test requires adb root.") 116 117 self._temp_dir = tempfile.mkdtemp() 118 for target_dir in (self._TARGET_ODM_DIR, self._TARGET_VENDOR_DIR): 119 if self._dut.IsDirectory(target_dir): 120 logging.info("adb pull %s %s", target_dir, self._temp_dir) 121 self._dut.AdbPull(target_dir, self._temp_dir) 122 else: 123 logging.info("Skip adb pull %s", target_dir) 124 125 vndk_lists = vndk_data.LoadVndkLibraryListsFromResources( 126 self._dut.GetVndkVersion(), 127 vndk_data.SP_HAL, 128 vndk_data.LL_NDK, 129 vndk_data.VNDK, 130 vndk_data.VNDK_SP) 131 self.assertTrue(vndk_lists, "Cannot load VNDK library lists.") 132 133 sp_hal_strings = vndk_lists[0] 134 self._sp_hal = [re.compile(x) for x in sp_hal_strings] 135 (self._ll_ndk, self._vndk, self._vndk_sp) = vndk_lists[1:] 136 137 logging.debug("LL_NDK: %s", self._ll_ndk) 138 logging.debug("SP_HAL: %s", sp_hal_strings) 139 logging.debug("VNDK: %s", self._vndk) 140 logging.debug("VNDK_SP: %s", self._vndk_sp) 141 142 def tearDown(self): 143 """Deletes the temporary directory.""" 144 logging.info("Delete %s", self._temp_dir) 145 shutil.rmtree(self._temp_dir, ignore_errors=True) 146 147 def _IsElfObjectForAp(self, elf, target_path, abi_list): 148 """Checks whether an ELF object is for application processor. 149 150 Args: 151 elf: The object of elf_parser.ElfParser. 152 target_path: The path to the ELF file on target. 153 abi_list: A list of strings, the ABIs of the application processor. 154 155 Returns: 156 A boolean, whether the ELF object is for application processor. 157 """ 158 if not any(elf.MatchCpuAbi(x) for x in abi_list): 159 logging.debug("%s does not match the ABI", target_path) 160 return False 161 162 # b/115567177 Skip an ELF file if it meets the following 3 conditions: 163 # The ELF type is executable. 164 if not elf.IsExecutable(): 165 return True 166 167 # It requires special program interpreter. 168 interp = elf.GetProgramInterpreter() 169 if not interp or interp in self._DEFAULT_PROGRAM_INTERPRETERS: 170 return True 171 172 # It does not have execute permission in the file system. 173 if self._dut.IsExecutable(target_path): 174 return True 175 176 return False 177 178 def _IsElfObjectBuiltForAndroid(self, elf, target_path): 179 """Checks whether an ELF object is built for Android. 180 181 Some ELF objects in vendor partition require special program 182 interpreters. Such executable files have .interp sections, but shared 183 libraries don't. As there is no reliable way to identify those 184 libraries. This method checks .note.android.ident section which is 185 created by Android build system. 186 187 Args: 188 elf: The object of elf_parser.ElfParser. 189 target_path: The path to the ELF file on target. 190 191 Returns: 192 A boolean, whether the ELF object is built for Android. 193 """ 194 # b/133399940 Skip an ELF file if it does not have .note.android.ident 195 # section and meets one of the following conditions: 196 if elf.HasAndroidIdent(): 197 return True 198 199 # It's in the specific directory and is a shared library. 200 if (target_path.startswith("/vendor/arib/lib/") and 201 ".so" in target_path and 202 elf.IsSharedObject()): 203 return False 204 205 # It's in the specific directory, requires special program interpreter, 206 # and is executable. 207 if target_path.startswith("/vendor/arib/bin/"): 208 interp = elf.GetProgramInterpreter() 209 if interp and interp not in self._DEFAULT_PROGRAM_INTERPRETERS: 210 if elf.IsExecutable() or self._dut.IsExecutable(target_path): 211 return False 212 213 return True 214 215 @staticmethod 216 def _IterateFiles(host_dir): 217 """Iterates files in a host directory. 218 219 Args: 220 host_dir: The host directory. 221 222 Yields: 223 The file paths under the directory. 224 """ 225 for root_dir, dir_names, file_names in os.walk(host_dir): 226 for file_name in file_names: 227 yield os.path.join(root_dir, file_name) 228 229 def _LoadElfObjects(self, host_dir, target_dir, abi_list, 230 elf_error_handler): 231 """Scans a host directory recursively and loads all ELF files in it. 232 233 Args: 234 host_dir: The host directory to scan. 235 target_dir: The path from which host_dir is copied. 236 abi_list: A list of strings, the ABIs of the ELF files to load. 237 elf_error_handler: A function that takes 2 arguments 238 (target_path, exception). It is called when 239 the parser fails to read an ELF file. 240 241 Returns: 242 List of ElfObject. 243 """ 244 objs = [] 245 for full_path in self._IterateFiles(host_dir): 246 rel_path = os.path.relpath(full_path, host_dir) 247 target_path = target_path_module.join( 248 target_dir, *rel_path.split(os.path.sep)) 249 try: 250 elf = elf_parser.ElfParser(full_path) 251 except elf_parser.ElfError: 252 logging.debug("%s is not an ELF file", target_path) 253 continue 254 try: 255 if not self._IsElfObjectForAp(elf, target_path, abi_list): 256 logging.info("%s is not for application processor", 257 target_path) 258 continue 259 if not self._IsElfObjectBuiltForAndroid(elf, target_path): 260 logging.warning("%s is not built for Android, which is no " 261 "longer exempted.", target_path) 262 263 deps, runpaths = elf.ListDependencies() 264 except elf_parser.ElfError as e: 265 elf_error_handler(target_path, e) 266 continue 267 finally: 268 elf.Close() 269 270 logging.info("%s depends on: %s", target_path, ", ".join(deps)) 271 if runpaths: 272 logging.info("%s has runpaths: %s", 273 target_path, ":".join(runpaths)) 274 275 # b/123216664 App libraries depend on those in the same directory. 276 custom_link_paths = [] 277 if any(target_path.startswith(app_dir + self._TARGET_DIR_SEP) for 278 app_dir in self._VENDOR_APP_DIRS): 279 custom_link_paths.append( 280 target_path_module.dirname(target_path)) 281 282 objs.append(self.ElfObject(target_path, elf.bitness, deps, 283 runpaths, custom_link_paths)) 284 return objs 285 286 def _FindLibsInLinkPaths(self, bitness, link_paths, objs): 287 """Finds libraries in link paths. 288 289 Args: 290 bitness: 32 or 64, the bitness of the returned libraries. 291 link_paths: List of strings, the default link paths. 292 objs: List of ElfObject, the libraries/executables to be filtered 293 by bitness and path. 294 295 Returns: 296 A defaultdict, {dir: {name: obj}} where obj is an ElfObject, dir 297 is obj.target_dir, and name is obj.name. 298 """ 299 namespace = collections.defaultdict(dict) 300 for obj in objs: 301 if (obj.bitness == bitness and 302 any(obj.target_path.startswith(link_path + 303 self._TARGET_DIR_SEP) 304 for link_path in link_paths)): 305 namespace[obj.target_dir][obj.name] = obj 306 return namespace 307 308 def _DfsDependencies(self, lib, searched, namespace, link_paths): 309 """Depth-first-search for library dependencies. 310 311 Args: 312 lib: ElfObject, the library to search for dependencies. 313 searched: The set of searched libraries. 314 namespace: Defaultdict, {dir: {name: obj}} containing all 315 searchable libraries. 316 link_paths: List of strings, the default link paths. 317 """ 318 if lib in searched: 319 return 320 searched.add(lib) 321 for dep_name in lib.deps: 322 for link_path in lib.custom_link_paths + lib.runpaths + link_paths: 323 if dep_name in namespace[link_path]: 324 self._DfsDependencies(namespace[link_path][dep_name], 325 searched, namespace, link_paths) 326 break 327 328 def _FindDisallowedDependencies(self, objs, namespace, link_paths, 329 *vndk_lists): 330 """Tests if libraries/executables have disallowed dependencies. 331 332 Args: 333 objs: Collection of ElfObject, the libraries/executables under 334 test. 335 namespace: Defaultdict, {dir: {name: obj}} containing all libraries 336 in the linker namespace. 337 link_paths: List of strings, the default link paths. 338 vndk_lists: Collections of library names in VNDK, VNDK-SP, etc. 339 340 Returns: 341 List of tuples (path, disallowed_dependencies). 342 """ 343 dep_errors = [] 344 for obj in objs: 345 disallowed_libs = [] 346 for dep_name in obj.deps: 347 if any((dep_name in vndk_list) for vndk_list in vndk_lists): 348 continue 349 if any((dep_name in namespace[link_path]) for link_path in 350 obj.custom_link_paths + obj.runpaths + link_paths): 351 continue 352 disallowed_libs.append(dep_name) 353 354 if disallowed_libs: 355 dep_errors.append((obj.target_path, disallowed_libs)) 356 return dep_errors 357 358 def _TestElfDependency(self, bitness, objs): 359 """Tests vendor libraries/executables and SP-HAL dependencies. 360 361 Args: 362 bitness: 32 or 64, the bitness of the vendor libraries. 363 objs: List of ElfObject. The libraries/executables in odm and 364 vendor partitions. 365 366 Returns: 367 List of tuples (path, disallowed_dependencies). 368 """ 369 vndk_sp_ext_dirs = vndk_utils.GetVndkSpExtDirectories(bitness) 370 371 vendor_link_paths = [vndk_utils.FormatVndkPath(x, bitness) for 372 x in self._VENDOR_LINK_PATHS] 373 vendor_namespace = self._FindLibsInLinkPaths( 374 bitness, self._VENDOR_PERMITTED_PATHS, objs) 375 # Exclude VNDK and VNDK-SP extensions from vendor libraries. 376 for vndk_ext_dir in (vndk_utils.GetVndkExtDirectories(bitness) + 377 vndk_utils.GetVndkSpExtDirectories(bitness)): 378 vendor_namespace.pop(vndk_ext_dir, None) 379 logging.info("%d-bit odm, vendor, and SP-HAL libraries:", bitness) 380 for dir_path, libs in vendor_namespace.items(): 381 logging.info("%s: %s", dir_path, ",".join(libs.keys())) 382 383 sp_hal_link_paths = [vndk_utils.FormatVndkPath(x, bitness) for 384 x in self._SP_HAL_LINK_PATHS] 385 sp_hal_namespace = self._FindLibsInLinkPaths( 386 bitness, self._VENDOR_PERMITTED_PATHS, objs) 387 388 # Find same-process HAL and dependencies 389 sp_hal_libs = set() 390 for link_path in sp_hal_link_paths: 391 for obj in sp_hal_namespace[link_path].values(): 392 if any(x.match(obj.target_path) for x in self._sp_hal): 393 self._DfsDependencies(obj, sp_hal_libs, sp_hal_namespace, 394 sp_hal_link_paths) 395 logging.info("%d-bit SP-HAL libraries: %s", 396 bitness, ", ".join(x.name for x in sp_hal_libs)) 397 398 # Find VNDK-SP extension libraries and their dependencies. 399 vndk_sp_ext_libs = set(obj for obj in objs if 400 obj.bitness == bitness and 401 obj.target_dir in vndk_sp_ext_dirs) 402 vndk_sp_ext_deps = set() 403 for lib in vndk_sp_ext_libs: 404 self._DfsDependencies(lib, vndk_sp_ext_deps, sp_hal_namespace, 405 sp_hal_link_paths) 406 logging.info("%d-bit VNDK-SP extension libraries and dependencies: %s", 407 bitness, ", ".join(x.name for x in vndk_sp_ext_deps)) 408 409 # A vendor library/executable is allowed to depend on 410 # LL-NDK 411 # VNDK 412 # VNDK-SP 413 # Other libraries in vendor link paths 414 vendor_objs = {obj for obj in objs if 415 obj.bitness == bitness and 416 obj not in sp_hal_libs and 417 obj not in vndk_sp_ext_deps} 418 dep_errors = self._FindDisallowedDependencies( 419 vendor_objs, vendor_namespace, vendor_link_paths, 420 self._ll_ndk, self._vndk, self._vndk_sp) 421 422 # A VNDK-SP extension library/dependency is allowed to depend on 423 # LL-NDK 424 # VNDK-SP 425 # Libraries in vendor link paths 426 # Other VNDK-SP extension libraries, which is a subset of VNDK-SP 427 # 428 # However, it is not allowed to indirectly depend on VNDK. i.e., the 429 # depended vendor libraries must not depend on VNDK. 430 # 431 # vndk_sp_ext_deps and sp_hal_libs may overlap. Their dependency 432 # restrictions are the same. 433 dep_errors.extend(self._FindDisallowedDependencies( 434 vndk_sp_ext_deps - sp_hal_libs, vendor_namespace, 435 vendor_link_paths, self._ll_ndk, self._vndk_sp)) 436 437 if not vndk_utils.IsVndkRuntimeEnforced(self._dut): 438 logging.warning("Ignore dependency errors: %s", dep_errors) 439 dep_errors = [] 440 441 # A same-process HAL library is allowed to depend on 442 # LL-NDK 443 # VNDK-SP 444 # Other same-process HAL libraries and dependencies 445 dep_errors.extend(self._FindDisallowedDependencies( 446 sp_hal_libs, sp_hal_namespace, sp_hal_link_paths, 447 self._ll_ndk, self._vndk_sp)) 448 return dep_errors 449 450 def testElfDependency(self): 451 """Tests vendor libraries/executables and SP-HAL dependencies.""" 452 read_errors = [] 453 abi_list = self._dut.GetCpuAbiList() 454 objs = self._LoadElfObjects( 455 self._temp_dir, self._TARGET_ROOT_DIR, abi_list, 456 lambda p, e: read_errors.append((p, str(e)))) 457 458 dep_errors = self._TestElfDependency(32, objs) 459 if self._dut.GetCpuAbiList(64): 460 dep_errors.extend(self._TestElfDependency(64, objs)) 461 462 assert_lines = [] 463 if read_errors: 464 error_lines = ["%s: %s" % (x[0], x[1]) for x in read_errors] 465 logging.error("%d read errors:\n%s", 466 len(read_errors), "\n".join(error_lines)) 467 assert_lines.extend(error_lines[:20]) 468 469 if dep_errors: 470 error_lines = ["%s: %s" % (x[0], ", ".join(x[1])) 471 for x in dep_errors] 472 logging.error("%d disallowed dependencies:\n%s", 473 len(dep_errors), "\n".join(error_lines)) 474 assert_lines.extend(error_lines[:20]) 475 476 error_count = len(read_errors) + len(dep_errors) 477 if error_count: 478 if error_count > len(assert_lines): 479 assert_lines.append("...") 480 assert_lines.append("Total number of errors: " + str(error_count)) 481 self.fail("\n".join(assert_lines)) 482 483 484if __name__ == "__main__": 485 # The logs are written to stdout so that TradeFed test runner can parse the 486 # results from stderr. 487 logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) 488 # Setting verbosity is required to generate output that the TradeFed test 489 # runner can parse. 490 unittest.main(verbosity=3) 491