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
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index 6370253..f3a626e 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -194,6 +194,59 @@
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 @@
{"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) {