1#!/usr/bin/env python 2# 3# Copyright (C) 2022 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); you may not 6# use this file except in compliance with the License. You may obtain a copy of 7# the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14# License for the specific language governing permissions and limitations under 15# the License. 16# 17"""Compatibility checks that should be performed on merged target_files.""" 18 19import json 20import logging 21import os 22from xml.etree import ElementTree 23 24import apex_utils 25import check_target_files_vintf 26import common 27import find_shareduid_violation 28 29logger = logging.getLogger(__name__) 30OPTIONS = common.OPTIONS 31 32 33def CheckCompatibility(target_files_dir, partition_map): 34 """Runs various compatibility checks. 35 36 Returns a possibly-empty list of error messages. 37 """ 38 errors = [] 39 40 errors.extend(CheckVintf(target_files_dir)) 41 errors.extend(CheckShareduidViolation(target_files_dir, partition_map)) 42 errors.extend(CheckApexDuplicatePackages(target_files_dir, partition_map)) 43 44 # The remaining checks only use the following partitions: 45 partition_map = { 46 partition: path 47 for partition, path in partition_map.items() 48 if partition in ('system', 'system_ext', 'product', 'vendor', 'odm') 49 } 50 51 errors.extend(CheckInitRcFiles(target_files_dir, partition_map)) 52 errors.extend(CheckCombinedSepolicy(target_files_dir, partition_map)) 53 54 return errors 55 56 57def CheckVintf(target_files_dir): 58 """Check for any VINTF issues using check_vintf.""" 59 errors = [] 60 try: 61 if not check_target_files_vintf.CheckVintf(target_files_dir): 62 errors.append('Incompatible VINTF.') 63 except RuntimeError as err: 64 errors.append(str(err)) 65 return errors 66 67 68def CheckShareduidViolation(target_files_dir, partition_map): 69 """Check for any APK sharedUserId violations across partition sets. 70 71 Writes results to META/shareduid_violation_modules.json to help 72 with followup debugging. 73 """ 74 errors = [] 75 violation = find_shareduid_violation.FindShareduidViolation( 76 target_files_dir, partition_map) 77 shareduid_violation_modules = os.path.join( 78 target_files_dir, 'META', 'shareduid_violation_modules.json') 79 with open(shareduid_violation_modules, 'w') as f: 80 # Write the output to a file to enable debugging. 81 f.write(violation) 82 83 # Check for violations across the partition sets. 84 shareduid_errors = common.SharedUidPartitionViolations( 85 json.loads(violation), 86 [OPTIONS.framework_partition_set, OPTIONS.vendor_partition_set]) 87 if shareduid_errors: 88 for error in shareduid_errors: 89 errors.append('APK sharedUserId error: %s' % error) 90 errors.append('See APK sharedUserId violations file: %s' % 91 shareduid_violation_modules) 92 return errors 93 94 95def CheckInitRcFiles(target_files_dir, partition_map): 96 """Check for any init.rc issues using host_init_verifier.""" 97 try: 98 vendor_partitions = set() 99 if OPTIONS.vendor_otatools: 100 vendor_partitions = {"vendor", "odm"} 101 common.RunVendoredHostInitVerifier( 102 product_out=target_files_dir, 103 partition_map={p: partition_map[p] for p in vendor_partitions}) 104 105 common.RunHostInitVerifier( 106 product_out=target_files_dir, 107 partition_map={ 108 p: partition_map[p] 109 for p in partition_map.keys() - vendor_partitions 110 }) 111 except RuntimeError as err: 112 return [str(err)] 113 return [] 114 115 116def CheckCombinedSepolicy(target_files_dir, partition_map, execute=True): 117 """Uses secilc to compile a split sepolicy file. 118 119 Depends on various */etc/selinux/* and */etc/vintf/* files within partitions. 120 """ 121 errors = [] 122 123 def get_file(partition, path): 124 if partition not in partition_map: 125 logger.warning('Cannot load SEPolicy files for missing partition %s', 126 partition) 127 return None 128 file_path = os.path.join(target_files_dir, partition_map[partition], path) 129 if os.path.exists(file_path): 130 return file_path 131 return None 132 133 # Load the kernel sepolicy version from the FCM. This is normally provided 134 # directly to selinux.cpp as a build flag, but is also available in this file. 135 fcm_file = get_file('system', 'etc/vintf/compatibility_matrix.device.xml') 136 if not fcm_file: 137 errors.append('Missing required file for loading sepolicy: ' 138 '/system/etc/vintf/compatibility_matrix.device.xml') 139 return errors 140 kernel_sepolicy_version = ElementTree.parse(fcm_file).getroot().find( 141 'sepolicy/kernel-sepolicy-version').text 142 143 # Load the vendor's plat sepolicy version. This is the version used for 144 # locating sepolicy mapping files. 145 vendor_plat_version_file = get_file('vendor', 146 'etc/selinux/plat_sepolicy_vers.txt') 147 if not vendor_plat_version_file: 148 errors.append('Missing required sepolicy file %s' % 149 vendor_plat_version_file) 150 return errors 151 with open(vendor_plat_version_file) as f: 152 vendor_plat_version = f.read().strip() 153 154 # Use the same flags and arguments as selinux.cpp OpenSplitPolicy(). 155 cmd = ['secilc', '-m', '-M', 'true', '-G', '-N'] 156 cmd.extend(['-c', kernel_sepolicy_version]) 157 cmd.extend(['-o', os.path.join(target_files_dir, 'META/combined_sepolicy')]) 158 cmd.extend(['-f', '/dev/null']) 159 160 required_policy_files = ( 161 ('system', 'etc/selinux/plat_sepolicy.cil'), 162 ('system', 'etc/selinux/mapping/%s.cil' % vendor_plat_version), 163 ('vendor', 'etc/selinux/vendor_sepolicy.cil'), 164 ('vendor', 'etc/selinux/plat_pub_versioned.cil'), 165 ) 166 for policy in (map(lambda partition_and_path: get_file(*partition_and_path), 167 required_policy_files)): 168 if not policy: 169 errors.append('Missing required sepolicy file %s' % policy) 170 return errors 171 cmd.append(policy) 172 173 optional_policy_files = ( 174 ('system', 'etc/selinux/mapping/%s.compat.cil' % vendor_plat_version), 175 ('system_ext', 'etc/selinux/system_ext_sepolicy.cil'), 176 ('system_ext', 'etc/selinux/mapping/%s.cil' % vendor_plat_version), 177 ('product', 'etc/selinux/product_sepolicy.cil'), 178 ('product', 'etc/selinux/mapping/%s.cil' % vendor_plat_version), 179 ('odm', 'etc/selinux/odm_sepolicy.cil'), 180 ) 181 for policy in (map(lambda partition_and_path: get_file(*partition_and_path), 182 optional_policy_files)): 183 if policy: 184 cmd.append(policy) 185 186 try: 187 if execute: 188 common.RunAndCheckOutput(cmd) 189 else: 190 return cmd 191 except RuntimeError as err: 192 errors.append(str(err)) 193 194 return errors 195 196 197def CheckApexDuplicatePackages(target_files_dir, partition_map): 198 """Checks if the same APEX package name is provided by multiple partitions.""" 199 errors = [] 200 201 apex_packages = set() 202 for partition in partition_map.keys(): 203 try: 204 apex_info = apex_utils.GetApexInfoForPartition( 205 target_files_dir, partition) 206 except RuntimeError as err: 207 errors.append(str(err)) 208 apex_info = [] 209 partition_apex_packages = set([info.package_name for info in apex_info]) 210 duplicates = apex_packages.intersection(partition_apex_packages) 211 if duplicates: 212 errors.append( 213 'Duplicate APEX package_names found in multiple partitions: %s' % 214 ' '.join(duplicates)) 215 apex_packages.update(partition_apex_packages) 216 217 return errors 218