| /* |
| * Copyright (C) 2006 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.permission; |
| |
| import static android.Manifest.permission.READ_EXTERNAL_STORAGE; |
| import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS; |
| import static android.content.pm.PermissionInfo.PROTECTION_NORMAL; |
| import static android.content.pm.PermissionInfo.PROTECTION_SIGNATURE; |
| import static android.content.pm.PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM; |
| |
| import static com.android.server.pm.Settings.ATTR_NAME; |
| import static com.android.server.pm.Settings.ATTR_PACKAGE; |
| import static com.android.server.pm.Settings.TAG_ITEM; |
| |
| import android.annotation.IntDef; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.content.pm.PackageParser; |
| import android.content.pm.PackageParser.Permission; |
| import android.content.pm.PermissionInfo; |
| import android.content.pm.Signature; |
| import android.os.UserHandle; |
| import android.util.Log; |
| import android.util.Slog; |
| |
| import com.android.server.pm.DumpState; |
| import com.android.server.pm.PackageManagerService; |
| import com.android.server.pm.PackageSetting; |
| import com.android.server.pm.PackageSettingBase; |
| |
| import org.xmlpull.v1.XmlPullParser; |
| import org.xmlpull.v1.XmlSerializer; |
| |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| |
| public final class BasePermission { |
| static final String TAG = "PackageManager"; |
| |
| public static final int TYPE_NORMAL = 0; |
| public static final int TYPE_BUILTIN = 1; |
| public static final int TYPE_DYNAMIC = 2; |
| @IntDef(value = { |
| TYPE_NORMAL, |
| TYPE_BUILTIN, |
| TYPE_DYNAMIC, |
| }) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface PermissionType {} |
| |
| @IntDef(value = { |
| PROTECTION_DANGEROUS, |
| PROTECTION_NORMAL, |
| PROTECTION_SIGNATURE, |
| PROTECTION_SIGNATURE_OR_SYSTEM, |
| }) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface ProtectionLevel {} |
| |
| final String name; |
| |
| final @PermissionType int type; |
| |
| String sourcePackageName; |
| |
| // TODO: Can we get rid of this? Seems we only use some signature info from the setting |
| PackageSettingBase sourcePackageSetting; |
| |
| int protectionLevel; |
| |
| PackageParser.Permission perm; |
| |
| PermissionInfo pendingPermissionInfo; |
| |
| /** UID that owns the definition of this permission */ |
| int uid; |
| |
| /** Additional GIDs given to apps granted this permission */ |
| private int[] gids; |
| |
| /** |
| * Flag indicating that {@link #gids} should be adjusted based on the |
| * {@link UserHandle} the granted app is running as. |
| */ |
| private boolean perUser; |
| |
| boolean usageInfoRequired; |
| |
| public BasePermission(String _name, String _sourcePackageName, @PermissionType int _type) { |
| name = _name; |
| sourcePackageName = _sourcePackageName; |
| type = _type; |
| // Default to most conservative protection level. |
| protectionLevel = PermissionInfo.PROTECTION_SIGNATURE; |
| } |
| |
| @Override |
| public String toString() { |
| return "BasePermission{" + Integer.toHexString(System.identityHashCode(this)) + " " + name |
| + "}"; |
| } |
| |
| public String getName() { |
| return name; |
| } |
| public int getProtectionLevel() { |
| return protectionLevel; |
| } |
| public String getSourcePackageName() { |
| return sourcePackageName; |
| } |
| public PackageSettingBase getSourcePackageSetting() { |
| return sourcePackageSetting; |
| } |
| public Signature[] getSourceSignatures() { |
| return sourcePackageSetting.getSignatures(); |
| } |
| public int getType() { |
| return type; |
| } |
| public int getUid() { |
| return uid; |
| } |
| public void setGids(int[] gids, boolean perUser) { |
| this.gids = gids; |
| this.perUser = perUser; |
| } |
| public void setPermission(@Nullable Permission perm) { |
| this.perm = perm; |
| } |
| public void setSourcePackageSetting(PackageSettingBase sourcePackageSetting) { |
| this.sourcePackageSetting = sourcePackageSetting; |
| } |
| |
| public int[] computeGids(int userId) { |
| if (perUser) { |
| final int[] userGids = new int[gids.length]; |
| for (int i = 0; i < gids.length; i++) { |
| userGids[i] = UserHandle.getUid(userId, gids[i]); |
| } |
| return userGids; |
| } else { |
| return gids; |
| } |
| } |
| |
| public int calculateFootprint(BasePermission perm) { |
| if (uid == perm.uid) { |
| return perm.name.length() + perm.perm.info.calculateFootprint(); |
| } |
| return 0; |
| } |
| |
| public boolean isPermission(Permission perm) { |
| return this.perm == perm; |
| } |
| |
| public boolean isDynamic() { |
| return type == TYPE_DYNAMIC; |
| } |
| |
| |
| public boolean isNormal() { |
| return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) |
| == PermissionInfo.PROTECTION_NORMAL; |
| } |
| public boolean isRuntime() { |
| return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) |
| == PermissionInfo.PROTECTION_DANGEROUS; |
| } |
| |
| public boolean isRemoved() { |
| return perm != null && perm.info != null |
| && (perm.info.flags & PermissionInfo.FLAG_REMOVED) != 0; |
| } |
| |
| public boolean isSignature() { |
| return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) == |
| PermissionInfo.PROTECTION_SIGNATURE; |
| } |
| |
| public boolean isAppOp() { |
| return (protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0; |
| } |
| public boolean isDevelopment() { |
| return isSignature() |
| && (protectionLevel & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0; |
| } |
| public boolean isInstaller() { |
| return (protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTALLER) != 0; |
| } |
| public boolean isInstant() { |
| return (protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0; |
| } |
| public boolean isOEM() { |
| return (protectionLevel & PermissionInfo.PROTECTION_FLAG_OEM) != 0; |
| } |
| public boolean isPre23() { |
| return (protectionLevel & PermissionInfo.PROTECTION_FLAG_PRE23) != 0; |
| } |
| public boolean isPreInstalled() { |
| return (protectionLevel & PermissionInfo.PROTECTION_FLAG_PREINSTALLED) != 0; |
| } |
| public boolean isPrivileged() { |
| return (protectionLevel & PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0; |
| } |
| public boolean isRuntimeOnly() { |
| return (protectionLevel & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) != 0; |
| } |
| public boolean isSetup() { |
| return (protectionLevel & PermissionInfo.PROTECTION_FLAG_SETUP) != 0; |
| } |
| public boolean isVerifier() { |
| return (protectionLevel & PermissionInfo.PROTECTION_FLAG_VERIFIER) != 0; |
| } |
| public boolean isVendorPrivileged() { |
| return (protectionLevel & PermissionInfo.PROTECTION_FLAG_VENDOR_PRIVILEGED) != 0; |
| } |
| public boolean isSystemTextClassifier() { |
| return (protectionLevel & PermissionInfo.PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER) |
| != 0; |
| } |
| public boolean isWellbeing() { |
| return (protectionLevel & PermissionInfo.PROTECTION_FLAG_WELLBEING) != 0; |
| } |
| public boolean isDocumenter() { |
| return (protectionLevel & PermissionInfo.PROTECTION_FLAG_DOCUMENTER) != 0; |
| } |
| public boolean isConfigurator() { |
| return (protectionLevel & PermissionInfo.PROTECTION_FLAG_CONFIGURATOR) |
| != 0; |
| } |
| |
| public void transfer(@NonNull String origPackageName, @NonNull String newPackageName) { |
| if (!origPackageName.equals(sourcePackageName)) { |
| return; |
| } |
| sourcePackageName = newPackageName; |
| sourcePackageSetting = null; |
| perm = null; |
| if (pendingPermissionInfo != null) { |
| pendingPermissionInfo.packageName = newPackageName; |
| } |
| uid = 0; |
| setGids(null, false); |
| } |
| |
| public boolean addToTree(@ProtectionLevel int protectionLevel, |
| @NonNull PermissionInfo info, @NonNull BasePermission tree) { |
| final boolean changed = |
| (this.protectionLevel != protectionLevel |
| || perm == null |
| || uid != tree.uid |
| || !perm.owner.equals(tree.perm.owner) |
| || !comparePermissionInfos(perm.info, info)); |
| this.protectionLevel = protectionLevel; |
| info = new PermissionInfo(info); |
| info.protectionLevel = protectionLevel; |
| perm = new PackageParser.Permission(tree.perm.owner, info); |
| perm.info.packageName = tree.perm.info.packageName; |
| uid = tree.uid; |
| return changed; |
| } |
| |
| public void updateDynamicPermission(Collection<BasePermission> permissionTrees) { |
| if (PackageManagerService.DEBUG_SETTINGS) Log.v(TAG, "Dynamic permission: name=" |
| + getName() + " pkg=" + getSourcePackageName() |
| + " info=" + pendingPermissionInfo); |
| if (sourcePackageSetting == null && pendingPermissionInfo != null) { |
| final BasePermission tree = findPermissionTree(permissionTrees, name); |
| if (tree != null && tree.perm != null) { |
| sourcePackageSetting = tree.sourcePackageSetting; |
| perm = new PackageParser.Permission(tree.perm.owner, |
| new PermissionInfo(pendingPermissionInfo)); |
| perm.info.packageName = tree.perm.info.packageName; |
| perm.info.name = name; |
| uid = tree.uid; |
| } |
| } |
| } |
| |
| static BasePermission createOrUpdate(@Nullable BasePermission bp, @NonNull Permission p, |
| @NonNull PackageParser.Package pkg, Collection<BasePermission> permissionTrees, |
| boolean chatty) { |
| final PackageSettingBase pkgSetting = (PackageSettingBase) pkg.mExtras; |
| // Allow system apps to redefine non-system permissions |
| if (bp != null && !Objects.equals(bp.sourcePackageName, p.info.packageName)) { |
| final boolean currentOwnerIsSystem = (bp.perm != null |
| && bp.perm.owner.isSystem()); |
| if (p.owner.isSystem()) { |
| if (bp.type == BasePermission.TYPE_BUILTIN && bp.perm == null) { |
| // It's a built-in permission and no owner, take ownership now |
| bp.sourcePackageSetting = pkgSetting; |
| bp.perm = p; |
| bp.uid = pkg.applicationInfo.uid; |
| bp.sourcePackageName = p.info.packageName; |
| p.info.flags |= PermissionInfo.FLAG_INSTALLED; |
| } else if (!currentOwnerIsSystem) { |
| String msg = "New decl " + p.owner + " of permission " |
| + p.info.name + " is system; overriding " + bp.sourcePackageName; |
| PackageManagerService.reportSettingsProblem(Log.WARN, msg); |
| bp = null; |
| } |
| } |
| } |
| if (bp == null) { |
| bp = new BasePermission(p.info.name, p.info.packageName, TYPE_NORMAL); |
| } |
| StringBuilder r = null; |
| if (bp.perm == null) { |
| if (bp.sourcePackageName == null |
| || bp.sourcePackageName.equals(p.info.packageName)) { |
| final BasePermission tree = findPermissionTree(permissionTrees, p.info.name); |
| if (tree == null |
| || tree.sourcePackageName.equals(p.info.packageName)) { |
| bp.sourcePackageSetting = pkgSetting; |
| bp.perm = p; |
| bp.uid = pkg.applicationInfo.uid; |
| bp.sourcePackageName = p.info.packageName; |
| p.info.flags |= PermissionInfo.FLAG_INSTALLED; |
| if (chatty) { |
| if (r == null) { |
| r = new StringBuilder(256); |
| } else { |
| r.append(' '); |
| } |
| r.append(p.info.name); |
| } |
| } else { |
| Slog.w(TAG, "Permission " + p.info.name + " from package " |
| + p.info.packageName + " ignored: base tree " |
| + tree.name + " is from package " |
| + tree.sourcePackageName); |
| } |
| } else { |
| Slog.w(TAG, "Permission " + p.info.name + " from package " |
| + p.info.packageName + " ignored: original from " |
| + bp.sourcePackageName); |
| } |
| } else if (chatty) { |
| if (r == null) { |
| r = new StringBuilder(256); |
| } else { |
| r.append(' '); |
| } |
| r.append("DUP:"); |
| r.append(p.info.name); |
| } |
| if (bp.perm == p) { |
| bp.protectionLevel = p.info.protectionLevel; |
| bp.usageInfoRequired = p.info.usageInfoRequired; |
| } |
| if (PackageManagerService.DEBUG_PACKAGE_SCANNING && r != null) { |
| Log.d(TAG, " Permissions: " + r); |
| } |
| return bp; |
| } |
| |
| static BasePermission enforcePermissionTree( |
| Collection<BasePermission> permissionTrees, String permName, int callingUid) { |
| if (permName != null) { |
| BasePermission bp = findPermissionTree(permissionTrees, permName); |
| if (bp != null) { |
| if (bp.uid == UserHandle.getAppId(callingUid)) { |
| return bp; |
| } |
| throw new SecurityException("Calling uid " + callingUid |
| + " is not allowed to add to permission tree " |
| + bp.name + " owned by uid " + bp.uid); |
| } |
| } |
| throw new SecurityException("No permission tree found for " + permName); |
| } |
| |
| public void enforceDeclaredUsedAndRuntimeOrDevelopment(PackageParser.Package pkg) { |
| final PackageSetting pkgSetting = (PackageSetting) pkg.mExtras; |
| final PermissionsState permsState = pkgSetting.getPermissionsState(); |
| int index = pkg.requestedPermissions.indexOf(name); |
| if (!permsState.hasRequestedPermission(name) && index == -1) { |
| throw new SecurityException("Package " + pkg.packageName |
| + " has not requested permission " + name); |
| } |
| if (!isRuntime() && !isDevelopment()) { |
| throw new SecurityException("Permission " + name |
| + " requested by " + pkg.packageName + " is not a changeable permission type"); |
| } |
| } |
| |
| private static BasePermission findPermissionTree( |
| Collection<BasePermission> permissionTrees, String permName) { |
| for (BasePermission bp : permissionTrees) { |
| if (permName.startsWith(bp.name) && |
| permName.length() > bp.name.length() && |
| permName.charAt(bp.name.length()) == '.') { |
| return bp; |
| } |
| } |
| return null; |
| } |
| |
| public @Nullable PermissionInfo generatePermissionInfo(@NonNull String groupName, int flags) { |
| if (groupName == null) { |
| if (perm == null || perm.info.group == null) { |
| return generatePermissionInfo(protectionLevel, flags); |
| } |
| } else { |
| if (perm != null && groupName.equals(perm.info.group)) { |
| return PackageParser.generatePermissionInfo(perm, flags); |
| } |
| } |
| return null; |
| } |
| |
| public @NonNull PermissionInfo generatePermissionInfo(int adjustedProtectionLevel, int flags) { |
| PermissionInfo permissionInfo; |
| if (perm != null) { |
| final boolean protectionLevelChanged = protectionLevel != adjustedProtectionLevel; |
| permissionInfo = PackageParser.generatePermissionInfo(perm, flags); |
| if (protectionLevelChanged && permissionInfo == perm.info) { |
| // if we return different protection level, don't use the cached info |
| permissionInfo = new PermissionInfo(permissionInfo); |
| permissionInfo.protectionLevel = adjustedProtectionLevel; |
| } |
| return permissionInfo; |
| } |
| permissionInfo = new PermissionInfo(); |
| permissionInfo.name = name; |
| permissionInfo.packageName = sourcePackageName; |
| permissionInfo.nonLocalizedLabel = name; |
| permissionInfo.protectionLevel = protectionLevel; |
| permissionInfo.usageInfoRequired = usageInfoRequired; |
| return permissionInfo; |
| } |
| |
| public static boolean readLPw(@NonNull Map<String, BasePermission> out, |
| @NonNull XmlPullParser parser) { |
| final String tagName = parser.getName(); |
| if (!tagName.equals(TAG_ITEM)) { |
| return false; |
| } |
| final String name = parser.getAttributeValue(null, ATTR_NAME); |
| final String sourcePackage = parser.getAttributeValue(null, ATTR_PACKAGE); |
| final String ptype = parser.getAttributeValue(null, "type"); |
| if (name == null || sourcePackage == null) { |
| PackageManagerService.reportSettingsProblem(Log.WARN, |
| "Error in package manager settings: permissions has" + " no name at " |
| + parser.getPositionDescription()); |
| return false; |
| } |
| final boolean dynamic = "dynamic".equals(ptype); |
| BasePermission bp = out.get(name); |
| // If the permission is builtin, do not clobber it. |
| if (bp == null || bp.type != TYPE_BUILTIN) { |
| bp = new BasePermission(name.intern(), sourcePackage, |
| dynamic ? TYPE_DYNAMIC : TYPE_NORMAL); |
| } |
| bp.protectionLevel = readInt(parser, null, "protection", |
| PermissionInfo.PROTECTION_NORMAL); |
| bp.protectionLevel = PermissionInfo.fixProtectionLevel(bp.protectionLevel); |
| bp.usageInfoRequired = readInt(parser, null, "usageInfoRequired", 0) != 0; |
| if (dynamic) { |
| final PermissionInfo pi = new PermissionInfo(); |
| pi.packageName = sourcePackage.intern(); |
| pi.name = name.intern(); |
| pi.icon = readInt(parser, null, "icon", 0); |
| pi.nonLocalizedLabel = parser.getAttributeValue(null, "label"); |
| pi.protectionLevel = bp.protectionLevel; |
| pi.usageInfoRequired = bp.usageInfoRequired; |
| bp.pendingPermissionInfo = pi; |
| } |
| out.put(bp.name, bp); |
| return true; |
| } |
| |
| private static int readInt(XmlPullParser parser, String ns, String name, int defValue) { |
| String v = parser.getAttributeValue(ns, name); |
| try { |
| if (v == null) { |
| return defValue; |
| } |
| return Integer.parseInt(v); |
| } catch (NumberFormatException e) { |
| PackageManagerService.reportSettingsProblem(Log.WARN, |
| "Error in package manager settings: attribute " + name |
| + " has bad integer value " + v + " at " |
| + parser.getPositionDescription()); |
| } |
| return defValue; |
| } |
| |
| public void writeLPr(@NonNull XmlSerializer serializer) throws IOException { |
| if (sourcePackageName == null) { |
| return; |
| } |
| serializer.startTag(null, TAG_ITEM); |
| serializer.attribute(null, ATTR_NAME, name); |
| serializer.attribute(null, ATTR_PACKAGE, sourcePackageName); |
| if (protectionLevel != PermissionInfo.PROTECTION_NORMAL) { |
| serializer.attribute(null, "protection", Integer.toString(protectionLevel)); |
| } |
| serializer.attribute(null, "usageInfoRequired", usageInfoRequired ? "1" : "0"); |
| if (type == BasePermission.TYPE_DYNAMIC) { |
| final PermissionInfo pi = perm != null ? perm.info : pendingPermissionInfo; |
| if (pi != null) { |
| serializer.attribute(null, "type", "dynamic"); |
| if (pi.icon != 0) { |
| serializer.attribute(null, "icon", Integer.toString(pi.icon)); |
| } |
| if (pi.nonLocalizedLabel != null) { |
| serializer.attribute(null, "label", pi.nonLocalizedLabel.toString()); |
| } |
| } |
| } |
| serializer.endTag(null, TAG_ITEM); |
| } |
| |
| private static boolean compareStrings(CharSequence s1, CharSequence s2) { |
| if (s1 == null) { |
| return s2 == null; |
| } |
| if (s2 == null) { |
| return false; |
| } |
| if (s1.getClass() != s2.getClass()) { |
| return false; |
| } |
| return s1.equals(s2); |
| } |
| |
| private static boolean comparePermissionInfos(PermissionInfo pi1, PermissionInfo pi2) { |
| if (pi1.icon != pi2.icon) return false; |
| if (pi1.logo != pi2.logo) return false; |
| if (pi1.protectionLevel != pi2.protectionLevel) return false; |
| if (!compareStrings(pi1.name, pi2.name)) return false; |
| if (!compareStrings(pi1.nonLocalizedLabel, pi2.nonLocalizedLabel)) return false; |
| // We'll take care of setting this one. |
| if (!compareStrings(pi1.packageName, pi2.packageName)) return false; |
| if (pi1.usageInfoRequired != pi2.usageInfoRequired) return false; |
| // These are not currently stored in settings. |
| //if (!compareStrings(pi1.group, pi2.group)) return false; |
| //if (!compareStrings(pi1.nonLocalizedDescription, pi2.nonLocalizedDescription)) return false; |
| //if (pi1.labelRes != pi2.labelRes) return false; |
| //if (pi1.descriptionRes != pi2.descriptionRes) return false; |
| return true; |
| } |
| |
| public boolean dumpPermissionsLPr(@NonNull PrintWriter pw, @NonNull String packageName, |
| @NonNull Set<String> permissionNames, boolean readEnforced, |
| boolean printedSomething, @NonNull DumpState dumpState) { |
| if (packageName != null && !packageName.equals(sourcePackageName)) { |
| return false; |
| } |
| if (permissionNames != null && !permissionNames.contains(name)) { |
| return false; |
| } |
| if (!printedSomething) { |
| if (dumpState.onTitlePrinted()) |
| pw.println(); |
| pw.println("Permissions:"); |
| printedSomething = true; |
| } |
| pw.print(" Permission ["); pw.print(name); pw.print("] ("); |
| pw.print(Integer.toHexString(System.identityHashCode(this))); |
| pw.println("):"); |
| pw.print(" sourcePackage="); pw.println(sourcePackageName); |
| pw.print(" uid="); pw.print(uid); |
| pw.print(" gids="); pw.print(Arrays.toString( |
| computeGids(UserHandle.USER_SYSTEM))); |
| pw.print(" type="); pw.print(type); |
| pw.print(" prot="); |
| pw.println(PermissionInfo.protectionToString(protectionLevel)); |
| if (perm != null) { |
| pw.print(" perm="); pw.println(perm); |
| if ((perm.info.flags & PermissionInfo.FLAG_INSTALLED) == 0 |
| || (perm.info.flags & PermissionInfo.FLAG_REMOVED) != 0) { |
| pw.print(" flags=0x"); pw.println(Integer.toHexString(perm.info.flags)); |
| } |
| } |
| if (sourcePackageSetting != null) { |
| pw.print(" packageSetting="); pw.println(sourcePackageSetting); |
| } |
| if (READ_EXTERNAL_STORAGE.equals(name)) { |
| pw.print(" enforced="); |
| pw.println(readEnforced); |
| } |
| pw.print(" usageInfoRequired="); |
| pw.println(usageInfoRequired); |
| return true; |
| } |
| } |