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
|