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