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 common.RunHostInitVerifier( 99 product_out=target_files_dir, partition_map=partition_map) 100 except RuntimeError as err: 101 return [str(err)] 102 return [] 103 104 105def CheckCombinedSepolicy(target_files_dir, partition_map, execute=True): 106 """Uses secilc to compile a split sepolicy file. 107 108 Depends on various */etc/selinux/* and */etc/vintf/* files within partitions. 109 """ 110 errors = [] 111 112 def get_file(partition, path): 113 if partition not in partition_map: 114 logger.warning('Cannot load SEPolicy files for missing partition %s', 115 partition) 116 return None 117 file_path = os.path.join(target_files_dir, partition_map[partition], path) 118 if os.path.exists(file_path): 119 return file_path 120 return None 121 122 # Load the kernel sepolicy version from the FCM. This is normally provided 123 # directly to selinux.cpp as a build flag, but is also available in this file. 124 fcm_file = get_file('system', 'etc/vintf/compatibility_matrix.device.xml') 125 if not fcm_file: 126 errors.append('Missing required file for loading sepolicy: ' 127 '/system/etc/vintf/compatibility_matrix.device.xml') 128 return errors 129 kernel_sepolicy_version = ElementTree.parse(fcm_file).getroot().find( 130 'sepolicy/kernel-sepolicy-version').text 131 132 # Load the vendor's plat sepolicy version. This is the version used for 133 # locating sepolicy mapping files. 134 vendor_plat_version_file = get_file('vendor', 135 'etc/selinux/plat_sepolicy_vers.txt') 136 if not vendor_plat_version_file: 137 errors.append('Missing required sepolicy file %s' % 138 vendor_plat_version_file) 139 return errors 140 with open(vendor_plat_version_file) as f: 141 vendor_plat_version = f.read().strip() 142 143 # Use the same flags and arguments as selinux.cpp OpenSplitPolicy(). 144 cmd = ['secilc', '-m', '-M', 'true', '-G', '-N'] 145 cmd.extend(['-c', kernel_sepolicy_version]) 146 cmd.extend(['-o', os.path.join(target_files_dir, 'META/combined_sepolicy')]) 147 cmd.extend(['-f', '/dev/null']) 148 149 required_policy_files = ( 150 ('system', 'etc/selinux/plat_sepolicy.cil'), 151 ('system', 'etc/selinux/mapping/%s.cil' % vendor_plat_version), 152 ('vendor', 'etc/selinux/vendor_sepolicy.cil'), 153 ('vendor', 'etc/selinux/plat_pub_versioned.cil'), 154 ) 155 for policy in (map(lambda partition_and_path: get_file(*partition_and_path), 156 required_policy_files)): 157 if not policy: 158 errors.append('Missing required sepolicy file %s' % policy) 159 return errors 160 cmd.append(policy) 161 162 optional_policy_files = ( 163 ('system', 'etc/selinux/mapping/%s.compat.cil' % vendor_plat_version), 164 ('system_ext', 'etc/selinux/system_ext_sepolicy.cil'), 165 ('system_ext', 'etc/selinux/mapping/%s.cil' % vendor_plat_version), 166 ('product', 'etc/selinux/product_sepolicy.cil'), 167 ('product', 'etc/selinux/mapping/%s.cil' % vendor_plat_version), 168 ('odm', 'etc/selinux/odm_sepolicy.cil'), 169 ) 170 for policy in (map(lambda partition_and_path: get_file(*partition_and_path), 171 optional_policy_files)): 172 if policy: 173 cmd.append(policy) 174 175 try: 176 if execute: 177 common.RunAndCheckOutput(cmd) 178 else: 179 return cmd 180 except RuntimeError as err: 181 errors.append(str(err)) 182 183 return errors 184 185 186def CheckApexDuplicatePackages(target_files_dir, partition_map): 187 """Checks if the same APEX package name is provided by multiple partitions.""" 188 errors = [] 189 190 apex_packages = set() 191 for partition in partition_map.keys(): 192 try: 193 apex_info = apex_utils.GetApexInfoForPartition( 194 target_files_dir, partition) 195 except RuntimeError as err: 196 errors.append(str(err)) 197 apex_info = [] 198 partition_apex_packages = set([info.package_name for info in apex_info]) 199 duplicates = apex_packages.intersection(partition_apex_packages) 200 if duplicates: 201 errors.append( 202 'Duplicate APEX package_names found in multiple partitions: %s' % 203 ' '.join(duplicates)) 204 apex_packages.update(partition_apex_packages) 205 206 return errors 207