• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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