blob: 40efb7cd96d70e153242bd79b8e4dddab207de07 [file] [log] [blame]
/*
* Copyright (C) 2019 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.om;
import android.annotation.NonNull;
import android.content.om.OverlayInfo;
import android.content.om.OverlayableInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.net.Uri;
import android.os.Process;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Pair;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.server.SystemConfig;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* Performs verification that a calling UID can act on a target package's overlayable.
*
* @hide
*/
public class OverlayActorEnforcer {
// By default, the reason is not logged to prevent leaks of why it failed
private static final boolean DEBUG_REASON = false;
private final OverlayableInfoCallback mOverlayableCallback;
/**
* @return nullable actor result with {@link ActorState} failure status
*/
static Pair<String, ActorState> getPackageNameForActor(String actorUriString,
Map<String, Map<String, String>> namedActors) {
Uri actorUri = Uri.parse(actorUriString);
String actorScheme = actorUri.getScheme();
List<String> actorPathSegments = actorUri.getPathSegments();
if (!"overlay".equals(actorScheme) || CollectionUtils.size(actorPathSegments) != 1) {
return Pair.create(null, ActorState.INVALID_OVERLAYABLE_ACTOR_NAME);
}
if (namedActors.isEmpty()) {
return Pair.create(null, ActorState.NO_NAMED_ACTORS);
}
String actorNamespace = actorUri.getAuthority();
Map<String, String> namespace = namedActors.get(actorNamespace);
if (namespace == null) {
return Pair.create(null, ActorState.MISSING_NAMESPACE);
}
String actorName = actorPathSegments.get(0);
String packageName = namespace.get(actorName);
if (TextUtils.isEmpty(packageName)) {
return Pair.create(null, ActorState.MISSING_ACTOR_NAME);
}
return Pair.create(packageName, ActorState.ALLOWED);
}
public OverlayActorEnforcer(@NonNull OverlayableInfoCallback overlayableCallback) {
mOverlayableCallback = overlayableCallback;
}
void enforceActor(@NonNull OverlayInfo overlayInfo, @NonNull String methodName,
int callingUid, int userId) throws SecurityException {
ActorState actorState = isAllowedActor(methodName, overlayInfo, callingUid, userId);
if (actorState == ActorState.ALLOWED) {
return;
}
String targetOverlayableName = overlayInfo.targetOverlayableName;
throw new SecurityException("UID" + callingUid + " is not allowed to call "
+ methodName + " for "
+ (TextUtils.isEmpty(targetOverlayableName) ? "" : (targetOverlayableName + " in "))
+ overlayInfo.targetPackageName + (DEBUG_REASON ? (" because " + actorState) : "")
);
}
/**
* An actor is valid if any of the following is true:
* - is {@link Process#ROOT_UID}, {@link Process#SYSTEM_UID}
* - is the target overlay package
* - has the CHANGE_OVERLAY_PACKAGES permission and an actor is not defined
* - is the same the as the package defined in {@link SystemConfig#getNamedActors()} for a given
* namespace and actor name
*
* @return true if the actor is allowed to act on the target overlayInfo
*/
private ActorState isAllowedActor(String methodName, OverlayInfo overlayInfo,
int callingUid, int userId) {
switch (callingUid) {
case Process.ROOT_UID:
case Process.SYSTEM_UID:
return ActorState.ALLOWED;
}
String[] callingPackageNames = mOverlayableCallback.getPackagesForUid(callingUid);
if (ArrayUtils.isEmpty(callingPackageNames)) {
return ActorState.NO_PACKAGES_FOR_UID;
}
// A target is always an allowed actor for itself
String targetPackageName = overlayInfo.targetPackageName;
if (ArrayUtils.contains(callingPackageNames, targetPackageName)) {
return ActorState.ALLOWED;
}
String targetOverlayableName = overlayInfo.targetOverlayableName;
if (TextUtils.isEmpty(targetOverlayableName)) {
try {
if (mOverlayableCallback.doesTargetDefineOverlayable(targetPackageName, userId)) {
return ActorState.MISSING_TARGET_OVERLAYABLE_NAME;
} else {
// If there's no overlayable defined, fallback to the legacy permission check
try {
mOverlayableCallback.enforcePermission(
android.Manifest.permission.CHANGE_OVERLAY_PACKAGES, methodName);
// If the previous method didn't throw, check passed
return ActorState.ALLOWED;
} catch (SecurityException e) {
return ActorState.MISSING_LEGACY_PERMISSION;
}
}
} catch (RemoteException | IOException e) {
return ActorState.ERROR_READING_OVERLAYABLE;
}
}
OverlayableInfo targetOverlayable;
try {
targetOverlayable = mOverlayableCallback.getOverlayableForTarget(targetPackageName,
targetOverlayableName, userId);
} catch (IOException e) {
return ActorState.UNABLE_TO_GET_TARGET;
}
if (targetOverlayable == null) {
return ActorState.MISSING_OVERLAYABLE;
}
String actor = targetOverlayable.actor;
if (TextUtils.isEmpty(actor)) {
// If there's no actor defined, fallback to the legacy permission check
try {
mOverlayableCallback.enforcePermission(
android.Manifest.permission.CHANGE_OVERLAY_PACKAGES, methodName);
// If the previous method didn't throw, check passed
return ActorState.ALLOWED;
} catch (SecurityException e) {
return ActorState.MISSING_LEGACY_PERMISSION;
}
}
Map<String, Map<String, String>> namedActors = mOverlayableCallback.getNamedActors();
Pair<String, ActorState> actorUriPair = getPackageNameForActor(actor, namedActors);
ActorState actorUriState = actorUriPair.second;
if (actorUriState != ActorState.ALLOWED) {
return actorUriState;
}
String packageName = actorUriPair.first;
PackageInfo packageInfo = mOverlayableCallback.getPackageInfo(packageName, userId);
if (packageInfo == null) {
return ActorState.MISSING_APP_INFO;
}
ApplicationInfo appInfo = packageInfo.applicationInfo;
if (appInfo == null) {
return ActorState.MISSING_APP_INFO;
}
// Currently only pre-installed apps can be actors
if (!appInfo.isSystemApp() && !appInfo.isUpdatedSystemApp()) {
return ActorState.ACTOR_NOT_PREINSTALLED;
}
if (ArrayUtils.contains(callingPackageNames, packageName)) {
return ActorState.ALLOWED;
}
return ActorState.INVALID_ACTOR;
}
/**
* For easier logging/debugging, a set of all possible failure/success states when running
* enforcement.
*/
public enum ActorState {
ALLOWED,
INVALID_ACTOR,
MISSING_NAMESPACE,
MISSING_PACKAGE,
MISSING_APP_INFO,
ACTOR_NOT_PREINSTALLED,
NO_PACKAGES_FOR_UID,
MISSING_ACTOR_NAME,
ERROR_READING_OVERLAYABLE,
MISSING_TARGET_OVERLAYABLE_NAME,
MISSING_OVERLAYABLE,
INVALID_OVERLAYABLE_ACTOR_NAME,
NO_NAMED_ACTORS,
UNABLE_TO_GET_TARGET,
MISSING_LEGACY_PERMISSION
}
}