blob: 1366de77af8b880f2800035042003d04b771389a [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.pm.pkg.component;
import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET;
import android.annotation.NonNull;
import android.content.pm.PermissionInfo;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.util.ArrayMap;
import android.util.EventLog;
import android.util.Slog;
import com.android.internal.R;
import com.android.server.pm.pkg.parsing.ParsingPackage;
import com.android.server.pm.pkg.parsing.ParsingUtils;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
/**
* @hide
*/
public class ParsedPermissionUtils {
private static final String TAG = ParsingUtils.TAG;
@NonNull
public static ParseResult<ParsedPermission> parsePermission(ParsingPackage pkg, Resources res,
XmlResourceParser parser, boolean useRoundIcon, ParseInput input)
throws IOException, XmlPullParserException {
String packageName = pkg.getPackageName();
ParsedPermissionImpl permission = new ParsedPermissionImpl();
String tag = "<" + parser.getName() + ">";
ParseResult<ParsedPermissionImpl> result;
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestPermission);
try {
result = ParsedComponentUtils.parseComponent(
permission, tag, pkg, sa, useRoundIcon, input,
R.styleable.AndroidManifestPermission_banner,
R.styleable.AndroidManifestPermission_description,
R.styleable.AndroidManifestPermission_icon,
R.styleable.AndroidManifestPermission_label,
R.styleable.AndroidManifestPermission_logo,
R.styleable.AndroidManifestPermission_name,
R.styleable.AndroidManifestPermission_roundIcon);
if (result.isError()) {
return input.error(result);
}
if (sa.hasValue(
R.styleable.AndroidManifestPermission_backgroundPermission)) {
if ("android".equals(packageName)) {
permission.setBackgroundPermission(sa.getNonResourceString(
R.styleable.AndroidManifestPermission_backgroundPermission));
} else {
Slog.w(TAG, packageName + " defines a background permission. Only the "
+ "'android' package can do that.");
}
}
// Note: don't allow this value to be a reference to a resource
// that may change.
permission.setGroup(sa.getNonResourceString(
R.styleable.AndroidManifestPermission_permissionGroup))
.setRequestRes(sa.getResourceId(
R.styleable.AndroidManifestPermission_request, 0))
.setProtectionLevel(sa.getInt(
R.styleable.AndroidManifestPermission_protectionLevel,
PermissionInfo.PROTECTION_NORMAL))
.setFlags(sa.getInt(
R.styleable.AndroidManifestPermission_permissionFlags, 0));
final int knownCertsResource = sa.getResourceId(
R.styleable.AndroidManifestPermission_knownCerts, 0);
if (knownCertsResource != 0) {
// The knownCerts attribute supports both a string array resource as well as a
// string resource for the case where the permission should only be granted to a
// single known signer.
final String resourceType = res.getResourceTypeName(knownCertsResource);
if (resourceType.equals("array")) {
final String[] knownCerts = res.getStringArray(knownCertsResource);
if (knownCerts != null) {
permission.setKnownCerts(knownCerts);
}
} else {
final String knownCert = res.getString(knownCertsResource);
if (knownCert != null) {
permission.setKnownCert(knownCert);
}
}
if (permission.getKnownCerts().isEmpty()) {
Slog.w(TAG, packageName + " defines a knownSigner permission but"
+ " the provided knownCerts resource is null");
}
} else {
// If the knownCerts resource ID is null check if the app specified a string
// value for the attribute representing a single trusted signer.
final String knownCert = sa.getString(
R.styleable.AndroidManifestPermission_knownCerts);
if (knownCert != null) {
permission.setKnownCert(knownCert);
}
}
// For now only platform runtime permissions can be restricted
if (!isRuntime(permission) || !"android".equals(permission.getPackageName())) {
permission.setFlags(permission.getFlags() & ~PermissionInfo.FLAG_HARD_RESTRICTED);
permission.setFlags(permission.getFlags() & ~PermissionInfo.FLAG_SOFT_RESTRICTED);
} else {
// The platform does not get to specify conflicting permissions
if ((permission.getFlags() & PermissionInfo.FLAG_HARD_RESTRICTED) != 0
&& (permission.getFlags() & PermissionInfo.FLAG_SOFT_RESTRICTED) != 0) {
throw new IllegalStateException("Permission cannot be both soft and hard"
+ " restricted: " + permission.getName());
}
}
} finally {
sa.recycle();
}
permission.setProtectionLevel(
PermissionInfo.fixProtectionLevel(permission.getProtectionLevel()));
final int otherProtectionFlags = getProtectionFlags(permission)
& ~(PermissionInfo.PROTECTION_FLAG_APPOP | PermissionInfo.PROTECTION_FLAG_INSTANT
| PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY);
if (otherProtectionFlags != 0
&& getProtection(permission) != PermissionInfo.PROTECTION_SIGNATURE
&& getProtection(permission) != PermissionInfo.PROTECTION_INTERNAL) {
return input.error("<permission> protectionLevel specifies a non-instant, non-appop,"
+ " non-runtimeOnly flag but is not based on signature or internal type");
}
result = ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, permission, input);
if (result.isError()) {
return input.error(result);
}
return input.success(result.getResult());
}
@NonNull
public static ParseResult<ParsedPermission> parsePermissionTree(ParsingPackage pkg, Resources res,
XmlResourceParser parser, boolean useRoundIcon, ParseInput input)
throws IOException, XmlPullParserException {
ParsedPermissionImpl permission = new ParsedPermissionImpl();
String tag = "<" + parser.getName() + ">";
ParseResult<ParsedPermissionImpl> result;
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestPermissionTree);
try {
result = ParsedComponentUtils.parseComponent(
permission, tag, pkg, sa, useRoundIcon, input,
R.styleable.AndroidManifestPermissionTree_banner,
NOT_SET /*descriptionAttr*/,
R.styleable.AndroidManifestPermissionTree_icon,
R.styleable.AndroidManifestPermissionTree_label,
R.styleable.AndroidManifestPermissionTree_logo,
R.styleable.AndroidManifestPermissionTree_name,
R.styleable.AndroidManifestPermissionTree_roundIcon);
if (result.isError()) {
return input.error(result);
}
} finally {
sa.recycle();
}
int index = permission.getName().indexOf('.');
if (index > 0) {
index = permission.getName().indexOf('.', index + 1);
}
if (index < 0) {
return input.error("<permission-tree> name has less than three segments: "
+ permission.getName());
}
permission.setProtectionLevel(PermissionInfo.PROTECTION_NORMAL)
.setTree(true);
result = ComponentParseUtils.parseAllMetaData(pkg, res, parser, tag, permission, input);
if (result.isError()) {
return input.error(result);
}
return input.success(result.getResult());
}
@NonNull
public static ParseResult<ParsedPermissionGroup> parsePermissionGroup(ParsingPackage pkg,
Resources res, XmlResourceParser parser, boolean useRoundIcon, ParseInput input)
throws IOException, XmlPullParserException {
ParsedPermissionGroupImpl
permissionGroup = new ParsedPermissionGroupImpl();
String tag = "<" + parser.getName() + ">";
TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestPermissionGroup);
try {
ParseResult<ParsedPermissionGroupImpl> result = ParsedComponentUtils.parseComponent(
permissionGroup, tag, pkg, sa, useRoundIcon, input,
R.styleable.AndroidManifestPermissionGroup_banner,
R.styleable.AndroidManifestPermissionGroup_description,
R.styleable.AndroidManifestPermissionGroup_icon,
R.styleable.AndroidManifestPermissionGroup_label,
R.styleable.AndroidManifestPermissionGroup_logo,
R.styleable.AndroidManifestPermissionGroup_name,
R.styleable.AndroidManifestPermissionGroup_roundIcon);
if (result.isError()) {
return input.error(result);
}
// @formatter:off
permissionGroup.setRequestDetailRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_requestDetail, 0))
.setBackgroundRequestRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_backgroundRequest, 0))
.setBackgroundRequestDetailRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_backgroundRequestDetail, 0))
.setRequestRes(sa.getResourceId(R.styleable.AndroidManifestPermissionGroup_request, 0))
.setPriority(sa.getInt(R.styleable.AndroidManifestPermissionGroup_priority, 0))
.setFlags(sa.getInt(R.styleable.AndroidManifestPermissionGroup_permissionGroupFlags,0));
// @formatter:on
} finally {
sa.recycle();
}
ParseResult<ParsedPermissionGroupImpl> result = ComponentParseUtils.parseAllMetaData(pkg,
res, parser, tag, permissionGroup, input);
if (result.isError()) {
return input.error(result);
}
return input.success(result.getResult());
}
public static boolean isRuntime(@NonNull ParsedPermission permission) {
return getProtection(permission) == PermissionInfo.PROTECTION_DANGEROUS;
}
public static boolean isAppOp(@NonNull ParsedPermission permission) {
return (permission.getProtectionLevel() & PermissionInfo.PROTECTION_FLAG_APPOP) != 0;
}
@PermissionInfo.Protection
public static int getProtection(@NonNull ParsedPermission permission) {
return permission.getProtectionLevel() & PermissionInfo.PROTECTION_MASK_BASE;
}
public static int getProtectionFlags(@NonNull ParsedPermission permission) {
return permission.getProtectionLevel() & ~PermissionInfo.PROTECTION_MASK_BASE;
}
public static int calculateFootprint(@NonNull ParsedPermission permission) {
int size = permission.getName().length();
CharSequence nonLocalizedLabel = permission.getNonLocalizedLabel();
if (nonLocalizedLabel != null) {
size += nonLocalizedLabel.length();
}
return size;
}
/**
* Determines if a duplicate permission is malformed .i.e. defines different protection level
* or group.
*/
private static boolean isMalformedDuplicate(ParsedPermission p1, ParsedPermission p2) {
// Since a permission tree is also added as a permission with normal protection
// level, we need to skip if the parsedPermission is a permission tree.
if (p1 == null || p2 == null || p1.isTree() || p2.isTree()) {
return false;
}
if (p1.getProtectionLevel() != p2.getProtectionLevel()) {
return true;
}
if (!Objects.equals(p1.getGroup(), p2.getGroup())) {
return true;
}
return false;
}
/**
* @return {@code true} if the package declares malformed duplicate permissions.
*/
public static boolean declareDuplicatePermission(@NonNull ParsingPackage pkg) {
final List<ParsedPermission> permissions = pkg.getPermissions();
final int size = permissions.size();
if (size > 0) {
final ArrayMap<String, ParsedPermission> checkDuplicatePerm = new ArrayMap<>(size);
for (int i = 0; i < size; i++) {
final ParsedPermission parsedPermission = permissions.get(i);
final String name = parsedPermission.getName();
final ParsedPermission perm = checkDuplicatePerm.get(name);
if (isMalformedDuplicate(parsedPermission, perm)) {
// Fix for b/213323615
EventLog.writeEvent(0x534e4554, "213323615");
return true;
}
checkDuplicatePerm.put(name, parsedPermission);
}
}
return false;
}
}