207 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			207 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Python
		
	
	
	
#!/usr/bin/env python
 | 
						|
#
 | 
						|
# Copyright (C) 2022 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.
 | 
						|
#
 | 
						|
"""Compatibility checks that should be performed on merged target_files."""
 | 
						|
 | 
						|
import json
 | 
						|
import logging
 | 
						|
import os
 | 
						|
from xml.etree import ElementTree
 | 
						|
 | 
						|
import apex_utils
 | 
						|
import check_target_files_vintf
 | 
						|
import common
 | 
						|
import find_shareduid_violation
 | 
						|
 | 
						|
logger = logging.getLogger(__name__)
 | 
						|
OPTIONS = common.OPTIONS
 | 
						|
 | 
						|
 | 
						|
def CheckCompatibility(target_files_dir, partition_map):
 | 
						|
  """Runs various compatibility checks.
 | 
						|
 | 
						|
  Returns a possibly-empty list of error messages.
 | 
						|
  """
 | 
						|
  errors = []
 | 
						|
 | 
						|
  errors.extend(CheckVintf(target_files_dir))
 | 
						|
  errors.extend(CheckShareduidViolation(target_files_dir, partition_map))
 | 
						|
  errors.extend(CheckApexDuplicatePackages(target_files_dir, partition_map))
 | 
						|
 | 
						|
  # The remaining checks only use the following partitions:
 | 
						|
  partition_map = {
 | 
						|
      partition: path
 | 
						|
      for partition, path in partition_map.items()
 | 
						|
      if partition in ('system', 'system_ext', 'product', 'vendor', 'odm')
 | 
						|
  }
 | 
						|
 | 
						|
  errors.extend(CheckInitRcFiles(target_files_dir, partition_map))
 | 
						|
  errors.extend(CheckCombinedSepolicy(target_files_dir, partition_map))
 | 
						|
 | 
						|
  return errors
 | 
						|
 | 
						|
 | 
						|
def CheckVintf(target_files_dir):
 | 
						|
  """Check for any VINTF issues using check_vintf."""
 | 
						|
  errors = []
 | 
						|
  try:
 | 
						|
    if not check_target_files_vintf.CheckVintf(target_files_dir):
 | 
						|
      errors.append('Incompatible VINTF.')
 | 
						|
  except RuntimeError as err:
 | 
						|
    errors.append(str(err))
 | 
						|
  return errors
 | 
						|
 | 
						|
 | 
						|
def CheckShareduidViolation(target_files_dir, partition_map):
 | 
						|
  """Check for any APK sharedUserId violations across partition sets.
 | 
						|
 | 
						|
  Writes results to META/shareduid_violation_modules.json to help
 | 
						|
  with followup debugging.
 | 
						|
  """
 | 
						|
  errors = []
 | 
						|
  violation = find_shareduid_violation.FindShareduidViolation(
 | 
						|
      target_files_dir, partition_map)
 | 
						|
  shareduid_violation_modules = os.path.join(
 | 
						|
      target_files_dir, 'META', 'shareduid_violation_modules.json')
 | 
						|
  with open(shareduid_violation_modules, 'w') as f:
 | 
						|
    # Write the output to a file to enable debugging.
 | 
						|
    f.write(violation)
 | 
						|
 | 
						|
    # Check for violations across the partition sets.
 | 
						|
    shareduid_errors = common.SharedUidPartitionViolations(
 | 
						|
        json.loads(violation),
 | 
						|
        [OPTIONS.framework_partition_set, OPTIONS.vendor_partition_set])
 | 
						|
    if shareduid_errors:
 | 
						|
      for error in shareduid_errors:
 | 
						|
        errors.append('APK sharedUserId error: %s' % error)
 | 
						|
      errors.append('See APK sharedUserId violations file: %s' %
 | 
						|
                    shareduid_violation_modules)
 | 
						|
  return errors
 | 
						|
 | 
						|
 | 
						|
def CheckInitRcFiles(target_files_dir, partition_map):
 | 
						|
  """Check for any init.rc issues using host_init_verifier."""
 | 
						|
  try:
 | 
						|
    common.RunHostInitVerifier(
 | 
						|
        product_out=target_files_dir, partition_map=partition_map)
 | 
						|
  except RuntimeError as err:
 | 
						|
    return [str(err)]
 | 
						|
  return []
 | 
						|
 | 
						|
 | 
						|
