Overlayable actor enforcement
Validates that the caller of an OverlayManager API that mutates state
is actually allowed to act on the target as defined in the target's
overlayable tag.
<overlayable name="MyResources" actor="namespace/name">
An actor is valid if any of the following is true:
- is root/system
- is the target overlay package
- has the CHANGE_OVERLAY_PACKAGES permission and an actor is not defined
- is the same package name as the sole resolved Activity for the actor specified
in the overlayable definition, with only pre-installed, namespaced actors
currently supported
Bug: 119442583
Bug: 135052950
Test: atest SystemConfigNamedActorTest
Test: atest com.android.server.om
Change-Id: If56b9e8366852eaef84f6bb25c3e6871eaa3f219
This commit is contained in:
120
core/java/android/content/om/OverlayableInfo.java
Normal file
120
core/java/android/content/om/OverlayableInfo.java
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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 android.content.om;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
|
||||
import com.android.internal.util.DataClass;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Immutable info on an overlayable defined inside a target package.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@DataClass(genSetters = false, genEqualsHashCode = true, genHiddenConstructor = true)
|
||||
public final class OverlayableInfo {
|
||||
|
||||
/**
|
||||
* The "name" attribute of the overlayable tag. Used to identify the set of resources overlaid.
|
||||
*/
|
||||
@NonNull
|
||||
public final String name;
|
||||
|
||||
/**
|
||||
* The "actor" attribute of the overlayable tag. Used to signal which apps are allowed to
|
||||
* modify overlay state for this overlayable.
|
||||
*/
|
||||
@Nullable
|
||||
public final String actor;
|
||||
|
||||
// CHECKSTYLE:OFF Generated code
|
||||
//
|
||||
|
||||
|
||||
|
||||
// Code below generated by codegen v1.0.3.
|
||||
//
|
||||
// DO NOT MODIFY!
|
||||
// CHECKSTYLE:OFF Generated code
|
||||
//
|
||||
// To regenerate run:
|
||||
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/om/OverlayableInfo.java
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new OverlayableInfo.
|
||||
*
|
||||
* @param name
|
||||
* The "name" attribute of the overlayable tag. Used to identify the set of resources overlaid.
|
||||
* @param actor
|
||||
* The "actor" attribute of the overlayable tag. Used to signal which apps are allowed to
|
||||
* modify overlay state for this overlayable.
|
||||
* @hide
|
||||
*/
|
||||
@DataClass.Generated.Member
|
||||
public OverlayableInfo(
|
||||
@NonNull String name,
|
||||
@Nullable String actor) {
|
||||
this.name = name;
|
||||
com.android.internal.util.AnnotationValidations.validate(
|
||||
NonNull.class, null, name);
|
||||
this.actor = actor;
|
||||
|
||||
// onConstructed(); // You can define this method to get a callback
|
||||
}
|
||||
|
||||
@Override
|
||||
@DataClass.Generated.Member
|
||||
public boolean equals(Object o) {
|
||||
// You can override field equality logic by defining either of the methods like:
|
||||
// boolean fieldNameEquals(OverlayableInfo other) { ... }
|
||||
// boolean fieldNameEquals(FieldType otherValue) { ... }
|
||||
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
@SuppressWarnings("unchecked")
|
||||
OverlayableInfo that = (OverlayableInfo) o;
|
||||
//noinspection PointlessBooleanExpression
|
||||
return true
|
||||
&& Objects.equals(name, that.name)
|
||||
&& Objects.equals(actor, that.actor);
|
||||
}
|
||||
|
||||
@Override
|
||||
@DataClass.Generated.Member
|
||||
public int hashCode() {
|
||||
// You can override field hashCode logic by defining methods like:
|
||||
// int fieldNameHashCode() { ... }
|
||||
|
||||
int _hash = 1;
|
||||
_hash = 31 * _hash + Objects.hashCode(name);
|
||||
_hash = 31 * _hash + Objects.hashCode(actor);
|
||||
return _hash;
|
||||
}
|
||||
|
||||
@DataClass.Generated(
|
||||
time = 1570059850579L,
|
||||
codegenVersion = "1.0.3",
|
||||
sourceFile = "frameworks/base/core/java/android/content/om/OverlayableInfo.java",
|
||||
inputSignatures = "public final @android.annotation.NonNull java.lang.String name\npublic final @android.annotation.Nullable java.lang.String actor\nclass OverlayableInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=false, genEqualsHashCode=true, genHiddenConstructor=true)")
|
||||
@Deprecated
|
||||
private void __metadata() {}
|
||||
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package android.content.res;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.UnsupportedAppUsage;
|
||||
import android.content.om.OverlayableInfo;
|
||||
import android.content.res.loader.ResourcesProvider;
|
||||
import android.text.TextUtils;
|
||||
|
||||
@@ -254,6 +255,17 @@ public final class ApkAssets {
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Nullable
|
||||
public OverlayableInfo getOverlayableInfo(String overlayableName) throws IOException {
|
||||
return nativeGetOverlayableInfo(mNativePtr, overlayableName);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public boolean definesOverlayable() throws IOException {
|
||||
return nativeDefinesOverlayable(mNativePtr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false if the underlying APK was changed since this ApkAssets was loaded.
|
||||
*/
|
||||
@@ -305,4 +317,7 @@ public final class ApkAssets {
|
||||
private static native long nativeGetStringBlock(long ptr);
|
||||
private static native boolean nativeIsUpToDate(long ptr);
|
||||
private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException;
|
||||
private static native @Nullable OverlayableInfo nativeGetOverlayableInfo(long ptr,
|
||||
String overlayableName) throws IOException;
|
||||
private static native boolean nativeDefinesOverlayable(long ptr) throws IOException;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.server;
|
||||
|
||||
import static com.android.internal.util.ArrayUtils.appendInt;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.pm.FeatureInfo;
|
||||
@@ -221,6 +222,12 @@ public class SystemConfig {
|
||||
private ArrayMap<String, Set<String>> mPackageToUserTypeWhitelist = new ArrayMap<>();
|
||||
private ArrayMap<String, Set<String>> mPackageToUserTypeBlacklist = new ArrayMap<>();
|
||||
|
||||
/**
|
||||
* Map of system pre-defined, uniquely named actors; keys are namespace,
|
||||
* value maps actor name to package name.
|
||||
*/
|
||||
private ArrayMap<String, ArrayMap<String, String>> mNamedActors = null;
|
||||
|
||||
public static SystemConfig getInstance() {
|
||||
if (!isSystemProcess()) {
|
||||
Slog.wtf(TAG, "SystemConfig is being accessed by a process other than "
|
||||
@@ -398,12 +405,17 @@ public class SystemConfig {
|
||||
return r;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Map<String, ? extends Map<String, String>> getNamedActors() {
|
||||
return mNamedActors != null ? mNamedActors : Collections.emptyMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Only use for testing. Do NOT use in production code.
|
||||
* @param readPermissions false to create an empty SystemConfig; true to read the permissions.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
protected SystemConfig(boolean readPermissions) {
|
||||
public SystemConfig(boolean readPermissions) {
|
||||
if (readPermissions) {
|
||||
Slog.w(TAG, "Constructing a test SystemConfig");
|
||||
readAllPermissions();
|
||||
@@ -1028,6 +1040,44 @@ public class SystemConfig {
|
||||
readInstallInUserType(parser,
|
||||
mPackageToUserTypeWhitelist, mPackageToUserTypeBlacklist);
|
||||
} break;
|
||||
case "named-actor": {
|
||||
String namespace = TextUtils.safeIntern(
|
||||
parser.getAttributeValue(null, "namespace"));
|
||||
String actorName = parser.getAttributeValue(null, "name");
|
||||
String pkgName = TextUtils.safeIntern(
|
||||
parser.getAttributeValue(null, "package"));
|
||||
if (TextUtils.isEmpty(namespace)) {
|
||||
Slog.wtf(TAG, "<" + name + "> without namespace in " + permFile
|
||||
+ " at " + parser.getPositionDescription());
|
||||
} else if (TextUtils.isEmpty(actorName)) {
|
||||
Slog.wtf(TAG, "<" + name + "> without actor name in " + permFile
|
||||
+ " at " + parser.getPositionDescription());
|
||||
} else if (TextUtils.isEmpty(pkgName)) {
|
||||
Slog.wtf(TAG, "<" + name + "> without package name in " + permFile
|
||||
+ " at " + parser.getPositionDescription());
|
||||
} else if ("android".equalsIgnoreCase(namespace)) {
|
||||
throw new IllegalStateException("Defining " + actorName + " as "
|
||||
+ pkgName + " for the android namespace is not allowed");
|
||||
} else {
|
||||
if (mNamedActors == null) {
|
||||
mNamedActors = new ArrayMap<>();
|
||||
}
|
||||
|
||||
ArrayMap<String, String> nameToPkgMap = mNamedActors.get(namespace);
|
||||
if (nameToPkgMap == null) {
|
||||
nameToPkgMap = new ArrayMap<>();
|
||||
mNamedActors.put(namespace, nameToPkgMap);
|
||||
} else if (nameToPkgMap.containsKey(actorName)) {
|
||||
String existing = nameToPkgMap.get(actorName);
|
||||
throw new IllegalStateException("Duplicate actor definition for "
|
||||
+ namespace + "/" + actorName
|
||||
+ "; defined as both " + existing + " and " + pkgName);
|
||||
}
|
||||
|
||||
nameToPkgMap.put(actorName, pkgName);
|
||||
}
|
||||
XmlUtils.skipCurrentTag(parser);
|
||||
} break;
|
||||
default: {
|
||||
Slog.w(TAG, "Tag " + name + " is unknown in "
|
||||
+ permFile + " at " + parser.getPositionDescription());
|
||||
|
||||
@@ -194,6 +194,59 @@ static jlong NativeOpenXml(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring fil
|
||||
return reinterpret_cast<jlong>(xml_tree.release());
|
||||
}
|
||||
|
||||
static jobject NativeGetOverlayableInfo(JNIEnv* env, jclass /*clazz*/, jlong ptr,
|
||||
jstring overlayable_name) {
|
||||
const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr);
|
||||
|
||||
const auto& packages = apk_assets->GetLoadedArsc()->GetPackages();
|
||||
if (packages.empty()) {
|
||||
jniThrowException(env, "java/io/IOException", "Error reading overlayable from APK");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO(b/119899133): Convert this to a search for the info rather than assuming it's at index 0
|
||||
const auto& overlayable_map = packages[0]->GetOverlayableMap();
|
||||
if (overlayable_map.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto overlayable_name_native = std::string(env->GetStringUTFChars(overlayable_name, NULL));
|
||||
auto actor = overlayable_map.find(overlayable_name_native);
|
||||
if (actor == overlayable_map.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
jstring actor_string = env->NewStringUTF(actor->first.c_str());
|
||||
if (env->ExceptionCheck() || actor_string == nullptr) {
|
||||
jniThrowException(env, "java/io/IOException", "Error reading overlayable from APK");
|
||||
return 0;
|
||||
}
|
||||
|
||||
jclass overlayable_class = env->FindClass("android/content/om/OverlayableInfo");
|
||||
jmethodID overlayable_constructor = env->GetMethodID(overlayable_class, "<init>",
|
||||
"(Ljava/lang/String;Ljava/lang/String;I)V");
|
||||
return env->NewObject(
|
||||
overlayable_class,
|
||||
overlayable_constructor,
|
||||
overlayable_name,
|
||||
actor_string
|
||||
);
|
||||
}
|
||||
|
||||
static jboolean NativeDefinesOverlayable(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
|
||||
const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr);
|
||||
|
||||
const auto& packages = apk_assets->GetLoadedArsc()->GetPackages();
|
||||
if (packages.empty()) {
|
||||
// Must throw to prevent bypass by returning false
|
||||
jniThrowException(env, "java/io/IOException", "Error reading overlayable from APK");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto& overlayable_infos = packages[0]->GetOverlayableMap();
|
||||
return overlayable_infos.empty() ? JNI_FALSE : JNI_TRUE;
|
||||
}
|
||||
|
||||
// JNI registration.
|
||||
static const JNINativeMethod gApkAssetsMethods[] = {
|
||||
{"nativeLoad", "(Ljava/lang/String;ZZZZ)J", (void*)NativeLoad},
|
||||
@@ -208,6 +261,9 @@ static const JNINativeMethod gApkAssetsMethods[] = {
|
||||
{"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock},
|
||||
{"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate},
|
||||
{"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml},
|
||||
{"nativeGetOverlayableInfo", "(JLjava/lang/String;)Landroid/content/om/OverlayableInfo;",
|
||||
(void*)NativeGetOverlayableInfo},
|
||||
{"nativeDefinesOverlayable", "(J)Z", (void*)NativeDefinesOverlayable},
|
||||
};
|
||||
|
||||
int register_android_content_res_ApkAssets(JNIEnv* env) {
|
||||
|
||||
@@ -273,6 +273,8 @@ class LoadedPackage {
|
||||
ByteBucketArray<uint32_t> resource_ids_;
|
||||
std::vector<DynamicPackageEntry> dynamic_package_map_;
|
||||
std::vector<const std::pair<OverlayableInfo, std::unordered_set<uint32_t>>> overlayable_infos_;
|
||||
|
||||
// A map of overlayable name to actor
|
||||
std::unordered_map<std::string, std::string> overlayable_map_;
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
* 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.annotation.Nullable;
|
||||
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 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 {
|
||||
|
||||
private final VerifyCallback mVerifyCallback;
|
||||
|
||||
public OverlayActorEnforcer(@NonNull VerifyCallback verifyCallback) {
|
||||
mVerifyCallback = verifyCallback;
|
||||
}
|
||||
|
||||
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 + " 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 = mVerifyCallback.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 (mVerifyCallback.doesTargetDefineOverlayable(targetPackageName, userId)) {
|
||||
return ActorState.MISSING_TARGET_OVERLAYABLE_NAME;
|
||||
} else {
|
||||
// If there's no overlayable defined, fallback to the legacy permission check
|
||||
try {
|
||||
mVerifyCallback.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 = mVerifyCallback.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 {
|
||||
mVerifyCallback.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, ? extends Map<String, String>> namedActors = mVerifyCallback.getNamedActors();
|
||||
if (namedActors.isEmpty()) {
|
||||
return ActorState.NO_NAMED_ACTORS;
|
||||
}
|
||||
|
||||
Uri actorUri = Uri.parse(actor);
|
||||
|
||||
String actorScheme = actorUri.getScheme();
|
||||
List<String> actorPathSegments = actorUri.getPathSegments();
|
||||
if (!"overlay".equals(actorScheme) || CollectionUtils.size(actorPathSegments) != 1) {
|
||||
return ActorState.INVALID_OVERLAYABLE_ACTOR_NAME;
|
||||
}
|
||||
|
||||
String actorNamespace = actorUri.getAuthority();
|
||||
Map<String, String> namespace = namedActors.get(actorNamespace);
|
||||
if (namespace == null) {
|
||||
return ActorState.MISSING_NAMESPACE;
|
||||
}
|
||||
|
||||
String actorName = actorPathSegments.get(0);
|
||||
String packageName = namespace.get(actorName);
|
||||
if (TextUtils.isEmpty(packageName)) {
|
||||
return ActorState.MISSING_ACTOR_NAME;
|
||||
}
|
||||
|
||||
PackageInfo packageInfo = mVerifyCallback.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.
|
||||
*/
|
||||
private 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
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate to the system for querying information about packages.
|
||||
*/
|
||||
public interface VerifyCallback {
|
||||
|
||||
/**
|
||||
* Read from the APK and AndroidManifest of a package to return the overlayable defined for
|
||||
* a given name.
|
||||
*
|
||||
* @throws IOException if the target can't be read
|
||||
*/
|
||||
@Nullable
|
||||
OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
|
||||
@Nullable String targetOverlayableName, int userId)
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* @see android.content.pm.PackageManager#getPackagesForUid(int)
|
||||
*/
|
||||
@Nullable
|
||||
String[] getPackagesForUid(int uid);
|
||||
|
||||
/**
|
||||
* @param userId user to filter package visibility by
|
||||
* @see android.content.pm.PackageManager#getPackageInfo(String, int)
|
||||
*/
|
||||
@Nullable
|
||||
PackageInfo getPackageInfo(@NonNull String packageName, int userId);
|
||||
|
||||
/**
|
||||
* @return map of system pre-defined, uniquely named actors; keys are namespace,
|
||||
* value maps actor name to package name
|
||||
*/
|
||||
@NonNull
|
||||
Map<String, ? extends Map<String, String>> getNamedActors();
|
||||
|
||||
/**
|
||||
* @return true if the target package has declared an overlayable
|
||||
*/
|
||||
boolean doesTargetDefineOverlayable(String targetPackageName, int userId)
|
||||
throws RemoteException, IOException;
|
||||
|
||||
/**
|
||||
* @throws SecurityException containing message if the caller doesn't have the given
|
||||
* permission
|
||||
*/
|
||||
void enforcePermission(String permission, String message) throws SecurityException;
|
||||
}
|
||||
}
|
||||
@@ -39,10 +39,12 @@ import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.om.IOverlayManager;
|
||||
import android.content.om.OverlayInfo;
|
||||
import android.content.om.OverlayableInfo;
|
||||
import android.content.pm.IPackageManager;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.content.res.ApkAssets;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.Environment;
|
||||
@@ -63,6 +65,7 @@ import android.util.SparseArray;
|
||||
import com.android.server.FgThread;
|
||||
import com.android.server.IoThread;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.SystemConfig;
|
||||
import com.android.server.SystemService;
|
||||
import com.android.server.pm.UserManagerService;
|
||||
|
||||
@@ -229,6 +232,8 @@ public final class OverlayManagerService extends SystemService {
|
||||
|
||||
private final OverlayManagerServiceImpl mImpl;
|
||||
|
||||
private final OverlayActorEnforcer mActorEnforcer;
|
||||
|
||||
private final AtomicBoolean mPersistSettingsScheduled = new AtomicBoolean(false);
|
||||
|
||||
public OverlayManagerService(@NonNull final Context context) {
|
||||
@@ -237,12 +242,13 @@ public final class OverlayManagerService extends SystemService {
|
||||
traceBegin(TRACE_TAG_RRO, "OMS#OverlayManagerService");
|
||||
mSettingsFile = new AtomicFile(
|
||||
new File(Environment.getDataSystemDirectory(), "overlays.xml"), "overlays");
|
||||
mPackageManager = new PackageManagerHelper();
|
||||
mPackageManager = new PackageManagerHelper(context);
|
||||
mUserManager = UserManagerService.getInstance();
|
||||
IdmapManager im = new IdmapManager(mPackageManager);
|
||||
mSettings = new OverlayManagerSettings();
|
||||
mImpl = new OverlayManagerServiceImpl(mPackageManager, im, mSettings,
|
||||
getDefaultOverlayPackages(), new OverlayChangeListener());
|
||||
mActorEnforcer = new OverlayActorEnforcer(mPackageManager);
|
||||
|
||||
final IntentFilter packageFilter = new IntentFilter();
|
||||
packageFilter.addAction(ACTION_PACKAGE_ADDED);
|
||||
@@ -581,7 +587,7 @@ public final class OverlayManagerService extends SystemService {
|
||||
int userId) throws RemoteException {
|
||||
try {
|
||||
traceBegin(TRACE_TAG_RRO, "OMS#setEnabled " + packageName + " " + enable);
|
||||
enforceChangeOverlayPackagesPermission("setEnabled");
|
||||
enforceActor(packageName, "setEnabled", userId);
|
||||
userId = handleIncomingUser(userId, "setEnabled");
|
||||
if (packageName == null) {
|
||||
return false;
|
||||
@@ -605,7 +611,7 @@ public final class OverlayManagerService extends SystemService {
|
||||
int userId) throws RemoteException {
|
||||
try {
|
||||
traceBegin(TRACE_TAG_RRO, "OMS#setEnabledExclusive " + packageName + " " + enable);
|
||||
enforceChangeOverlayPackagesPermission("setEnabledExclusive");
|
||||
enforceActor(packageName, "setEnabledExclusive", userId);
|
||||
userId = handleIncomingUser(userId, "setEnabledExclusive");
|
||||
if (packageName == null || !enable) {
|
||||
return false;
|
||||
@@ -630,7 +636,7 @@ public final class OverlayManagerService extends SystemService {
|
||||
throws RemoteException {
|
||||
try {
|
||||
traceBegin(TRACE_TAG_RRO, "OMS#setEnabledExclusiveInCategory " + packageName);
|
||||
enforceChangeOverlayPackagesPermission("setEnabledExclusiveInCategory");
|
||||
enforceActor(packageName, "setEnabledExclusiveInCategory", userId);
|
||||
userId = handleIncomingUser(userId, "setEnabledExclusiveInCategory");
|
||||
if (packageName == null) {
|
||||
return false;
|
||||
@@ -656,7 +662,7 @@ public final class OverlayManagerService extends SystemService {
|
||||
try {
|
||||
traceBegin(TRACE_TAG_RRO, "OMS#setPriority " + packageName + " "
|
||||
+ parentPackageName);
|
||||
enforceChangeOverlayPackagesPermission("setPriority");
|
||||
enforceActor(packageName, "setPriority", userId);
|
||||
userId = handleIncomingUser(userId, "setPriority");
|
||||
if (packageName == null || parentPackageName == null) {
|
||||
return false;
|
||||
@@ -680,7 +686,7 @@ public final class OverlayManagerService extends SystemService {
|
||||
throws RemoteException {
|
||||
try {
|
||||
traceBegin(TRACE_TAG_RRO, "OMS#setHighestPriority " + packageName);
|
||||
enforceChangeOverlayPackagesPermission("setHighestPriority");
|
||||
enforceActor(packageName, "setHighestPriority", userId);
|
||||
userId = handleIncomingUser(userId, "setHighestPriority");
|
||||
if (packageName == null) {
|
||||
return false;
|
||||
@@ -704,7 +710,7 @@ public final class OverlayManagerService extends SystemService {
|
||||
throws RemoteException {
|
||||
try {
|
||||
traceBegin(TRACE_TAG_RRO, "OMS#setLowestPriority " + packageName);
|
||||
enforceChangeOverlayPackagesPermission("setLowestPriority");
|
||||
enforceActor(packageName, "setLowestPriority", userId);
|
||||
userId = handleIncomingUser(userId, "setLowestPriority");
|
||||
if (packageName == null) {
|
||||
return false;
|
||||
@@ -750,7 +756,7 @@ public final class OverlayManagerService extends SystemService {
|
||||
return;
|
||||
}
|
||||
|
||||
enforceChangeOverlayPackagesPermission("invalidateCachesForOverlay");
|
||||
enforceActor(packageName, "invalidateCachesForOverlay", userId);
|
||||
userId = handleIncomingUser(userId, "invalidateCachesForOverlay");
|
||||
final long ident = Binder.clearCallingIdentity();
|
||||
try {
|
||||
@@ -860,18 +866,6 @@ public final class OverlayManagerService extends SystemService {
|
||||
Binder.getCallingUid(), userId, false, true, message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforce that the caller holds the CHANGE_OVERLAY_PACKAGES permission (or is
|
||||
* system or root).
|
||||
*
|
||||
* @param message used as message if SecurityException is thrown
|
||||
* @throws SecurityException if the permission check fails
|
||||
*/
|
||||
private void enforceChangeOverlayPackagesPermission(@NonNull final String message) {
|
||||
getContext().enforceCallingOrSelfPermission(
|
||||
android.Manifest.permission.CHANGE_OVERLAY_PACKAGES, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforce that the caller holds the DUMP permission (or is system or root).
|
||||
*
|
||||
@@ -881,6 +875,13 @@ public final class OverlayManagerService extends SystemService {
|
||||
private void enforceDumpPermission(@NonNull final String message) {
|
||||
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, message);
|
||||
}
|
||||
|
||||
private void enforceActor(String packageName, String methodName, int userId)
|
||||
throws SecurityException {
|
||||
OverlayInfo overlayInfo = mImpl.getOverlayInfo(packageName, userId);
|
||||
int callingUid = Binder.getCallingUid();
|
||||
mActorEnforcer.enforceActor(overlayInfo, methodName, callingUid, userId);
|
||||
}
|
||||
};
|
||||
|
||||
private final class OverlayChangeListener
|
||||
@@ -1035,9 +1036,16 @@ public final class OverlayManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PackageManagerHelper implements
|
||||
OverlayManagerServiceImpl.PackageManagerHelper {
|
||||
/**
|
||||
* Delegate for {@link android.content.pm.PackageManager} and {@link PackageManagerInternal}
|
||||
* functionality, separated for easy testing.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final class PackageManagerHelper implements
|
||||
OverlayManagerServiceImpl.PackageManagerHelper, OverlayActorEnforcer.VerifyCallback {
|
||||
|
||||
private final Context mContext;
|
||||
private final IPackageManager mPackageManager;
|
||||
private final PackageManagerInternal mPackageManagerInternal;
|
||||
|
||||
@@ -1048,11 +1056,14 @@ public final class OverlayManagerService extends SystemService {
|
||||
// behind until all pending intents have been processed.
|
||||
private final SparseArray<HashMap<String, PackageInfo>> mCache = new SparseArray<>();
|
||||
|
||||
PackageManagerHelper() {
|
||||
PackageManagerHelper(Context context) {
|
||||
mContext = context;
|
||||
mPackageManager = getPackageManager();
|
||||
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
|
||||
}
|
||||
|
||||
// TODO(b/143096091): Remove PackageInfo cache so that PackageManager is always queried
|
||||
// to enforce visibility/other permission checks
|
||||
public PackageInfo getPackageInfo(@NonNull final String packageName, final int userId,
|
||||
final boolean useCache) {
|
||||
if (useCache) {
|
||||
@@ -1075,7 +1086,19 @@ public final class OverlayManagerService extends SystemService {
|
||||
|
||||
@Override
|
||||
public PackageInfo getPackageInfo(@NonNull final String packageName, final int userId) {
|
||||
return getPackageInfo(packageName, userId, true);
|
||||
// TODO(b/143096091): Remove clearing calling ID
|
||||
long callingIdentity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
return getPackageInfo(packageName, userId, true);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(callingIdentity);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Map<String, ? extends Map<String, String>> getNamedActors() {
|
||||
return SystemConfig.getInstance().getNamedActors();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1097,6 +1120,70 @@ public final class OverlayManagerService extends SystemService {
|
||||
return mPackageManagerInternal.getOverlayPackages(userId);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
|
||||
@Nullable String targetOverlayableName, int userId)
|
||||
throws IOException {
|
||||
// TODO(b/143096091): Remove clearing calling ID
|
||||
long callingIdentity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
PackageInfo packageInfo = getPackageInfo(packageName, userId);
|
||||
if (packageInfo == null) {
|
||||
throw new IOException("Unable to get target package");
|
||||
}
|
||||
|
||||
String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
|
||||
|
||||
ApkAssets apkAssets = null;
|
||||
try {
|
||||
apkAssets = ApkAssets.loadFromPath(baseCodePath);
|
||||
return apkAssets.getOverlayableInfo(targetOverlayableName);
|
||||
} finally {
|
||||
if (apkAssets != null) {
|
||||
try {
|
||||
apkAssets.close();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(callingIdentity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doesTargetDefineOverlayable(String targetPackageName, int userId)
|
||||
throws RemoteException, IOException {
|
||||
// TODO(b/143096091): Remove clearing calling ID
|
||||
long callingIdentity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
PackageInfo packageInfo = mPackageManager.getPackageInfo(targetPackageName, 0,
|
||||
userId);
|
||||
String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
|
||||
|
||||
ApkAssets apkAssets = null;
|
||||
try {
|
||||
apkAssets = ApkAssets.loadFromPath(baseCodePath);
|
||||
return apkAssets.definesOverlayable();
|
||||
} finally {
|
||||
if (apkAssets != null) {
|
||||
try {
|
||||
apkAssets.close();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(callingIdentity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enforcePermission(String permission, String message) throws SecurityException {
|
||||
mContext.enforceCallingOrSelfPermission(permission, message);
|
||||
}
|
||||
|
||||
public PackageInfo getCachedPackageInfo(@NonNull final String packageName,
|
||||
final int userId) {
|
||||
final HashMap<String, PackageInfo> map = mCache.get(userId);
|
||||
@@ -1128,6 +1215,22 @@ public final class OverlayManagerService extends SystemService {
|
||||
mCache.delete(userId);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String[] getPackagesForUid(int uid) {
|
||||
// TODO(b/143096091): Remove clearing calling ID
|
||||
long callingIdentity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
try {
|
||||
return mPackageManager.getPackagesForUid(uid);
|
||||
} catch (RemoteException ignored) {
|
||||
return null;
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(callingIdentity);
|
||||
}
|
||||
}
|
||||
|
||||
private static final String TAB1 = " ";
|
||||
private static final String TAB2 = TAB1 + TAB1;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ android_test {
|
||||
// Include all test java files.
|
||||
srcs: [
|
||||
"src/**/*.java",
|
||||
"src/**/*.kt",
|
||||
|
||||
"aidl/com/android/servicestests/aidl/INetworkStateObserver.aidl",
|
||||
"aidl/com/android/servicestests/aidl/ICmdReceiverService.aidl",
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* 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.content.om.OverlayInfo
|
||||
import android.content.om.OverlayableInfo
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.os.Process
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.ExpectedException
|
||||
|
||||
class OverlayActorEnforcerTests {
|
||||
companion object {
|
||||
private const val NAMESPACE = "testnamespace"
|
||||
private const val ACTOR_NAME = "testactor"
|
||||
private const val ACTOR_PKG_NAME = "com.test.actor.one"
|
||||
private const val OVERLAYABLE_NAME = "TestOverlayable"
|
||||
private const val UID = 3536
|
||||
private const val USER_ID = 55
|
||||
}
|
||||
|
||||
@get:Rule
|
||||
val expectedException = ExpectedException.none()!!
|
||||
|
||||
@Test
|
||||
fun isRoot() {
|
||||
verify(callingUid = Process.ROOT_UID)
|
||||
}
|
||||
|
||||
@Test(expected = SecurityException::class)
|
||||
fun isShell() {
|
||||
verify(callingUid = Process.SHELL_UID)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isSystem() {
|
||||
verify(callingUid = Process.SYSTEM_UID)
|
||||
}
|
||||
|
||||
@Test(expected = SecurityException::class)
|
||||
fun noOverlayable_noTarget() {
|
||||
verify(targetOverlayableName = null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun noOverlayable_noTarget_withPermission() {
|
||||
verify(targetOverlayableName = null, hasPermission = true)
|
||||
}
|
||||
|
||||
@Test(expected = SecurityException::class)
|
||||
fun noOverlayable_withTarget() {
|
||||
verify(targetOverlayableName = OVERLAYABLE_NAME)
|
||||
}
|
||||
|
||||
@Test(expected = SecurityException::class)
|
||||
fun withOverlayable_noTarget() {
|
||||
verify(
|
||||
targetOverlayableName = null,
|
||||
overlayableInfo = OverlayableInfo(OVERLAYABLE_NAME, null)
|
||||
)
|
||||
}
|
||||
|
||||
@Test(expected = SecurityException::class)
|
||||
fun withOverlayable_noActor() {
|
||||
verify(
|
||||
overlayableInfo = OverlayableInfo(OVERLAYABLE_NAME, null)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun withOverlayable_noActor_withPermission() {
|
||||
verify(
|
||||
hasPermission = true,
|
||||
overlayableInfo = OverlayableInfo(OVERLAYABLE_NAME, null)
|
||||
)
|
||||
}
|
||||
|
||||
@Test(expected = SecurityException::class)
|
||||
fun withOverlayable_withActor_notActor() {
|
||||
verify(
|
||||
isActor = false,
|
||||
overlayableInfo = OverlayableInfo(OVERLAYABLE_NAME,
|
||||
"overlay://$NAMESPACE/$ACTOR_NAME")
|
||||
)
|
||||
}
|
||||
|
||||
@Test(expected = SecurityException::class)
|
||||
fun withOverlayable_withActor_isActor_notPreInstalled() {
|
||||
verify(
|
||||
isActor = true,
|
||||
isPreInstalled = false,
|
||||
overlayableInfo = OverlayableInfo(OVERLAYABLE_NAME,
|
||||
"overlay://$NAMESPACE/$ACTOR_NAME")
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun withOverlayable_withActor_isActor_isPreInstalled() {
|
||||
verify(
|
||||
isActor = true,
|
||||
isPreInstalled = true,
|
||||
overlayableInfo = OverlayableInfo(OVERLAYABLE_NAME,
|
||||
"overlay://$NAMESPACE/$ACTOR_NAME")
|
||||
)
|
||||
}
|
||||
|
||||
@Test(expected = SecurityException::class)
|
||||
fun withOverlayable_invalidActor() {
|
||||
verify(
|
||||
isActor = true,
|
||||
isPreInstalled = true,
|
||||
overlayableInfo = OverlayableInfo(OVERLAYABLE_NAME, "notValidActor")
|
||||
)
|
||||
}
|
||||
|
||||
private fun verify(
|
||||
isActor: Boolean = false,
|
||||
isPreInstalled: Boolean = false,
|
||||
hasPermission: Boolean = false,
|
||||
overlayableInfo: OverlayableInfo? = null,
|
||||
callingUid: Int = UID,
|
||||
targetOverlayableName: String? = OVERLAYABLE_NAME
|
||||
) {
|
||||
val callback = MockCallback(
|
||||
isActor = isActor,
|
||||
isPreInstalled = isPreInstalled,
|
||||
hasPermission = hasPermission,
|
||||
overlayableInfo = overlayableInfo
|
||||
)
|
||||
|
||||
val overlayInfo = overlayInfo(targetOverlayableName)
|
||||
OverlayActorEnforcer(callback)
|
||||
.enforceActor(overlayInfo, "test", callingUid, USER_ID)
|
||||
}
|
||||
|
||||
private fun overlayInfo(targetOverlayableName: String?) = OverlayInfo("com.test.overlay",
|
||||
"com.test.target", targetOverlayableName, null, "/path", OverlayInfo.STATE_UNKNOWN, 0,
|
||||
0, false)
|
||||
|
||||
private class MockCallback(
|
||||
private val isActor: Boolean = false,
|
||||
private val isPreInstalled: Boolean = false,
|
||||
private val hasPermission: Boolean = false,
|
||||
private val overlayableInfo: OverlayableInfo? = null,
|
||||
private vararg val packageNames: String = arrayOf("com.test.actor.one")
|
||||
) : OverlayActorEnforcer.VerifyCallback {
|
||||
|
||||
override fun getNamedActors() = if (isActor) {
|
||||
mapOf(NAMESPACE to mapOf(ACTOR_NAME to ACTOR_PKG_NAME))
|
||||
} else {
|
||||
emptyMap()
|
||||
}
|
||||
|
||||
override fun getOverlayableForTarget(
|
||||
packageName: String,
|
||||
targetOverlayableName: String?,
|
||||
userId: Int
|
||||
) = overlayableInfo
|
||||
|
||||
override fun getPackagesForUid(uid: Int) = when (uid) {
|
||||
UID -> packageNames
|
||||
else -> null
|
||||
}
|
||||
|
||||
override fun getPackageInfo(packageName: String, userId: Int) = PackageInfo().apply {
|
||||
applicationInfo = ApplicationInfo().apply {
|
||||
flags = if (isPreInstalled) ApplicationInfo.FLAG_SYSTEM else 0
|
||||
}
|
||||
}
|
||||
|
||||
override fun doesTargetDefineOverlayable(targetPackageName: String?, userId: Int): Boolean {
|
||||
return overlayableInfo != null
|
||||
}
|
||||
|
||||
override fun enforcePermission(permission: String?, message: String?) {
|
||||
if (!hasPermission) {
|
||||
throw SecurityException()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* 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.systemconfig
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.InstrumentationRegistry
|
||||
import com.android.server.SystemConfig
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.ExpectedException
|
||||
import org.junit.rules.TemporaryFolder
|
||||
|
||||
class SystemConfigNamedActorTest {
|
||||
|
||||
companion object {
|
||||
private const val NAMESPACE_TEST = "someTestNamespace"
|
||||
private const val NAMESPACE_ANDROID = "android"
|
||||
private const val ACTOR_ONE = "iconShaper"
|
||||
private const val ACTOR_TWO = "colorChanger"
|
||||
private const val PACKAGE_ONE = "com.test.actor.one"
|
||||
private const val PACKAGE_TWO = "com.test.actor.two"
|
||||
}
|
||||
|
||||
private val context: Context = InstrumentationRegistry.getContext()
|
||||
|
||||
@get:Rule
|
||||
val tempFolder = TemporaryFolder(context.filesDir)
|
||||
|
||||
@get:Rule
|
||||
val expected = ExpectedException.none()
|
||||
|
||||
private var uniqueCounter = 0
|
||||
|
||||
@Test
|
||||
fun twoUnique() {
|
||||
"""
|
||||
<config>
|
||||
<named-actor
|
||||
namespace="$NAMESPACE_TEST"
|
||||
name="$ACTOR_ONE"
|
||||
package="$PACKAGE_ONE"
|
||||
/>
|
||||
<named-actor
|
||||
namespace="$NAMESPACE_TEST"
|
||||
name="$ACTOR_TWO"
|
||||
package="$PACKAGE_TWO"
|
||||
/>
|
||||
</config>
|
||||
""".write()
|
||||
|
||||
assertPermissions().containsExactlyEntriesIn(
|
||||
mapOf(
|
||||
NAMESPACE_TEST to mapOf(
|
||||
ACTOR_ONE to PACKAGE_ONE,
|
||||
ACTOR_TWO to PACKAGE_TWO
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun twoSamePackage() {
|
||||
"""
|
||||
<config>
|
||||
<named-actor
|
||||
namespace="$NAMESPACE_TEST"
|
||||
name="$ACTOR_ONE"
|
||||
package="$PACKAGE_ONE"
|
||||
/>
|
||||
<named-actor
|
||||
namespace="$NAMESPACE_TEST"
|
||||
name="$ACTOR_TWO"
|
||||
package="$PACKAGE_ONE"
|
||||
/>
|
||||
</config>
|
||||
""".write()
|
||||
|
||||
assertPermissions().containsExactlyEntriesIn(
|
||||
mapOf(
|
||||
NAMESPACE_TEST to mapOf(
|
||||
ACTOR_ONE to PACKAGE_ONE,
|
||||
ACTOR_TWO to PACKAGE_ONE
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun missingNamespace() {
|
||||
"""
|
||||
<config>
|
||||
<named-actor
|
||||
name="$ACTOR_ONE"
|
||||
package="$PACKAGE_ONE"
|
||||
/>
|
||||
<named-actor
|
||||
namespace="$NAMESPACE_TEST"
|
||||
name="$ACTOR_TWO"
|
||||
package="$PACKAGE_TWO"
|
||||
/>
|
||||
</config>
|
||||
""".write()
|
||||
|
||||
assertPermissions().containsExactlyEntriesIn(
|
||||
mapOf(
|
||||
NAMESPACE_TEST to mapOf(
|
||||
ACTOR_TWO to PACKAGE_TWO
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun missingName() {
|
||||
"""
|
||||
<config>
|
||||
<named-actor
|
||||
namespace="$NAMESPACE_TEST"
|
||||
package="$PACKAGE_ONE"
|
||||
/>
|
||||
<named-actor
|
||||
namespace="$NAMESPACE_TEST"
|
||||
name="$ACTOR_TWO"
|
||||
package="$PACKAGE_TWO"
|
||||
/>
|
||||
</config>
|
||||
""".write()
|
||||
|
||||
assertPermissions().containsExactlyEntriesIn(
|
||||
mapOf(
|
||||
NAMESPACE_TEST to mapOf(
|
||||
ACTOR_TWO to PACKAGE_TWO
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun missingPackage() {
|
||||
"""
|
||||
<config>
|
||||
<named-actor
|
||||
namespace="$NAMESPACE_TEST"
|
||||
name="$ACTOR_ONE"
|
||||
/>
|
||||
<named-actor
|
||||
namespace="$NAMESPACE_TEST"
|
||||
name="$ACTOR_TWO"
|
||||
package="$PACKAGE_TWO"
|
||||
/>
|
||||
</config>
|
||||
""".write()
|
||||
|
||||
assertPermissions().containsExactlyEntriesIn(
|
||||
mapOf(
|
||||
NAMESPACE_TEST to mapOf(
|
||||
ACTOR_TWO to PACKAGE_TWO
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun androidNamespaceThrows() {
|
||||
"""
|
||||
<config>
|
||||
<named-actor
|
||||
namespace="$NAMESPACE_TEST"
|
||||
name="$ACTOR_ONE"
|
||||
package="$PACKAGE_ONE"
|
||||
/>
|
||||
<named-actor
|
||||
namespace="$NAMESPACE_ANDROID"
|
||||
name="$ACTOR_ONE"
|
||||
package="$PACKAGE_ONE"
|
||||
/>
|
||||
</config>
|
||||
""".write()
|
||||
|
||||
expected.expect(IllegalStateException::class.java)
|
||||
expected.expectMessage("Defining $ACTOR_ONE as $PACKAGE_ONE " +
|
||||
"for the android namespace is not allowed")
|
||||
|
||||
assertPermissions()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun duplicateActorNameThrows() {
|
||||
"""
|
||||
<config>
|
||||
<named-actor
|
||||
namespace="$NAMESPACE_TEST"
|
||||
name="$ACTOR_ONE"
|
||||
package="$PACKAGE_ONE"
|
||||
/>
|
||||
<named-actor
|
||||
namespace="$NAMESPACE_TEST"
|
||||
name="$ACTOR_ONE"
|
||||
package="$PACKAGE_TWO"
|
||||
/>
|
||||
</config>
|
||||
""".write()
|
||||
|
||||
expected.expect(IllegalStateException::class.java)
|
||||
expected.expectMessage("Duplicate actor definition for $NAMESPACE_TEST/$ACTOR_ONE;" +
|
||||
" defined as both $PACKAGE_ONE and $PACKAGE_TWO")
|
||||
|
||||
assertPermissions()
|
||||
}
|
||||
|
||||
private fun String.write() = tempFolder.root.resolve("${uniqueCounter++}.xml")
|
||||
.writeText(this.trimIndent())
|
||||
|
||||
private fun assertPermissions() = SystemConfig(false).apply {
|
||||
readPermissions(tempFolder.root, 0)
|
||||
}. let { assertThat(it.namedActors) }
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server;
|
||||
package com.android.server.systemconfig;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@@ -25,6 +25,8 @@ import android.util.Log;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.server.SystemConfig;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
Reference in New Issue
Block a user