From b8d52a2fdcefc564b10229c22ba04b05c80eabac Mon Sep 17 00:00:00 2001 From: Daniel Norman Date: Mon, 26 Oct 2020 17:55:00 -0700 Subject: [PATCH] Finds APK shared UID violations when merging target files. This involved moving the find-shareduid-violation.py script to releasetools to simplify the cross-tool usage. This new location aligns this script with other similar python host tools. In a future change this violation file will be used to check for shared UID violations across the input build partition boundary. Bug: 171431774 Test: test_merge_target_files Test: Use merge_target_files.py to merge two partial builds, observe shared UID violations file contents in the result. Test: m dist out/dist/shareduid_violation_modules.json (Checking that existing behavior in core/tasks is presereved) Change-Id: I7deecbe019379c71bfdbedce56edac55e7b27b41 --- core/tasks/find-shareduid-violation.mk | 6 +- core/tasks/find-shareduid-violation.py | 99 ---------- tools/releasetools/Android.bp | 28 +++ .../releasetools/find_shareduid_violation.py | 175 ++++++++++++++++++ tools/releasetools/merge_target_files.py | 16 ++ 5 files changed, 221 insertions(+), 103 deletions(-) delete mode 100755 core/tasks/find-shareduid-violation.py create mode 100755 tools/releasetools/find_shareduid_violation.py diff --git a/core/tasks/find-shareduid-violation.mk b/core/tasks/find-shareduid-violation.mk index 972b1ec37..d6885ebc7 100644 --- a/core/tasks/find-shareduid-violation.mk +++ b/core/tasks/find-shareduid-violation.mk @@ -16,8 +16,6 @@ shareduid_violation_modules_filename := $(PRODUCT_OUT)/shareduid_violation_modules.json -find_shareduid_script := $(BUILD_SYSTEM)/tasks/find-shareduid-violation.py - $(shareduid_violation_modules_filename): $(INSTALLED_SYSTEMIMAGE_TARGET) \ $(INSTALLED_RAMDISK_TARGET) \ $(INSTALLED_BOOTIMAGE_TARGET) \ @@ -26,9 +24,9 @@ $(shareduid_violation_modules_filename): $(INSTALLED_SYSTEMIMAGE_TARGET) \ $(INSTALLED_PRODUCTIMAGE_TARGET) \ $(INSTALLED_SYSTEM_EXTIMAGE_TARGET) -$(shareduid_violation_modules_filename): $(find_shareduid_script) +$(shareduid_violation_modules_filename): $(HOST_OUT_EXECUTABLES)/find_shareduid_violation $(shareduid_violation_modules_filename): $(AAPT2) - $(find_shareduid_script) \ + $(HOST_OUT_EXECUTABLES)/find_shareduid_violation \ --product_out $(PRODUCT_OUT) \ --aapt $(AAPT2) \ --copy_out_system $(TARGET_COPY_OUT_SYSTEM) \ diff --git a/core/tasks/find-shareduid-violation.py b/core/tasks/find-shareduid-violation.py deleted file mode 100755 index 8dba5a1d7..000000000 --- a/core/tasks/find-shareduid-violation.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2019 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 argparse -import json -import os -import subprocess -import sys - -from collections import defaultdict -from glob import glob - -def parse_args(): - """Parse commandline arguments.""" - parser = argparse.ArgumentParser(description='Find sharedUserId violators') - parser.add_argument('--product_out', help='PRODUCT_OUT directory', - default=os.environ.get("PRODUCT_OUT")) - parser.add_argument('--aapt', help='Path to aapt or aapt2', - default="aapt2") - parser.add_argument('--copy_out_system', help='TARGET_COPY_OUT_SYSTEM', - default="system") - parser.add_argument('--copy_out_vendor', help='TARGET_COPY_OUT_VENDOR', - default="vendor") - parser.add_argument('--copy_out_product', help='TARGET_COPY_OUT_PRODUCT', - default="product") - parser.add_argument('--copy_out_system_ext', help='TARGET_COPY_OUT_SYSTEM_EXT', - default="system_ext") - return parser.parse_args() - -def execute(cmd): - p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - out, err = map(lambda b: b.decode('utf-8'), p.communicate()) - return p.returncode == 0, out, err - -def make_aapt_cmds(file): - return [aapt + ' dump ' + file + ' --file AndroidManifest.xml', - aapt + ' dump xmltree ' + file + ' --file AndroidManifest.xml'] - -def extract_shared_uid(file): - for cmd in make_aapt_cmds(file): - success, manifest, error_msg = execute(cmd) - if success: - break - else: - print(error_msg, file=sys.stderr) - sys.exit() - - for l in manifest.split('\n'): - if "sharedUserId" in l: - return l.split('"')[-2] - return None - - -args = parse_args() - -product_out = args.product_out -aapt = args.aapt - -partitions = ( - ("system", args.copy_out_system), - ("vendor", args.copy_out_vendor), - ("product", args.copy_out_product), - ("system_ext", args.copy_out_system_ext), -) - -shareduid_app_dict = defaultdict(list) - -for part, location in partitions: - for f in glob(os.path.join(product_out, location, "*", "*", "*.apk")): - apk_file = os.path.basename(f) - shared_uid = extract_shared_uid(f) - - if shared_uid is None: - continue - shareduid_app_dict[shared_uid].append((part, apk_file)) - - -output = defaultdict(lambda: defaultdict(list)) - -for uid, app_infos in shareduid_app_dict.items(): - partitions = {p for p, _ in app_infos} - if len(partitions) > 1: - for part in partitions: - output[uid][part].extend([a for p, a in app_infos if p == part]) - -print(json.dumps(output, indent=2, sort_keys=True)) diff --git a/tools/releasetools/Android.bp b/tools/releasetools/Android.bp index e1543e7d1..b12a77af1 100644 --- a/tools/releasetools/Android.bp +++ b/tools/releasetools/Android.bp @@ -368,6 +368,32 @@ python_binary_host { ], } +python_defaults { + name: "releasetools_find_shareduid_violation_defaults", + srcs: [ + "find_shareduid_violation.py", + ], + libs: [ + "releasetools_common", + ], +} + +python_binary_host { + name: "find_shareduid_violation", + defaults: [ + "releasetools_binary_defaults", + "releasetools_find_shareduid_violation_defaults", + ], +} + +python_library_host { + name: "releasetools_find_shareduid_violation", + defaults: [ + "releasetools_find_shareduid_violation_defaults", + "releasetools_library_defaults", + ], +} + python_binary_host { name: "make_recovery_patch", defaults: ["releasetools_binary_defaults"], @@ -402,6 +428,7 @@ python_binary_host { "releasetools_build_super_image", "releasetools_check_target_files_vintf", "releasetools_common", + "releasetools_find_shareduid_violation", "releasetools_img_from_target_files", "releasetools_ota_from_target_files", ], @@ -504,6 +531,7 @@ python_defaults { "releasetools_build_super_image", "releasetools_check_target_files_vintf", "releasetools_common", + "releasetools_find_shareduid_violation", "releasetools_img_from_target_files", "releasetools_ota_from_target_files", "releasetools_verity_utils", diff --git a/tools/releasetools/find_shareduid_violation.py b/tools/releasetools/find_shareduid_violation.py new file mode 100755 index 000000000..35acde349 --- /dev/null +++ b/tools/releasetools/find_shareduid_violation.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python +# +# Copyright (C) 2019 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. +# +"""Find APK sharedUserId violators. + +Usage: find_shareduid_violation [args] + + --product_out + PRODUCT_OUT directory + + --aapt + Path to aapt or aapt2 + + --copy_out_system + TARGET_COPY_OUT_SYSTEM + + --copy_out_vendor_ + TARGET_COPY_OUT_VENDOR + + --copy_out_product + TARGET_COPY_OUT_PRODUCT + + --copy_out_system_ext + TARGET_COPY_OUT_SYSTEM_EXT +""" + +import json +import logging +import os +import re +import subprocess +import sys + +from collections import defaultdict +from glob import glob + +import common + +logger = logging.getLogger(__name__) + +OPTIONS = common.OPTIONS +OPTIONS.product_out = os.environ.get("PRODUCT_OUT") +OPTIONS.aapt = "aapt2" +OPTIONS.copy_out_system = "system" +OPTIONS.copy_out_vendor = "vendor" +OPTIONS.copy_out_product = "product" +OPTIONS.copy_out_system_ext = "system_ext" + + +def execute(cmd): + p = subprocess.Popen( + cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = map(lambda b: b.decode("utf-8"), p.communicate()) + return p.returncode == 0, out, err + + +def make_aapt_cmds(aapt, apk): + return [ + aapt + " dump " + apk + " --file AndroidManifest.xml", + aapt + " dump xmltree " + apk + " --file AndroidManifest.xml" + ] + + +def extract_shared_uid(aapt, apk): + for cmd in make_aapt_cmds(aapt, apk): + success, manifest, error_msg = execute(cmd) + if success: + break + else: + logger.error(error_msg) + sys.exit() + + pattern = re.compile(r"sharedUserId.*=\"([^\"]*)") + + for line in manifest.split("\n"): + match = pattern.search(line) + if match: + return match.group(1) + return None + + +def FindShareduidViolation(product_out, partition_map, aapt="aapt2"): + """Find sharedUserId violators in the given partitions. + + Args: + product_out: The base directory containing the partition directories. + partition_map: A map of partition name -> directory name. + aapt: The name of the aapt binary. Defaults to aapt2. + + Returns: + A string containing a JSON object describing the shared UIDs. + """ + shareduid_app_dict = defaultdict(lambda: defaultdict(list)) + + for part, location in partition_map.items(): + for f in glob(os.path.join(product_out, location, "*", "*", "*.apk")): + apk_file = os.path.basename(f) + shared_uid = extract_shared_uid(aapt, f) + + if shared_uid is None: + continue + shareduid_app_dict[shared_uid][part].append(apk_file) + + # Only output sharedUserId values that appear in >1 partition. + output = {} + for uid, partitions in shareduid_app_dict.items(): + if len(partitions) > 1: + output[uid] = shareduid_app_dict[uid] + + return json.dumps(output, indent=2, sort_keys=True) + + +def main(): + common.InitLogging() + + def option_handler(o, a): + if o == "--product_out": + OPTIONS.product_out = a + elif o == "--aapt": + OPTIONS.aapt = a + elif o == "--copy_out_system": + OPTIONS.copy_out_system = a + elif o == "--copy_out_vendor": + OPTIONS.copy_out_vendor = a + elif o == "--copy_out_product": + OPTIONS.copy_out_product = a + elif o == "--copy_out_system_ext": + OPTIONS.copy_out_system_ext = a + else: + return False + return True + + args = common.ParseOptions( + sys.argv[1:], + __doc__, + extra_long_opts=[ + "product_out=", + "aapt=", + "copy_out_system=", + "copy_out_vendor=", + "copy_out_product=", + "copy_out_system_ext=", + ], + extra_option_handler=option_handler) + + if args: + common.Usage(__doc__) + sys.exit(1) + + partition_map = { + "system": OPTIONS.copy_out_system, + "vendor": OPTIONS.copy_out_vendor, + "product": OPTIONS.copy_out_product, + "system_ext": OPTIONS.copy_out_system_ext, + } + + print( + FindShareduidViolation(OPTIONS.product_out, partition_map, OPTIONS.aapt)) + + +if __name__ == "__main__": + main() diff --git a/tools/releasetools/merge_target_files.py b/tools/releasetools/merge_target_files.py index 2da5cc08d..0d135d625 100755 --- a/tools/releasetools/merge_target_files.py +++ b/tools/releasetools/merge_target_files.py @@ -98,6 +98,7 @@ import build_super_image import check_target_files_vintf import common import img_from_target_files +import find_shareduid_violation import ota_from_target_files logger = logging.getLogger(__name__) @@ -943,6 +944,21 @@ def merge_target_files(temp_dir, framework_target_files, framework_item_list, if not check_target_files_vintf.CheckVintf(output_target_files_temp_dir): raise RuntimeError('Incompatible VINTF metadata') + shareduid_violation_modules = os.path.join( + output_target_files_temp_dir, 'META', 'shareduid_violation_modules.json') + with open(shareduid_violation_modules, 'w') as f: + partition_map = { + 'system': 'SYSTEM', + 'vendor': 'VENDOR', + 'product': 'PRODUCT', + 'system_ext': 'SYSTEM_EXT', + } + violation = find_shareduid_violation.FindShareduidViolation( + output_target_files_temp_dir, partition_map) + f.write(violation) + # TODO(b/171431774): Add a check to common.py to check if the + # shared UIDs cross the input build partition boundary. + generate_images(output_target_files_temp_dir, rebuild_recovery) generate_super_empty_image(output_target_files_temp_dir, output_super_empty)