def CheckCombinedSepolicy(target_files_dir, partition_map, execute=True):
 | 
						|
  """Uses secilc to compile a split sepolicy file.
 | 
						|
 | 
						|
  Depends on various */etc/selinux/* and */etc/vintf/* files within partitions.
 | 
						|
  """
 | 
						|
  errors = []
 | 
						|
 | 
						|
  def get_file(partition, path):
 | 
						|
    if partition not in partition_map:
 | 
						|
      logger.warning('Cannot load SEPolicy files for missing partition %s',
 | 
						|
                     partition)
 | 
						|
      return None
 | 
						|
    file_path = os.path.join(target_files_dir, partition_map[partition], path)
 | 
						|
    if os.path.exists(file_path):
 | 
						|
      return file_path
 | 
						|
    return None
 | 
						|
 | 
						|
  # Load the kernel sepolicy version from the FCM. This is normally provided
 | 
						|
  # directly to selinux.cpp as a build flag, but is also available in this file.
 | 
						|
  fcm_file = get_file('system', 'etc/vintf/compatibility_matrix.device.xml')
 | 
						|
  if not fcm_file:
 | 
						|
    errors.append('Missing required file for loading sepolicy: '
 | 
						|
                  '/system/etc/vintf/compatibility_matrix.device.xml')
 | 
						|
    return errors
 | 
						|
  kernel_sepolicy_version = ElementTree.parse(fcm_file).getroot().find(
 | 
						|
      'sepolicy/kernel-sepolicy-version').text
 | 
						|
 | 
						|
  # Load the vendor's plat sepolicy version. This is the version used for
 | 
						|
  # locating sepolicy mapping files.
 | 
						|
  vendor_plat_version_file = get_file('vendor',
 | 
						|
                                      'etc/selinux/plat_sepolicy_vers.txt')
 | 
						|
  if not vendor_plat_version_file:
 | 
						|
    errors.append('Missing required sepolicy file %s' %
 | 
						|
                  vendor_plat_version_file)
 | 
						|
    return errors
 | 
						|
  with open(vendor_plat_version_file) as f:
 | 
						|
    vendor_plat_version = f.read().strip()
 | 
						|
 | 
						|
  # Use the same flags and arguments as selinux.cpp OpenSplitPolicy().
 | 
						|
  cmd = ['secilc', '-m', '-M', 'true', '-G', '-N']
 | 
						|
  cmd.extend(['-c', kernel_sepolicy_version])
 | 
						|
  cmd.extend(['-o', os.path.join(target_files_dir, 'META/combined_sepolicy')])
 | 
						|
  cmd.extend(['-f', '/dev/null'])
 | 
						|
 | 
						|
  required_policy_files = (
 | 
						|
      ('system', 'etc/selinux/plat_sepolicy.cil'),
 | 
						|
      ('system', 'etc/selinux/mapping/%s.cil' % vendor_plat_version),
 | 
						|
      ('vendor', 'etc/selinux/vendor_sepolicy.cil'),
 | 
						|
      ('vendor', 'etc/selinux/plat_pub_versioned.cil'),
 | 
						|
  )
 | 
						|
  for policy in (map(lambda partition_and_path: get_file(*partition_and_path),
 | 
						|
                     required_policy_files)):
 | 
						|
    if not policy:
 | 
						|
      errors.append('Missing required sepolicy file %s' % policy)
 | 
						|
      return errors
 | 
						|
    cmd.append(policy)
 | 
						|
 | 
						|
  optional_policy_files = (
 | 
						|
      ('system', 'etc/selinux/mapping/%s.compat.cil' % vendor_plat_version),
 | 
						|
      ('system_ext', 'etc/selinux/system_ext_sepolicy.cil'),
 | 
						|
      ('system_ext', 'etc/selinux/mapping/%s.cil' % vendor_plat_version),
 | 
						|
      ('product', 'etc/selinux/product_sepolicy.cil'),
 | 
						|
      ('product', 'etc/selinux/mapping/%s.cil' % vendor_plat_version),
 | 
						|
      ('odm', 'etc/selinux/odm_sepolicy.cil'),
 | 
						|
  )
 | 
						|
  for policy in (map(lambda partition_and_path: get_file(*partition_and_path),
 | 
						|
                     optional_policy_files)):
 | 
						|
    if policy:
 | 
						|
      cmd.append(policy)
 | 
						|
 | 
						|
  try:
 | 
						|
    if execute:
 | 
						|
      common.RunAndCheckOutput(cmd)
 | 
						|
    else:
 | 
						|
      return cmd
 | 
						|
  except RuntimeError as err:
 | 
						|
    errors.append(str(err))
 | 
						|
 | 
						|
  return errors
 | 
						|
 | 
						|
 | 
						|
def CheckApexDuplicatePackages(target_files_dir, partition_map):
 | 
						|
  """Checks if the same APEX package name is provided by multiple partitions."""
 | 
						|
  errors = []
 | 
						|
 | 
						|
  apex_packages = set()
 | 
						|
  for partition in partition_map.keys():
 | 
						|
    try:
 | 
						|
      apex_info = apex_utils.GetApexInfoFromTargetFiles(
 | 
						|
          target_files_dir, partition, compressed_only=False)
 | 
						|
    except RuntimeError as err:
 | 
						|
      errors.append(str(err))
 | 
						|
      apex_info = []
 | 
						|
    partition_apex_packages = set([info.package_name for info in apex_info])
 | 
						|
    duplicates = apex_packages.intersection(partition_apex_packages)
 | 
						|
    if duplicates:
 | 
						|
      errors.append(
 | 
						|
          'Duplicate APEX package_names found in multiple partitions: %s' %
 | 
						|
          ' '.join(duplicates))
 | 
						|
    apex_packages.update(partition_apex_packages)
 | 
						|
 | 
						|
  return errors
 |