Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b8d52a2f authored by Daniel Norman's avatar Daniel Norman
Browse files

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
parent 865b6605
Loading
Loading
Loading
Loading
+2 −4
Original line number Original line Diff line number Diff line
@@ -16,8 +16,6 @@


shareduid_violation_modules_filename := $(PRODUCT_OUT)/shareduid_violation_modules.json
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) \
$(shareduid_violation_modules_filename): $(INSTALLED_SYSTEMIMAGE_TARGET) \
    $(INSTALLED_RAMDISK_TARGET) \
    $(INSTALLED_RAMDISK_TARGET) \
    $(INSTALLED_BOOTIMAGE_TARGET) \
    $(INSTALLED_BOOTIMAGE_TARGET) \
@@ -26,9 +24,9 @@ $(shareduid_violation_modules_filename): $(INSTALLED_SYSTEMIMAGE_TARGET) \
    $(INSTALLED_PRODUCTIMAGE_TARGET) \
    $(INSTALLED_PRODUCTIMAGE_TARGET) \
    $(INSTALLED_SYSTEM_EXTIMAGE_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)
$(shareduid_violation_modules_filename): $(AAPT2)
	$(find_shareduid_script) \
	$(HOST_OUT_EXECUTABLES)/find_shareduid_violation \
		--product_out $(PRODUCT_OUT) \
		--product_out $(PRODUCT_OUT) \
		--aapt $(AAPT2) \
		--aapt $(AAPT2) \
		--copy_out_system $(TARGET_COPY_OUT_SYSTEM) \
		--copy_out_system $(TARGET_COPY_OUT_SYSTEM) \
+0 −99
Original line number Original line Diff line number Diff line
#!/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))
+28 −0
Original line number Original line Diff line number Diff line
@@ -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 {
python_binary_host {
    name: "make_recovery_patch",
    name: "make_recovery_patch",
    defaults: ["releasetools_binary_defaults"],
    defaults: ["releasetools_binary_defaults"],
@@ -402,6 +428,7 @@ python_binary_host {
        "releasetools_build_super_image",
        "releasetools_build_super_image",
        "releasetools_check_target_files_vintf",
        "releasetools_check_target_files_vintf",
        "releasetools_common",
        "releasetools_common",
        "releasetools_find_shareduid_violation",
        "releasetools_img_from_target_files",
        "releasetools_img_from_target_files",
        "releasetools_ota_from_target_files",
        "releasetools_ota_from_target_files",
    ],
    ],
@@ -504,6 +531,7 @@ python_defaults {
        "releasetools_build_super_image",
        "releasetools_build_super_image",
        "releasetools_check_target_files_vintf",
        "releasetools_check_target_files_vintf",
        "releasetools_common",
        "releasetools_common",
        "releasetools_find_shareduid_violation",
        "releasetools_img_from_target_files",
        "releasetools_img_from_target_files",
        "releasetools_ota_from_target_files",
        "releasetools_ota_from_target_files",
        "releasetools_verity_utils",
        "releasetools_verity_utils",
+175 −0
Original line number Original line Diff line number Diff line
#!/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()
+16 −0
Original line number Original line Diff line number Diff line
@@ -98,6 +98,7 @@ import build_super_image
import check_target_files_vintf
import check_target_files_vintf
import common
import common
import img_from_target_files
import img_from_target_files
import find_shareduid_violation
import ota_from_target_files
import ota_from_target_files


logger = logging.getLogger(__name__)
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):
  if not check_target_files_vintf.CheckVintf(output_target_files_temp_dir):
    raise RuntimeError('Incompatible VINTF metadata')
    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_images(output_target_files_temp_dir, rebuild_recovery)


  generate_super_empty_image(output_target_files_temp_dir, output_super_empty)
  generate_super_empty_image(output_target_files_temp_dir, output_super_empty)