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