1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.pm.pkg.component; 18 19 import static com.android.internal.pm.pkg.parsing.ParsingPackageUtils.PARSE_APK_IN_APEX; 20 import static com.android.internal.pm.pkg.parsing.ParsingUtils.NOT_SET; 21 22 import android.annotation.NonNull; 23 import android.content.pm.PermissionInfo; 24 import android.content.pm.parsing.result.ParseInput; 25 import android.content.pm.parsing.result.ParseResult; 26 import android.content.res.Resources; 27 import android.content.res.TypedArray; 28 import android.content.res.XmlResourceParser; 29 import android.os.Build; 30 import android.permission.flags.Flags; 31 import android.util.ArrayMap; 32 import android.util.EventLog; 33 import android.util.Slog; 34 35 import com.android.internal.R; 36 import com.android.internal.pm.pkg.parsing.ParsingPackage; 37 import com.android.internal.pm.pkg.parsing.ParsingUtils; 38 39 import org.xmlpull.v1.XmlPullParserException; 40 41 import java.io.IOException; 42 import java.util.List; 43 import java.util.Objects; 44 45 /** 46 * @hide 47 */ 48 public class ParsedPermissionUtils { 49 50 private static final String TAG = ParsingUtils.TAG; 51 52 @NonNull parsePermission(ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean useRoundIcon, ParseInput input, int flags)53 public static ParseResult<ParsedPermission> parsePermission(ParsingPackage pkg, Resources res, 54 XmlResourceParser parser, boolean useRoundIcon, ParseInput input, int flags) 55 throws IOException, XmlPullParserException { 56 String packageName = pkg.getPackageName(); 57 ParsedPermissionImpl permission = new ParsedPermissionImpl(); 58 String tag = "<" + parser.getName() + ">"; 59 ParseResult<ParsedPermissionImpl> result; 60 61 try (TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestPermission)) { 62 result = ParsedComponentUtils.parseComponent( 63 permission, tag, pkg, sa, useRoundIcon, input, 64 R.styleable.AndroidManifestPermission_banner, 65 R.styleable.AndroidManifestPermission_description, 66 R.styleable.AndroidManifestPermission_icon, 67 R.styleable.AndroidManifestPermission_label, 68 R.styleable.AndroidManifestPermission_logo, 69 R.styleable.AndroidManifestPermission_name, 70 R.styleable.AndroidManifestPermission_roundIcon); 71 if (result.isError()) { 72 return input.error(result); 73 } 74 75 int maxSdkVersion = sa.getInt(R.styleable.AndroidManifestPermission_maxSdkVersion, -1); 76 if ((maxSdkVersion != -1) && (maxSdkVersion < Build.VERSION.SDK_INT)) { 77 return input.success(null); 78 } 79 80 if (sa.hasValue( 81 R.styleable.AndroidManifestPermission_backgroundPermission)) { 82 final boolean isApkInApex = (flags & PARSE_APK_IN_APEX) != 0; 83 final boolean canUseBackgroundPermissionAttr = 84 "android".equals(packageName) || 85 (Flags.replaceBodySensorPermissionEnabled() && isApkInApex); 86 if (canUseBackgroundPermissionAttr) { 87 permission.setBackgroundPermission(sa.getNonResourceString( 88 R.styleable.AndroidManifestPermission_backgroundPermission)); 89 } else { 90 String allowedPackages = "'android'" 91 + (Flags.replaceBodySensorPermissionEnabled() ? " and APK_IN_APEX" : ""); 92 Slog.w(TAG, packageName + " defines a background permission. Only the " 93 + allowedPackages + " packages can do that."); 94 } 95 } 96 97 // Note: don't allow this value to be a reference to a resource 98 // that may change. 99 permission.setGroup(sa.getNonResourceString( 100 R.styleable.AndroidManifestPermission_permissionGroup)) 101 .setRequestRes(sa.getResourceId( 102 R.styleable.AndroidManifestPermission_request, 0)) 103 .setProtectionLevel(sa.getInt( 104 R.styleable.AndroidManifestPermission_protectionLevel, 105 PermissionInfo.PROTECTION_NORMAL)) 106 .setFlags(sa.getInt( 107 R.styleable.AndroidManifestPermission_permissionFlags, 0)); 108 109 final int knownCertsResource = sa.getResourceId( 110 R.styleable.AndroidManifestPermission_knownCerts, 0); 111 if (knownCertsResource != 0) { 112 // The knownCerts attribute supports both a string array resource as well as a 113 // string resource for the case where the permission should only be granted to a 114 // single known signer. 115 final String resourceType = res.getResourceTypeName(knownCertsResource); 116 if (resourceType.equals("array")) { 117 final String[] knownCerts = res.getStringArray(knownCertsResource); 118 if (knownCerts != null) { 119 permission.setKnownCerts(knownCerts); 120 } 121 } else { 122 final String knownCert = res.getString(knownCertsResource); 123 if (knownCert != null) { 124 permission.setKnownCert(knownCert); 125 } 126 } 127 if (permission.getKnownCerts().isEmpty()) { 128 Slog.w(TAG, packageName + " defines a knownSigner permission but" 129 + " the provided knownCerts resource is null"); 130 } 131 } else { 132 // If the knownCerts resource ID is null check if the app specified a string 133 // value for the attribute representing a single trusted signer. 134 final String knownCert = sa.getString( 135 R.styleable.AndroidManifestPermission_knownCerts); 136 if (knownCert != null) { 137 permission.setKnownCert(knownCert); 138 } 139 } 140 141 // For now only platform runtime permissions can be restricted 142 if (!isRuntime(permission) || !"android".equals(permission.getPackageName())) { 143 permission.setFlags(permission.getFlags() & ~PermissionInfo.FLAG_HARD_RESTRICTED); 144 permission.setFlags(permission.getFlags() & ~PermissionInfo.FLAG_SOFT_RESTRICTED); 145 } else { 146 // The platform does not get to specify conflicting permissions 147 if ((permission.getFlags() & PermissionInfo.FLAG_HARD_RESTRICTED) != 0 148 && (permission.getFlags() & PermissionInfo.FLAG_SOFT_RESTRICTED) != 0) { 149 throw new IllegalStateException("Permission cannot be both soft and hard" 150 + " restricted: " + permission.getName()); 151 } 152 } 153 } 154 155 permission.setProtectionLevel( 156 PermissionInfo.fixProtectionLevel(permission.getProtectionLevel())); 157 158 final int otherProtectionFlags = getProtectionFlags(permission) 159 & ~(PermissionInfo.PROTECTION_FLAG_APPOP | PermissionInfo.PROTECTION_FLAG_INSTANT 160 | PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY); 161 if (otherProtectionFlags != 0 162 && getProtection(permission) != PermissionInfo.PROTECTION_SIGNATURE 163 && getProtection(permission) != PermissionInfo.PROTECTION_INTERNAL) { 164 return input.error("<permission> protectionLevel specifies a non-instant, non-appop," 165 + " non-runtimeOnly flag but is not based on signature or internal type"); 166 } 167 168 result = ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, permission, input); 169 if (result.isError()) { 170 return input.error(result); 171 } 172 173 return input.success(result.getResult()); 174 } 175 176 @NonNull parsePermissionTree(ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean useRoundIcon, ParseInput input)177 public static ParseResult<ParsedPermission> parsePermissionTree(ParsingPackage pkg, Resources res, 178 XmlResourceParser parser, boolean useRoundIcon, ParseInput input) 179 throws IOException, XmlPullParserException { 180 ParsedPermissionImpl permission = new ParsedPermissionImpl(); 181 String tag = "<" + parser.getName() + ">"; 182 ParseResult<ParsedPermissionImpl> result; 183 184 TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestPermissionTree); 185 try { 186 result = ParsedComponentUtils.parseComponent( 187 permission, tag, pkg, sa, useRoundIcon, input, 188 R.styleable.AndroidManifestPermissionTree_banner, 189 NOT_SET /*descriptionAttr*/, 190 R.styleable.AndroidManifestPermissionTree_icon, 191 R.styleable.AndroidManifestPermissionTree_label, 192 R.styleable.AndroidManifestPermissionTree_logo, 193 R.styleable.AndroidManifestPermissionTree_name, 194 R.styleable.AndroidManifestPermissionTree_roundIcon); 195 if (result.isError()) { 196 return input.error(result); 197 } 198 } finally { 199 sa.recycle(); 200 } 201 202 int index = permission.getName().indexOf('.'); 203 if (index > 0) { 204 index = permission.getName().indexOf('.', index + 1); 205 } 206 if (index < 0) { 207 return input.error("<permission-tree> name has less than three segments: " 208 + permission.getName()); 209 } 210 211 permission.setProtectionLevel(PermissionInfo.PROTECTION_NORMAL) 212 .setTree(true); 213 214 result = ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, permission, input); 215 if (result.isError()) { 216 return input.error(result); 217 } 218 219 return input.success(result.getResult()); 220 } 221 222 @NonNull parsePermissionGroup(ParsingPackage pkg, Resources res, XmlResourceParser parser, boolean useRoundIcon, ParseInput input)223 public static ParseResult<ParsedPermissionGroup> parsePermissionGroup(ParsingPackage pkg, 224 Resources res, XmlResourceParser parser, boolean useRoundIcon, ParseInput input) 225 throws IOException, XmlPullParserException { 226 ParsedPermissionGroupImpl 227 permissionGroup = new ParsedPermissionGroupImpl(); 228 String tag = "<" + parser.getName() + ">"; 229 230 TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestPermissionGroup); 231 try { 232 ParseResult<ParsedPermissionGroupImpl> result = ParsedComponentUtils.parseComponent( 233 permissionGroup, tag, pkg, sa, useRoundIcon, input, 234 R.styleable.AndroidManifestPermissionGroup_banner, 235 R.styleable.AndroidManifestPermissionGroup_description, 236 R.styleable.AndroidManifestPermissionGroup_icon, 237 R.styleable.AndroidManifestPermissionGroup_label, 238 R.styleable.AndroidManifestPermissionGroup_logo, 239 R.styleable.AndroidManifestPermissionGroup_name, 240 R.styleable.AndroidManifestPermissionGroup_roundIcon); 241 if (result.isError()) { 242 return input.error(result); 243 } 244 245 // @formatter:off 246 permissionGroup.setRequestDetailRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_requestDetail, 0)) 247 .setBackgroundRequestRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_backgroundRequest, 0)) 248 .setBackgroundRequestDetailRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_backgroundRequestDetail, 0)) 249 .setRequestRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_request, 0)) 250 .setPriority(sa.getInt(R.styleable.AndroidManifestPermissionGroup_priority, 0)) 251 .setFlags(sa.getInt(R.styleable.AndroidManifestPermissionGroup_permissionGroupFlags,0)); 252 // @formatter:on 253 } finally { 254 sa.recycle(); 255 } 256 257 ParseResult<ParsedPermissionGroupImpl> result = ComponentParseUtils.parseAllMetaData(pkg, 258 res, parser, tag, permissionGroup, input); 259 if (result.isError()) { 260 return input.error(result); 261 } 262 263 return input.success(result.getResult()); 264 } 265 isRuntime(@onNull ParsedPermission permission)266 public static boolean isRuntime(@NonNull ParsedPermission permission) { 267 return getProtection(permission) == PermissionInfo.PROTECTION_DANGEROUS; 268 } 269 isAppOp(@onNull ParsedPermission permission)270 public static boolean isAppOp(@NonNull ParsedPermission permission) { 271 return (permission.getProtectionLevel() & PermissionInfo.PROTECTION_FLAG_APPOP) != 0; 272 } 273 274 @PermissionInfo.Protection getProtection(@onNull ParsedPermission permission)275 public static int getProtection(@NonNull ParsedPermission permission) { 276 return permission.getProtectionLevel() & PermissionInfo.PROTECTION_MASK_BASE; 277 } 278 getProtectionFlags(@onNull ParsedPermission permission)279 public static int getProtectionFlags(@NonNull ParsedPermission permission) { 280 return permission.getProtectionLevel() & ~PermissionInfo.PROTECTION_MASK_BASE; 281 } 282 calculateFootprint(@onNull ParsedPermission permission)283 public static int calculateFootprint(@NonNull ParsedPermission permission) { 284 int size = permission.getName().length(); 285 CharSequence nonLocalizedLabel = permission.getNonLocalizedLabel(); 286 if (nonLocalizedLabel != null) { 287 size += nonLocalizedLabel.length(); 288 } 289 return size; 290 } 291 292 /** 293 * Determines if a duplicate permission is malformed .i.e. defines different protection level 294 * or group. 295 */ isMalformedDuplicate(ParsedPermission p1, ParsedPermission p2)296 private static boolean isMalformedDuplicate(ParsedPermission p1, ParsedPermission p2) { 297 // Since a permission tree is also added as a permission with normal protection 298 // level, we need to skip if the parsedPermission is a permission tree. 299 if (p1 == null || p2 == null || p1.isTree() || p2.isTree()) { 300 return false; 301 } 302 303 if (p1.getProtectionLevel() != p2.getProtectionLevel()) { 304 return true; 305 } 306 if (!Objects.equals(p1.getGroup(), p2.getGroup())) { 307 return true; 308 } 309 310 return false; 311 } 312 313 /** 314 * @return {@code true} if the package declares malformed duplicate permissions. 315 */ declareDuplicatePermission(@onNull ParsingPackage pkg)316 public static boolean declareDuplicatePermission(@NonNull ParsingPackage pkg) { 317 final List<ParsedPermission> permissions = pkg.getPermissions(); 318 final int size = permissions.size(); 319 if (size > 0) { 320 final ArrayMap<String, ParsedPermission> checkDuplicatePerm = new ArrayMap<>(size); 321 for (int i = 0; i < size; i++) { 322 final ParsedPermission parsedPermission = permissions.get(i); 323 final String name = parsedPermission.getName(); 324 final ParsedPermission perm = checkDuplicatePerm.get(name); 325 if (isMalformedDuplicate(parsedPermission, perm)) { 326 // Fix for b/213323615 327 EventLog.writeEvent(0x534e4554, "213323615"); 328 return true; 329 } 330 checkDuplicatePerm.put(name, parsedPermission); 331 } 332 } 333 return false; 334 } 335 } 336