Daniel Norman | 2465fc8 | 2022-03-02 12:01:20 -0800 | [diff] [blame^] | 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 | |
| 19 | import json |
| 20 | import logging |
| 21 | import os |
| 22 | from xml.etree import ElementTree |
| 23 | |
| 24 | import apex_utils |
| 25 | import check_target_files_vintf |
| 26 | import common |
| 27 | import find_shareduid_violation |
| 28 | |
| 29 | logger = logging.getLogger(__name__) |
| 30 | OPTIONS = common.OPTIONS |
| 31 | |
| 32 | |
| 33 | def 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 | |
| 57 | def 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 | |
| 68 | def 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 | |
| 95 | def 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 | |
| 105 | def 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 | |
| 186 | def 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.GetApexInfoFromTargetFiles( |
| 194 | target_files_dir, partition, compressed_only=False) |
| 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 |