From 2d354379a0ed78bf0a777bde65025557df077377 Mon Sep 17 00:00:00 2001 From: Hsin-Yi Chen Date: Mon, 2 Mar 2020 17:40:19 +0800 Subject: Add vts_vndk_open_libraries_test This commit copies VtsVndkOpenLibrariesTest.py to vts_vndk_open_libraries_test.py, removes the dependency on VTS framework, and adds a python_test_host module for the new file. Bug: 147454897 Test: ANDROID_SERIAL=1234 \ LD_LIBRARY_PATH=$ANDROID_HOST_OUT/lib64 \ $ANDROID_HOST_OUT/nativetest64/vts_vndk_open_libraries_test/vts_vndk_open_libraries_test \ --verbose Change-Id: I0f35d8322045cedc0f1e43b3fb8eb7367c8e39b1 Merged-In: I0f35d8322045cedc0f1e43b3fb8eb7367c8e39b1 (cherry picked from commit 1d1c640b24f9ec493e3094197f3e4cf79a0ddf79) --- Android.bp | 13 ++ open_libraries/vts_vndk_open_libraries_test.py | 162 ++++++++++++++++++++++++ open_libraries/vts_vndk_open_libraries_test.xml | 23 ++++ utils.py | 17 ++- 4 files changed, 206 insertions(+), 9 deletions(-) create mode 100644 open_libraries/vts_vndk_open_libraries_test.py create mode 100644 open_libraries/vts_vndk_open_libraries_test.xml diff --git a/Android.bp b/Android.bp index 9eae205..176da18 100644 --- a/Android.bp +++ b/Android.bp @@ -75,3 +75,16 @@ python_test_host { ], test_config: "files/vts_vndk_files_test.xml", } + +python_test_host { + name: "vts_vndk_open_libraries_test", + defaults: ["vts_vndk_default"], + main: "open_libraries/vts_vndk_open_libraries_test.py", + srcs: [ + "open_libraries/vts_vndk_open_libraries_test.py", + ], + test_suites: [ + "vts-core", + ], + test_config: "open_libraries/vts_vndk_open_libraries_test.xml", +} diff --git a/open_libraries/vts_vndk_open_libraries_test.py b/open_libraries/vts_vndk_open_libraries_test.py new file mode 100644 index 0000000..aac1b66 --- /dev/null +++ b/open_libraries/vts_vndk_open_libraries_test.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2020 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import logging +import os +import posixpath as target_path_module +import re +import unittest + +from vts.testcases.vndk import utils +from vts.testcases.vndk.golden import vndk_data +from vts.utils.python.vndk import vndk_utils + + +class VtsVndkOpenLibrariesTest(unittest.TestCase): + """A test module to verify libraries opened by running processes. + + Attributes: + _dut: The AndroidDevice under test. + """ + + def setUp(self): + """Initializes attributes.""" + serial_number = os.environ.get("ANDROID_SERIAL") + self.assertTrue(serial_number, "$ANDROID_SERIAL is empty.") + self._dut = utils.AndroidDevice(serial_number) + + def _ListProcessCommands(self, cmd_filter): + """Finds current processes whose commands match the filter. + + Args: + cmd_filter: A function that takes a binary file path as argument + and returns whether the path matches the condition. + + Returns: + A dict of {pid: command} where pid and command are strings. + """ + ps_cmd = ["ps", "-Aw", "-o", "PID,COMMAND"] + out, err, return_code = self._dut.Execute(*ps_cmd) + if err.strip(): + logging.info("`%s` stderr: %s", " ".join(ps_cmd), err) + self.assertEqual(return_code, 0) + + lines = out.split("\n") + pid_end = lines[0].index("PID") + len("PID") + cmd_begin = lines[0].index("COMMAND", pid_end) + cmds = {} + for line in lines[1:]: + cmd = line[cmd_begin:] + if not cmd_filter(cmd): + continue + pid = line[:pid_end].lstrip() + cmds[pid] = cmd + return cmds + + def _ListOpenFiles(self, pids, file_filter): + """Finds open files whose names match the filter. + + Args: + pids: A collection of strings, the PIDs to list open files. + file_filter: A function that takes a file path as argument and + returns whether the path matches the condition. + + Returns: + A dict of {pid: [file, ...]} where pid and file are strings. + """ + lsof_cmd = ["lsof", "-p", ",".join(pids)] + out, err, return_code = self._dut.Execute(*lsof_cmd) + if err.strip(): + logging.info("`%s` stderr: %s", " ".join(lsof), err) + self.assertEqual(return_code, 0) + # The first line consists of the column names: + # COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME + # PID is right-justified. NAME is left-justified. + lines = out.split("\n") + pid_end = lines[0].index("PID") + len("PID") + name_begin = lines[0].index("NAME") + files = {} + for line in lines[1:]: + if not line.strip(): + continue + # On Android, COMMAND may exceed the column and causes the right + # columns to be misaligned. This program looks for digits in the + # PID column or on the right of the column. + try: + match_pid = next(match for match in + re.finditer(r"\s(\d+)\s", line) if + match.end(1) >= pid_end) + except StopIteration: + self.fail("Cannot parse PID from lsof output: " + line) + offset = match_pid.end(1) - pid_end + self.assertEqual(line[name_begin + offset - 1], " ", + "Cannot parse NAME from lsof output: " + line) + name = line[name_begin + offset:] + if not file_filter(name): + continue + pid = match_pid.group(1) + if pid in files: + files[pid].append(name) + else: + files[pid] = [name] + return files + + def testVendorProcessOpenLibraries(self): + """Checks if vendor processes load shared libraries on system.""" + if not vndk_utils.IsVndkRuntimeEnforced(self._dut): + logging.info("Skip the test as VNDK runtime is not enforced on " + "the device.") + return + vndk_lists = vndk_data.LoadVndkLibraryListsFromResources( + self._dut.GetVndkVersion(), + vndk_data.LL_NDK, + vndk_data.LL_NDK_PRIVATE, + vndk_data.VNDK, + vndk_data.VNDK_PRIVATE, + vndk_data.VNDK_SP, + vndk_data.VNDK_SP_PRIVATE) + self.assertTrue(vndk_lists, "Cannot load VNDK library lists.") + allowed_libs = set().union(*vndk_lists) + logging.debug("Allowed system libraries: %s", allowed_libs) + + self.assertTrue(self._dut.IsRoot(), + "Must be root to find all libraries in use.") + cmds = self._ListProcessCommands(lambda x: (x.startswith("/odm/") or + x.startswith("/vendor/"))) + + def _IsDisallowedSystemLib(lib_path): + return ((lib_path.startswith("/system/") or + lib_path.startswith("/apex/")) and + lib_path.endswith(".so") and + target_path_module.basename(lib_path) not in allowed_libs) + + deps = self._ListOpenFiles(cmds.keys(), _IsDisallowedSystemLib) + if deps: + error_lines = ["%s %s %s" % (pid, cmds[pid], libs) + for pid, libs in deps.items()] + logging.error("pid command libraries\n%s", "\n".join(error_lines)) + + assert_lines = ["pid command libraries"] + error_lines[:20] + if len(deps) > 20: + assert_lines.append("...") + assert_lines.append("Number of vendor processes using system " + "libraries: " + str(len(deps))) + self.fail("\n".join(assert_lines)) + + +if __name__ == "__main__": + unittest.main() diff --git a/open_libraries/vts_vndk_open_libraries_test.xml b/open_libraries/vts_vndk_open_libraries_test.xml new file mode 100644 index 0000000..664f60e --- /dev/null +++ b/open_libraries/vts_vndk_open_libraries_test.xml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/utils.py b/utils.py index 0592bfa..38efdce 100644 --- a/utils.py +++ b/utils.py @@ -32,7 +32,7 @@ class AndroidDevice(object): subprocess.check_call(cmd, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - def _ExecuteCommand(self, *args): + def Execute(self, *args): """Executes a command. Args: @@ -66,7 +66,7 @@ class AndroidDevice(object): Raises: IOError if the command fails. """ - out, err, return_code = self._ExecuteCommand("getprop", name) + out, err, return_code = self.Execute("getprop", name) if err.strip() or return_code != 0: raise IOError("`getprop %s` stdout: %s\nstderr: %s" % (name, out, err)) @@ -143,14 +143,14 @@ class AndroidDevice(object): def IsRoot(self): """Returns whether adb has root privilege on the device.""" - out, err, return_code = self._ExecuteCommand("id") + out, err, return_code = self.Execute("id") if err.strip() or return_code != 0: raise IOError("`id` stdout: %s\nstderr: %s \n" % (out, err)) return "uid=0(root)" in out.strip() def _Test(self, *args): """Tests file types and status.""" - out, err, return_code = self._ExecuteCommand("test", *args) + out, err, return_code = self.Execute("test", *args) if out.strip() or err.strip(): raise IOError("`test` args: %s\nstdout: %s\nstderr: %s" % (args, out, err)) @@ -166,8 +166,7 @@ class AndroidDevice(object): def _Stat(self, fmt, path): """Executes stat command.""" - out, err, return_code = self._ExecuteCommand("stat", "--format", fmt, - path) + out, err, return_code = self.Execute("stat", "--format", fmt, path) if return_code != 0 or err.strip(): raise IOError("`stat --format %s %s` stdout: %s\nstderr: %s" % (fmt, path, out, err)) @@ -194,9 +193,9 @@ class AndroidDevice(object): """ if '"' in name_pattern or "'" in name_pattern: raise ValueError("File name pattern contains quotes.") - out, err, return_code = self._ExecuteCommand("find", path, "-name", - "'" + name_pattern + "'", - *options) + out, err, return_code = self.Execute("find", path, "-name", + "'" + name_pattern + "'", + *options) if return_code != 0 or err.strip(): raise IOError("`find %s -name '%s' %s` stdout: %s\nstderr: %s" % (path, name_pattern, " ".join(options), out, err)) -- cgit v1.2.3