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