blob: 358531a81979d92e3e130e3860f7a3d84e6eeea4 [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.
*/
#include <dlfcn.h>
#include <optional>
#define LOG_TAG "OverlayManagerImpl"
#include "android-base/no_destructor.h"
#include "androidfw/ResourceTypes.h"
#include "core_jni_helpers.h"
#include "jni.h"
namespace android {
static struct fabricated_overlay_internal_offsets_t {
jclass classObject;
jfieldID packageName;
jfieldID overlayName;
jfieldID targetPackageName;
jfieldID targetOverlayable;
jfieldID entries;
} gFabricatedOverlayInternalOffsets;
static struct fabricated_overlay_internal_entry_offsets_t {
jclass classObject;
jfieldID resourceName;
jfieldID dataType;
jfieldID data;
jfieldID stringData;
jfieldID binaryData;
jfieldID configuration;
jfieldID binaryDataOffset;
jfieldID binaryDataSize;
jfieldID isNinePatch;
} gFabricatedOverlayInternalEntryOffsets;
static struct parcel_file_descriptor_offsets_t {
jclass classObject;
jmethodID getFd;
} gParcelFileDescriptorOffsets;
static struct List_offsets_t {
jclass classObject;
jmethodID size;
jmethodID get;
} gListOffsets;
static struct fabricated_overlay_info_offsets_t {
jclass classObject;
jmethodID constructor;
jfieldID packageName;
jfieldID overlayName;
jfieldID targetPackageName;
jfieldID targetOverlayable;
jfieldID path;
} gFabricatedOverlayInfoOffsets;
namespace self_targeting {
constexpr const char kIOException[] = "java/io/IOException";
constexpr const char IllegalArgumentException[] = "java/lang/IllegalArgumentException";
class DynamicLibraryLoader {
public:
explicit DynamicLibraryLoader(JNIEnv* env) {
/* For SelfTargeting, there are 2 types of files to be handled. One is frro and the other is
* idmap. For creating frro/idmap files and reading frro files, it needs libandroid_runtime
* to do a shared link to libidmap2. However, libidmap2 contains the codes generated from
* google protocol buffer. When libandroid_runtime does a shared link to libidmap2, it will
* impact the memory for system_server and zygote(a.k.a. all applications).
*
* Not all applications need to either create/read frro files or create idmap files all the
* time. When the apps apply the SelfTargeting overlay effect, it only needs libandroifw
* that is loaded. To use dlopen(libidmap2.so) is to make sure that applications don't
* impact themselves' memory by loading libidmap2 until they need to create/read frro files
* or create idmap files.
*/
handle_ = dlopen("libidmap2.so", RTLD_NOW);
if (handle_ == nullptr) {
jniThrowNullPointerException(env);
return;
}
createIdmapFileFuncPtr_ =
reinterpret_cast<CreateIdmapFileFunc>(dlsym(handle_, "CreateIdmapFile"));
if (createIdmapFileFuncPtr_ == nullptr) {
jniThrowNullPointerException(env, "The symbol CreateIdmapFile is not found.");
return;
}
getFabricatedOverlayInfoFuncPtr_ = reinterpret_cast<GetFabricatedOverlayInfoFunc>(
dlsym(handle_, "GetFabricatedOverlayInfo"));
if (getFabricatedOverlayInfoFuncPtr_ == nullptr) {
jniThrowNullPointerException(env, "The symbol GetFabricatedOverlayInfo is not found.");
return;
}
createFrroFile_ = reinterpret_cast<CreateFrroFileFunc>(dlsym(handle_, "CreateFrroFile"));
if (createFrroFile_ == nullptr) {
jniThrowNullPointerException(env, "The symbol CreateFrroFile is not found.");
return;
}
}
bool callCreateFrroFile(std::string& out_error, const std::string& packageName,
const std::string& overlayName, const std::string& targetPackageName,
const std::optional<std::string>& targetOverlayable,
const std::vector<FabricatedOverlayEntryParameters>& entries_params,
const std::string& frro_file_path) {
return createFrroFile_(out_error, packageName, overlayName, targetPackageName,
targetOverlayable, entries_params, frro_file_path);
}
bool callCreateIdmapFile(std::string& out_error, const std::string& targetPath,
const std::string& overlayPath, const std::string& idmapPath,
const std::string& overlayName, const bool isSystem,
const bool isVendor, const bool isProduct,
const bool isTargetSignature, const bool isOdm, const bool isOem) {
return createIdmapFileFuncPtr_(out_error, targetPath, overlayPath, idmapPath, overlayName,
isSystem, isVendor, isProduct, isTargetSignature, isOdm,
isOem);
}
bool callGetFabricatedOverlayInfo(std::string& out_error, const std::string& overlay_path,
OverlayManifestInfo& out_overlay_manifest_info) {
return getFabricatedOverlayInfoFuncPtr_(out_error, overlay_path, out_overlay_manifest_info);
}
explicit operator bool() const {
return handle_ != nullptr && createFrroFile_ != nullptr &&
createIdmapFileFuncPtr_ != nullptr && getFabricatedOverlayInfoFuncPtr_ != nullptr;
}
DynamicLibraryLoader(const DynamicLibraryLoader&) = delete;
DynamicLibraryLoader& operator=(const DynamicLibraryLoader&) = delete;
~DynamicLibraryLoader() {
if (handle_ != nullptr) {
dlclose(handle_);
}
}
private:
typedef bool (*CreateFrroFileFunc)(
std::string& out_error, const std::string& packageName, const std::string& overlayName,
const std::string& targetPackageName,
const std::optional<std::string>& targetOverlayable,
const std::vector<FabricatedOverlayEntryParameters>& entries_params,
const std::string& frro_file_path);
typedef bool (*CreateIdmapFileFunc)(std::string& out_error, const std::string& targetPath,
const std::string& overlayPath,
const std::string& idmapPath,
const std::string& overlayName, const jboolean isSystem,
const jboolean isVendor, const jboolean isProduct,
const jboolean isSameWithTargetSignature,
const jboolean isOdm, const jboolean isOem);
typedef bool (*GetFabricatedOverlayInfoFunc)(std::string& out_error,
const std::string& overlay_path,
OverlayManifestInfo& out_overlay_manifest_info);
void* handle_;
CreateFrroFileFunc createFrroFile_;
CreateIdmapFileFunc createIdmapFileFuncPtr_;
GetFabricatedOverlayInfoFunc getFabricatedOverlayInfoFuncPtr_;
};
static DynamicLibraryLoader& EnsureDynamicLibraryLoader(JNIEnv* env) {
static android::base::NoDestructor<DynamicLibraryLoader> loader(env);
return *loader;
}
static std::optional<std::string> getNullableString(JNIEnv* env, jobject object, jfieldID field) {
auto javaString = reinterpret_cast<jstring>(env->GetObjectField(object, field));
if (javaString == nullptr) {
return std::nullopt;
}
const ScopedUtfChars result(env, javaString);
if (result.c_str() == nullptr) {
return std::nullopt;
}
return std::optional<std::string>{result.c_str()};
}
static std::optional<android::base::borrowed_fd> getNullableFileDescriptor(JNIEnv* env,
jobject object,
jfieldID field) {
auto binaryData = env->GetObjectField(object, field);
if (binaryData == nullptr) {
return std::nullopt;
}
return env->CallIntMethod(binaryData, gParcelFileDescriptorOffsets.getFd);
}
static void CreateFrroFile(JNIEnv* env, jclass /*clazz*/, jstring jsFrroFilePath, jobject overlay) {
DynamicLibraryLoader& dlLoader = EnsureDynamicLibraryLoader(env);
if (!dlLoader) {
jniThrowNullPointerException(env, "libidmap2 is not loaded");
return;
}
if (overlay == nullptr) {
jniThrowNullPointerException(env, "overlay is null");
return;
}
auto jsPackageName =
(jstring)env->GetObjectField(overlay, gFabricatedOverlayInternalOffsets.packageName);
const ScopedUtfChars packageName(env, jsPackageName);
if (packageName.c_str() == nullptr) {
jniThrowNullPointerException(env, "packageName is null");
return;
}
auto jsOverlayName =
(jstring)env->GetObjectField(overlay, gFabricatedOverlayInternalOffsets.overlayName);
const ScopedUtfChars overlayName(env, jsOverlayName);
if (overlayName.c_str() == nullptr) {
jniThrowNullPointerException(env, "overlayName is null");
return;
}
auto jsTargetPackageName =
(jstring)env->GetObjectField(overlay,
gFabricatedOverlayInternalOffsets.targetPackageName);
const ScopedUtfChars targetPackageName(env, jsTargetPackageName);
if (targetPackageName.c_str() == nullptr) {
jniThrowNullPointerException(env, "targetPackageName is null");
return;
}
auto overlayable =
getNullableString(env, overlay, gFabricatedOverlayInternalOffsets.targetOverlayable);
const ScopedUtfChars frroFilePath(env, jsFrroFilePath);
if (frroFilePath.c_str() == nullptr) {
jniThrowNullPointerException(env, "frroFilePath is null");
return;
}
jobject entries = env->GetObjectField(overlay, gFabricatedOverlayInternalOffsets.entries);
if (entries == nullptr) {
jniThrowNullPointerException(env, "overlay entries is null");
return;
}
const jint size = env->CallIntMethod(entries, gListOffsets.size);
ALOGV("frroFilePath = %s, packageName = %s, overlayName = %s, targetPackageName = %s,"
" targetOverlayable = %s, size = %d",
frroFilePath.c_str(), packageName.c_str(), overlayName.c_str(), targetPackageName.c_str(),
overlayable.value_or(std::string()).c_str(), size);
std::vector<FabricatedOverlayEntryParameters> entries_params;
for (jint i = 0; i < size; i++) {
jobject entry = env->CallObjectMethod(entries, gListOffsets.get, i);
auto jsResourceName = reinterpret_cast<jstring>(
env->GetObjectField(entry, gFabricatedOverlayInternalEntryOffsets.resourceName));
const ScopedUtfChars resourceName(env, jsResourceName);
const jint dataType =
env->GetIntField(entry, gFabricatedOverlayInternalEntryOffsets.dataType);
// In Java, the data type is int but the maximum value of data Type is less than 0xff.
if (dataType >= static_cast<jint>(UCHAR_MAX)) {
jniThrowException(env, IllegalArgumentException, "Unsupported data type");
return;
}
const auto data = env->GetIntField(entry, gFabricatedOverlayInternalEntryOffsets.data);
auto configuration =
getNullableString(env, entry, gFabricatedOverlayInternalEntryOffsets.configuration);
auto string_data =
getNullableString(env, entry, gFabricatedOverlayInternalEntryOffsets.stringData);
auto binary_data =
getNullableFileDescriptor(env, entry,
gFabricatedOverlayInternalEntryOffsets.binaryData);
const auto data_offset =
env->GetLongField(entry, gFabricatedOverlayInternalEntryOffsets.binaryDataOffset);
const auto data_size =
env->GetLongField(entry, gFabricatedOverlayInternalEntryOffsets.binaryDataSize);
const auto nine_patch =
env->GetBooleanField(entry, gFabricatedOverlayInternalEntryOffsets.isNinePatch);
entries_params.push_back(
FabricatedOverlayEntryParameters{resourceName.c_str(), (DataType)dataType,
(DataValue)data,
string_data.value_or(std::string()), binary_data,
static_cast<off64_t>(data_offset),
static_cast<size_t>(data_size),
configuration.value_or(std::string()),
static_cast<bool>(nine_patch)});
ALOGV("resourceName = %s, dataType = 0x%08x, data = 0x%08x, dataString = %s,"
" binaryData = %d, configuration = %s",
resourceName.c_str(), dataType, data, string_data.value_or(std::string()).c_str(),
binary_data.has_value(), configuration.value_or(std::string()).c_str());
}
std::string err_result;
if (!dlLoader.callCreateFrroFile(err_result, packageName.c_str(), overlayName.c_str(),
targetPackageName.c_str(), overlayable, entries_params,
frroFilePath.c_str())) {
jniThrowException(env, IllegalArgumentException, err_result.c_str());
return;
}
}
static void CreateIdmapFile(JNIEnv* env, jclass /* clazz */, jstring jsTargetPath,
jstring jsOverlayPath, jstring jsIdmapPath, jstring jsOverlayName,
jboolean isSystem, jboolean isVendor, jboolean isProduct,
jboolean isTargetSignature, jboolean isOdm, jboolean isOem) {
DynamicLibraryLoader& dlLoader = EnsureDynamicLibraryLoader(env);
if (!dlLoader) {
jniThrowNullPointerException(env, "libidmap2 is not loaded");
return;
}
const ScopedUtfChars targetPath(env, jsTargetPath);
if (targetPath.c_str() == nullptr) {
jniThrowNullPointerException(env, "targetPath is null");
return;
}
const ScopedUtfChars overlayPath(env, jsOverlayPath);
if (overlayPath.c_str() == nullptr) {
jniThrowNullPointerException(env, "overlayPath is null");
return;
}
const ScopedUtfChars idmapPath(env, jsIdmapPath);
if (idmapPath.c_str() == nullptr) {
jniThrowNullPointerException(env, "idmapPath is null");
return;
}
const ScopedUtfChars overlayName(env, jsOverlayName);
if (overlayName.c_str() == nullptr) {
jniThrowNullPointerException(env, "overlayName is null");
return;
}
ALOGV("target_path = %s, overlay_path = %s, idmap_path = %s, overlay_name = %s",
targetPath.c_str(), overlayPath.c_str(), idmapPath.c_str(), overlayName.c_str());
std::string err_result;
if (!dlLoader.callCreateIdmapFile(err_result, targetPath.c_str(), overlayPath.c_str(),
idmapPath.c_str(), overlayName.c_str(),
(isSystem == JNI_TRUE), (isVendor == JNI_TRUE),
(isProduct == JNI_TRUE), (isTargetSignature == JNI_TRUE),
(isOdm == JNI_TRUE), (isOem == JNI_TRUE))) {
jniThrowException(env, kIOException, err_result.c_str());
return;
}
}
static jobject GetFabricatedOverlayInfo(JNIEnv* env, jclass /* clazz */, jstring jsOverlayPath) {
const ScopedUtfChars overlay_path(env, jsOverlayPath);
if (overlay_path.c_str() == nullptr) {
jniThrowNullPointerException(env, "overlay_path is null");
return nullptr;
}
ALOGV("overlay_path = %s", overlay_path.c_str());
DynamicLibraryLoader& dlLoader = EnsureDynamicLibraryLoader(env);
if (!dlLoader) {
return nullptr;
}
std::string err_result;
OverlayManifestInfo overlay_manifest_info;
if (!dlLoader.callGetFabricatedOverlayInfo(err_result, overlay_path.c_str(),
overlay_manifest_info) != 0) {
jniThrowException(env, kIOException, err_result.c_str());
return nullptr;
}
jobject info = env->NewObject(gFabricatedOverlayInfoOffsets.classObject,
gFabricatedOverlayInfoOffsets.constructor);
jstring jsOverName = env->NewStringUTF(overlay_manifest_info.name.c_str());
jstring jsPackageName = env->NewStringUTF(overlay_manifest_info.package_name.c_str());
jstring jsTargetPackage = env->NewStringUTF(overlay_manifest_info.target_package.c_str());
jstring jsTargetOverlayable = env->NewStringUTF(overlay_manifest_info.target_name.c_str());
env->SetObjectField(info, gFabricatedOverlayInfoOffsets.overlayName, jsOverName);
env->SetObjectField(info, gFabricatedOverlayInfoOffsets.packageName, jsPackageName);
env->SetObjectField(info, gFabricatedOverlayInfoOffsets.targetPackageName, jsTargetPackage);
env->SetObjectField(info, gFabricatedOverlayInfoOffsets.targetOverlayable, jsTargetOverlayable);
env->SetObjectField(info, gFabricatedOverlayInfoOffsets.path, jsOverlayPath);
return info;
}
} // namespace self_targeting
// JNI registration.
static const JNINativeMethod gOverlayManagerMethods[] = {
{"createFrroFile", "(Ljava/lang/String;Landroid/os/FabricatedOverlayInternal;)V",
reinterpret_cast<void*>(self_targeting::CreateFrroFile)},
{"createIdmapFile",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZZZZZ)V",
reinterpret_cast<void*>(self_targeting::CreateIdmapFile)},
{"getFabricatedOverlayInfo", "(Ljava/lang/String;)Landroid/os/FabricatedOverlayInfo;",
reinterpret_cast<void*>(self_targeting::GetFabricatedOverlayInfo)},
};
int register_com_android_internal_content_om_OverlayManagerImpl(JNIEnv* env) {
jclass ListClass = FindClassOrDie(env, "java/util/List");
gListOffsets.classObject = MakeGlobalRefOrDie(env, ListClass);
gListOffsets.size = GetMethodIDOrDie(env, gListOffsets.classObject, "size", "()I");
gListOffsets.get =
GetMethodIDOrDie(env, gListOffsets.classObject, "get", "(I)Ljava/lang/Object;");
jclass fabricatedOverlayInternalClass =
FindClassOrDie(env, "android/os/FabricatedOverlayInternal");
gFabricatedOverlayInternalOffsets.classObject =
MakeGlobalRefOrDie(env, fabricatedOverlayInternalClass);
gFabricatedOverlayInternalOffsets.packageName =
GetFieldIDOrDie(env, gFabricatedOverlayInternalOffsets.classObject, "packageName",
"Ljava/lang/String;");
gFabricatedOverlayInternalOffsets.overlayName =
GetFieldIDOrDie(env, gFabricatedOverlayInternalOffsets.classObject, "overlayName",
"Ljava/lang/String;");
gFabricatedOverlayInternalOffsets.targetPackageName =
GetFieldIDOrDie(env, gFabricatedOverlayInternalOffsets.classObject, "targetPackageName",
"Ljava/lang/String;");
gFabricatedOverlayInternalOffsets.targetOverlayable =
GetFieldIDOrDie(env, gFabricatedOverlayInternalOffsets.classObject, "targetOverlayable",
"Ljava/lang/String;");
gFabricatedOverlayInternalOffsets.entries =
GetFieldIDOrDie(env, gFabricatedOverlayInternalOffsets.classObject, "entries",
"Ljava/util/List;");
jclass fabricatedOverlayInternalEntryClass =
FindClassOrDie(env, "android/os/FabricatedOverlayInternalEntry");
gFabricatedOverlayInternalEntryOffsets.classObject =
MakeGlobalRefOrDie(env, fabricatedOverlayInternalEntryClass);
gFabricatedOverlayInternalEntryOffsets.resourceName =
GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject, "resourceName",
"Ljava/lang/String;");
gFabricatedOverlayInternalEntryOffsets.dataType =
GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject, "dataType",
"I");
gFabricatedOverlayInternalEntryOffsets.data =
GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject, "data", "I");
gFabricatedOverlayInternalEntryOffsets.stringData =
GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject, "stringData",
"Ljava/lang/String;");
gFabricatedOverlayInternalEntryOffsets.binaryData =
GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject, "binaryData",
"Landroid/os/ParcelFileDescriptor;");
gFabricatedOverlayInternalEntryOffsets.configuration =
GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject,
"configuration", "Ljava/lang/String;");
gFabricatedOverlayInternalEntryOffsets.binaryDataOffset =
GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject,
"binaryDataOffset", "J");
gFabricatedOverlayInternalEntryOffsets.binaryDataSize =
GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject,
"binaryDataSize", "J");
gFabricatedOverlayInternalEntryOffsets.isNinePatch =
GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject, "isNinePatch",
"Z");
jclass parcelFileDescriptorClass =
android::FindClassOrDie(env, "android/os/ParcelFileDescriptor");
gParcelFileDescriptorOffsets.classObject = MakeGlobalRefOrDie(env, parcelFileDescriptorClass);
gParcelFileDescriptorOffsets.getFd =
GetMethodIDOrDie(env, gParcelFileDescriptorOffsets.classObject, "getFd", "()I");
jclass fabricatedOverlayInfoClass = FindClassOrDie(env, "android/os/FabricatedOverlayInfo");
gFabricatedOverlayInfoOffsets.classObject = MakeGlobalRefOrDie(env, fabricatedOverlayInfoClass);
gFabricatedOverlayInfoOffsets.constructor =
GetMethodIDOrDie(env, gFabricatedOverlayInfoOffsets.classObject, "<init>", "()V");
gFabricatedOverlayInfoOffsets.packageName =
GetFieldIDOrDie(env, gFabricatedOverlayInfoOffsets.classObject, "packageName",
"Ljava/lang/String;");
gFabricatedOverlayInfoOffsets.overlayName =
GetFieldIDOrDie(env, gFabricatedOverlayInfoOffsets.classObject, "overlayName",
"Ljava/lang/String;");
gFabricatedOverlayInfoOffsets.targetPackageName =
GetFieldIDOrDie(env, gFabricatedOverlayInfoOffsets.classObject, "targetPackageName",
"Ljava/lang/String;");
gFabricatedOverlayInfoOffsets.targetOverlayable =
GetFieldIDOrDie(env, gFabricatedOverlayInfoOffsets.classObject, "targetOverlayable",
"Ljava/lang/String;");
gFabricatedOverlayInfoOffsets.path =
GetFieldIDOrDie(env, gFabricatedOverlayInfoOffsets.classObject, "path",
"Ljava/lang/String;");
return RegisterMethodsOrDie(env, "com/android/internal/content/om/OverlayManagerImpl",
gOverlayManagerMethods, NELEM(gOverlayManagerMethods));
}
} // namespace android