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
This commit is contained in:
Daniel Norman 2020-10-26 17:55:00 -07:00
parent 865b6605ca
commit b8d52a2fdc
5 changed files with 221 additions and 103 deletions

View file

@ -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) \

View file

@ -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))

View file

@ -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",

View file

@ -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()

View file

@ -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)