diff options
15 files changed, 1477 insertions, 11 deletions
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp index 7a08cbdcddd4..5f06c971ba98 100644 --- a/cmds/idmap2/Android.bp +++ b/cmds/idmap2/Android.bp @@ -71,6 +71,7 @@ cc_library { host_supported: true, srcs: [ "libidmap2/**/*.cpp", + "self_targeting/*.cpp", ], export_include_dirs: ["include"], target: { diff --git a/cmds/idmap2/include/idmap2/ResourceContainer.h b/cmds/idmap2/include/idmap2/ResourceContainer.h index 2452ff0784ce..4d2832113f71 100644 --- a/cmds/idmap2/include/idmap2/ResourceContainer.h +++ b/cmds/idmap2/include/idmap2/ResourceContainer.h @@ -46,14 +46,6 @@ struct TargetResourceContainer : public ResourceContainer { ~TargetResourceContainer() override = default; }; -struct OverlayManifestInfo { - std::string package_name; // NOLINT(misc-non-private-member-variables-in-classes) - std::string name; // NOLINT(misc-non-private-member-variables-in-classes) - std::string target_package; // NOLINT(misc-non-private-member-variables-in-classes) - std::string target_name; // NOLINT(misc-non-private-member-variables-in-classes) - ResourceId resource_mapping; // NOLINT(misc-non-private-member-variables-in-classes) -}; - struct OverlayData { struct ResourceIdValue { // The overlay resource id. diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h index 2214a83bd2da..c2b0abed442c 100644 --- a/cmds/idmap2/include/idmap2/ResourceUtils.h +++ b/cmds/idmap2/include/idmap2/ResourceUtils.h @@ -30,13 +30,13 @@ namespace android::idmap2 { #define EXTRACT_ENTRY(resid) (0x0000ffff & (resid)) // use typedefs to let the compiler warn us about implicit casts -using ResourceId = uint32_t; // 0xpptteeee +using ResourceId = android::ResourceId; // 0xpptteeee using PackageId = uint8_t; // pp in 0xpptteeee using TypeId = uint8_t; // tt in 0xpptteeee using EntryId = uint16_t; // eeee in 0xpptteeee -using DataType = uint8_t; // Res_value::dataType -using DataValue = uint32_t; // Res_value::data +using DataType = android::DataType; // Res_value::dataType +using DataValue = android::DataValue; // Res_value::data struct TargetValue { DataType data_type; diff --git a/cmds/idmap2/self_targeting/SelfTargeting.cpp b/cmds/idmap2/self_targeting/SelfTargeting.cpp new file mode 100644 index 000000000000..20aa7d32a3c2 --- /dev/null +++ b/cmds/idmap2/self_targeting/SelfTargeting.cpp @@ -0,0 +1,170 @@ +/* + * 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 <sys/stat.h> + +#include <fstream> +#include <optional> + +#define LOG_TAG "SelfTargeting" + +#include "androidfw/ResourceTypes.h" +#include "idmap2/BinaryStreamVisitor.h" +#include "idmap2/FabricatedOverlay.h" +#include "idmap2/Idmap.h" +#include "idmap2/Result.h" + +using PolicyBitmask = android::ResTable_overlayable_policy_header::PolicyBitmask; +using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags; +using android::idmap2::BinaryStreamVisitor; +using android::idmap2::Idmap; +using android::idmap2::OverlayResourceContainer; + +namespace android::self_targeting { + +constexpr const mode_t kIdmapFilePermission = S_IRUSR | S_IWUSR; // u=rw-, g=---, o=--- + +extern "C" bool +CreateFrroFile(std::string& out_err_result, std::string& packageName, std::string& overlayName, + std::string& targetPackageName, std::optional<std::string>& targetOverlayable, + std::vector<FabricatedOverlayEntryParameters>& entries_params, + const std::string& frro_file_path) { + android::idmap2::FabricatedOverlay::Builder builder(packageName, overlayName, + targetPackageName); + if (targetOverlayable.has_value()) { + builder.SetOverlayable(targetOverlayable.value_or(std::string())); + } + for (const auto& entry_params : entries_params) { + const auto dataType = entry_params.data_type; + if (entry_params.data_binary_value.has_value()) { + builder.SetResourceValue(entry_params.resource_name, *entry_params.data_binary_value, + entry_params.configuration); + } else if (dataType >= Res_value::TYPE_FIRST_INT && dataType <= Res_value::TYPE_LAST_INT) { + builder.SetResourceValue(entry_params.resource_name, dataType, + entry_params.data_value, entry_params.configuration); + } else if (dataType == Res_value::TYPE_STRING) { + builder.SetResourceValue(entry_params.resource_name, dataType, + entry_params.data_string_value , entry_params.configuration); + } else { + out_err_result = base::StringPrintf("Unsupported data type %d", dataType); + return false; + } + } + + const auto frro = builder.Build(); + std::ofstream fout(frro_file_path); + if (fout.fail()) { + out_err_result = base::StringPrintf("open output stream fail %s", std::strerror(errno)); + return false; + } + auto result = frro->ToBinaryStream(fout); + if (!result) { + unlink(frro_file_path.c_str()); + out_err_result = base::StringPrintf("to stream fail %s", result.GetErrorMessage().c_str()); + return false; + } + fout.close(); + if (fout.fail()) { + unlink(frro_file_path.c_str()); + out_err_result = base::StringPrintf("output stream fail %s", std::strerror(errno)); + return false; + } + if (chmod(frro_file_path.c_str(), kIdmapFilePermission) == -1) { + out_err_result = base::StringPrintf("Failed to change the file permission %s", + frro_file_path.c_str()); + return false; + } + return true; +} + +extern "C" bool +CreateIdmapFile(std::string& out_err, const std::string& targetPath, const std::string& overlayPath, + const std::string& idmapPath, const std::string& overlayName) { + // idmap files are mapped with mmap in libandroidfw. Deleting and recreating the idmap + // guarantees that existing memory maps will continue to be valid and unaffected. The file must + // be deleted before attempting to create the idmap, so that if idmap creation fails, the + // overlay will no longer be usable. + unlink(idmapPath.c_str()); + + const auto target = idmap2::TargetResourceContainer::FromPath(targetPath); + if (!target) { + out_err = base::StringPrintf("Failed to load target %s because of %s", targetPath.c_str(), + target.GetErrorMessage().c_str()); + return false; + } + + const auto overlay = OverlayResourceContainer::FromPath(overlayPath); + if (!overlay) { + out_err = base::StringPrintf("Failed to load overlay %s because of %s", overlayPath.c_str(), + overlay.GetErrorMessage().c_str()); + return false; + } + + // Overlay self target process. Only allow self-targeting types. + const auto fulfilled_policies = static_cast<PolicyBitmask>( + PolicyFlags::PUBLIC | PolicyFlags::SYSTEM_PARTITION | PolicyFlags::VENDOR_PARTITION | + PolicyFlags::PRODUCT_PARTITION | PolicyFlags::SIGNATURE | PolicyFlags::ODM_PARTITION | + PolicyFlags::OEM_PARTITION | PolicyFlags::ACTOR_SIGNATURE | + PolicyFlags::CONFIG_SIGNATURE); + + const auto idmap = Idmap::FromContainers(**target, **overlay, overlayName, + fulfilled_policies, false /* enforce_overlayable */); + if (!idmap) { + out_err = base::StringPrintf("Failed to create idmap because of %s", + idmap.GetErrorMessage().c_str()); + return false; + } + + std::ofstream fout(idmapPath.c_str()); + if (fout.fail()) { + out_err = base::StringPrintf("Failed to create idmap %s because of %s", idmapPath.c_str(), + strerror(errno)); + return false; + } + + BinaryStreamVisitor visitor(fout); + (*idmap)->accept(&visitor); + fout.close(); + if (fout.fail()) { + unlink(idmapPath.c_str()); + out_err = base::StringPrintf("Failed to write idmap %s because of %s", idmapPath.c_str(), + strerror(errno)); + return false; + } + if (chmod(idmapPath.c_str(), kIdmapFilePermission) == -1) { + out_err = base::StringPrintf("Failed to change the file permission %s", idmapPath.c_str()); + return false; + } + return true; +} + +extern "C" bool +GetFabricatedOverlayInfo(std::string& out_err, const std::string& overlay_path, + OverlayManifestInfo& out_info) { + const auto overlay = idmap2::FabricatedOverlayContainer::FromPath(overlay_path); + if (!overlay) { + out_err = base::StringPrintf("Failed to write idmap %s because of %s", + overlay_path.c_str(), strerror(errno)); + return false; + } + + out_info = (*overlay)->GetManifestInfo(); + + return true; +} + +} // namespace android::self_targeting + diff --git a/core/java/com/android/internal/content/om/OverlayManagerImpl.java b/core/java/com/android/internal/content/om/OverlayManagerImpl.java new file mode 100644 index 000000000000..76e068d11d74 --- /dev/null +++ b/core/java/com/android/internal/content/om/OverlayManagerImpl.java @@ -0,0 +1,323 @@ +/* + * 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. + */ + +package com.android.internal.content.om; + +import static android.content.Context.MODE_PRIVATE; + +import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; +import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; +import static com.android.internal.content.om.OverlayConfig.DEFAULT_PRIORITY; + +import android.annotation.NonNull; +import android.content.Context; +import android.content.om.OverlayInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.parsing.FrameworkParsingPackageUtils; +import android.os.FabricatedOverlayInfo; +import android.os.FabricatedOverlayInternal; +import android.os.FabricatedOverlayInternalEntry; +import android.os.FileUtils; +import android.os.Process; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * This class provides the functionalities of registering an overlay, unregistering an overlay, and + * getting the list of overlays information. + */ +public class OverlayManagerImpl { + private static final String TAG = "OverlayManagerImpl"; + private static final boolean DEBUG = false; + + private static final String FRRO_EXTENSION = ".frro"; + + private static final String IDMAP_EXTENSION = ".idmap"; + + @VisibleForTesting(visibility = PRIVATE) + public static final String SELF_TARGET = ".self_target"; + + @NonNull + private final Context mContext; + private Path mBasePath; + + /** + * Init a OverlayManagerImpl by the context. + * + * @param context the context to create overlay environment + */ + @VisibleForTesting(visibility = PACKAGE) + public OverlayManagerImpl(@NonNull Context context) { + mContext = Objects.requireNonNull(context); + + if (!Process.myUserHandle().equals(context.getUser())) { + throw new SecurityException("Self-Targeting doesn't support multiple user now!"); + } + } + + private static void cleanExpiredOverlays(Path selfTargetingBasePath, + Path folderForCurrentBaseApk) { + try { + final String currentBaseFolder = folderForCurrentBaseApk.toString(); + final String selfTargetingDir = selfTargetingBasePath.getFileName().toString(); + Files.walkFileTree( + selfTargetingBasePath, + new SimpleFileVisitor<>() { + @Override + public FileVisitResult preVisitDirectory(Path dir, + BasicFileAttributes attrs) + throws IOException { + final String fileName = dir.getFileName().toString(); + return fileName.equals(currentBaseFolder) + ? FileVisitResult.SKIP_SUBTREE + : super.preVisitDirectory(dir, attrs); + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + if (!file.toFile().delete()) { + Log.w(TAG, "Failed to delete file " + file); + } + return super.visitFile(file, attrs); + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) + throws IOException { + final String fileName = dir.getFileName().toString(); + if (!fileName.equals(currentBaseFolder) + && !fileName.equals(selfTargetingDir)) { + if (!dir.toFile().delete()) { + Log.w(TAG, "Failed to delete dir " + dir); + } + } + return super.postVisitDirectory(dir, exc); + } + }); + } catch (IOException e) { + Log.w(TAG, "Unknown fail " + e); + } + } + + /** + * Ensure the base dir for self-targeting is valid. + */ + @VisibleForTesting + public void ensureBaseDir() { + final String baseApkPath = mContext.getApplicationInfo().getBaseCodePath(); + final Path baseApkFolderName = Path.of(baseApkPath).getParent().getFileName(); + final File selfTargetingBaseFile = mContext.getDir(SELF_TARGET, MODE_PRIVATE); + Preconditions.checkArgument( + selfTargetingBaseFile.isDirectory() + && selfTargetingBaseFile.exists() + && selfTargetingBaseFile.canWrite() + && selfTargetingBaseFile.canRead() + && selfTargetingBaseFile.canExecute(), + "Can't work for this context"); + cleanExpiredOverlays(selfTargetingBaseFile.toPath(), baseApkFolderName); + + final File baseFile = new File(selfTargetingBaseFile, baseApkFolderName.toString()); + if (!baseFile.exists()) { + if (!baseFile.mkdirs()) { + Log.w(TAG, "Failed to create directory " + baseFile); + } + + // It fails to create frro and idmap files without this setting. + FileUtils.setPermissions( + baseFile, + FileUtils.S_IRWXU, + -1 /* uid unchanged */, + -1 /* gid unchanged */); + } + Preconditions.checkArgument( + baseFile.isDirectory() + && baseFile.exists() + && baseFile.canWrite() + && baseFile.canRead() + && baseFile.canExecute(), // 'list' capability + "Can't create a workspace for this context"); + + mBasePath = baseFile.toPath(); + } + + /** + * Check if the overlay name is valid or not. + * + * @param name the non-check overlay name + * @return the valid overlay name + */ + private static String checkOverlayNameValid(@NonNull String name) { + final String overlayName = + Preconditions.checkStringNotEmpty( + name, "overlayName should be neither empty nor null string"); + final String checkOverlayNameResult = + FrameworkParsingPackageUtils.validateName( + overlayName, false /* requireSeparator */, true /* requireFilename */); + Preconditions.checkArgument( + checkOverlayNameResult == null, + TextUtils.formatSimple( + "Invalid overlayName \"%s\". The check result is %s.", + overlayName, checkOverlayNameResult)); + return overlayName; + } + + private void checkPackageName(@NonNull String packageName) { + Preconditions.checkStringNotEmpty(packageName); + Preconditions.checkArgument( + TextUtils.equals(mContext.getPackageName(), packageName), + TextUtils.formatSimple( + "UID %d doesn't own the package %s", Process.myUid(), packageName)); + } + + /** + * Save FabricatedOverlay instance as frro and idmap files. + * + * @param overlayInternal the FabricatedOverlayInternal to be saved. + */ + public void registerFabricatedOverlay(@NonNull FabricatedOverlayInternal overlayInternal) + throws IOException, PackageManager.NameNotFoundException { + ensureBaseDir(); + Objects.requireNonNull(overlayInternal); + final List<FabricatedOverlayInternalEntry> entryList = + Objects.requireNonNull(overlayInternal.entries); + Preconditions.checkArgument(!entryList.isEmpty(), "overlay entries shouldn't be empty"); + final String overlayName = checkOverlayNameValid(overlayInternal.overlayName); + checkPackageName(overlayInternal.packageName); + checkPackageName(overlayInternal.targetPackageName); + + final ApplicationInfo applicationInfo = mContext.getApplicationInfo(); + final String targetPackage = Preconditions.checkStringNotEmpty( + applicationInfo.getBaseCodePath()); + final Path frroPath = mBasePath.resolve(overlayName + FRRO_EXTENSION); + final Path idmapPath = mBasePath.resolve(overlayName + IDMAP_EXTENSION); + + createFrroFile(frroPath.toString(), overlayInternal); + try { + createIdmapFile(targetPackage, frroPath.toString(), idmapPath.toString(), overlayName); + } catch (IOException e) { + if (!frroPath.toFile().delete()) { + Log.w(TAG, "Failed to delete file " + frroPath); + } + throw e; + } + } + + /** + * Remove the overlay with the specific name + * + * @param overlayName the specific name + */ + public void unregisterFabricatedOverlay(@NonNull String overlayName) { + ensureBaseDir(); + checkOverlayNameValid(overlayName); + final Path frroPath = mBasePath.resolve(overlayName + FRRO_EXTENSION); + final Path idmapPath = mBasePath.resolve(overlayName + IDMAP_EXTENSION); + + if (!frroPath.toFile().delete()) { + Log.w(TAG, "Failed to delete file " + frroPath); + } + if (!idmapPath.toFile().delete()) { + Log.w(TAG, "Failed to delete file " + idmapPath); + } + } + + /** + * Get the list of overlays information for the target package name. + * + * @param targetPackage the target package name + * @return the list of overlays information. + */ + @NonNull + public List<OverlayInfo> getOverlayInfosForTarget(@NonNull String targetPackage) { + ensureBaseDir(); + + final File base = mBasePath.toFile(); + final File[] frroFiles = base.listFiles((dir, name) -> { + if (!name.endsWith(FRRO_EXTENSION)) { + return false; + } + + final String idmapFileName = name.substring(0, name.length() - FRRO_EXTENSION.length()) + + IDMAP_EXTENSION; + final File idmapFile = new File(dir, idmapFileName); + return idmapFile.exists(); + }); + + final ArrayList<OverlayInfo> overlayInfos = new ArrayList<>(); + for (File file : frroFiles) { + final FabricatedOverlayInfo fabricatedOverlayInfo; + try { + fabricatedOverlayInfo = getFabricatedOverlayInfo(file.getAbsolutePath()); + } catch (IOException e) { + Log.w(TAG, "can't load " + file); + continue; + } + if (!TextUtils.equals(targetPackage, fabricatedOverlayInfo.targetPackageName)) { + continue; + } + if (DEBUG) { + Log.i(TAG, "load " + file); + } + + final OverlayInfo overlayInfo = + new OverlayInfo( + fabricatedOverlayInfo.packageName, + fabricatedOverlayInfo.overlayName, + fabricatedOverlayInfo.targetPackageName, + fabricatedOverlayInfo.targetOverlayable, + null, + file.getAbsolutePath(), + OverlayInfo.STATE_ENABLED, + UserHandle.myUserId(), + DEFAULT_PRIORITY, + true /* isMutable */, + true /* isFabricated */); + overlayInfos.add(overlayInfo); + } + return overlayInfos; + } + + private static native void createFrroFile( + @NonNull String frroFile, @NonNull FabricatedOverlayInternal fabricatedOverlayInternal) + throws IOException; + + private static native void createIdmapFile( + @NonNull String targetPath, + @NonNull String overlayPath, + @NonNull String idmapPath, + @NonNull String overlayName) + throws IOException; + + private static native FabricatedOverlayInfo getFabricatedOverlayInfo( + @NonNull String overlayPath) throws IOException; +} diff --git a/core/jni/Android.bp b/core/jni/Android.bp index d8b91c7dca79..f140e7980f52 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -215,6 +215,7 @@ cc_library_shared { "android_content_res_ResourceTimer.cpp", "android_security_Scrypt.cpp", "com_android_internal_content_om_OverlayConfig.cpp", + "com_android_internal_content_om_OverlayManagerImpl.cpp", "com_android_internal_expresslog_Counter.cpp", "com_android_internal_net_NetworkUtilsInternal.cpp", "com_android_internal_os_ClassLoaderFactory.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index f549cd828e7d..9e563dec3e4e 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -198,6 +198,7 @@ extern int register_android_security_Scrypt(JNIEnv *env); extern int register_com_android_internal_content_F2fsUtils(JNIEnv* env); extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env); extern int register_com_android_internal_content_om_OverlayConfig(JNIEnv *env); +extern int register_com_android_internal_content_om_OverlayManagerImpl(JNIEnv* env); extern int register_com_android_internal_expresslog_Counter(JNIEnv* env); extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env); extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env); @@ -1587,6 +1588,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_os_SharedMemory), REG_JNI(register_android_os_incremental_IncrementalManager), REG_JNI(register_com_android_internal_content_om_OverlayConfig), + REG_JNI(register_com_android_internal_content_om_OverlayManagerImpl), REG_JNI(register_com_android_internal_expresslog_Counter), REG_JNI(register_com_android_internal_net_NetworkUtilsInternal), REG_JNI(register_com_android_internal_os_ClassLoaderFactory), diff --git a/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp b/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp new file mode 100644 index 000000000000..df55e424f2ed --- /dev/null +++ b/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp @@ -0,0 +1,462 @@ +/* + * 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; +} 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) { + return createIdmapFileFuncPtr_(out_error, targetPath, overlayPath, idmapPath, overlayName); + } + + 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); + + 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 auto 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 >= 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); + entries_params.push_back( + FabricatedOverlayEntryParameters{resourceName.c_str(), (DataType)dataType, + (DataValue)data, + string_data.value_or(std::string()), binary_data, + configuration.value_or(std::string())}); + 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) { + 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())) { + 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;)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;"); + + 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 diff --git a/core/tests/overlaytests/device_self_targeting/Android.bp b/core/tests/overlaytests/device_self_targeting/Android.bp new file mode 100644 index 000000000000..82998dbdae27 --- /dev/null +++ b/core/tests/overlaytests/device_self_targeting/Android.bp @@ -0,0 +1,39 @@ +// 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. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "SelfTargetingOverlayDeviceTests", + srcs: ["src/**/*.java"], + platform_apis: true, + static_libs: [ + "androidx.test.rules", + "androidx.test.runner", + "androidx.test.ext.junit", + "truth-prebuilt", + ], + + optimize: { + enabled: false, + }, + test_suites: ["device-tests"], +} diff --git a/core/tests/overlaytests/device_self_targeting/AndroidManifest.xml b/core/tests/overlaytests/device_self_targeting/AndroidManifest.xml new file mode 100644 index 000000000000..c121bf28fa7a --- /dev/null +++ b/core/tests/overlaytests/device_self_targeting/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.overlaytest.self_targeting"> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.overlaytest.self_targeting" + android:label="Self-Targeting resource overlay tests" /> +</manifest> diff --git a/core/tests/overlaytests/device_self_targeting/res/drawable/mydrawable.webp b/core/tests/overlaytests/device_self_targeting/res/drawable/mydrawable.webp Binary files differnew file mode 100644 index 000000000000..aa7d6427e6fa --- /dev/null +++ b/core/tests/overlaytests/device_self_targeting/res/drawable/mydrawable.webp diff --git a/core/tests/overlaytests/device_self_targeting/res/raw/overlay_drawable.webp b/core/tests/overlaytests/device_self_targeting/res/raw/overlay_drawable.webp Binary files differnew file mode 100644 index 000000000000..9126ae37cbc3 --- /dev/null +++ b/core/tests/overlaytests/device_self_targeting/res/raw/overlay_drawable.webp diff --git a/core/tests/overlaytests/device_self_targeting/res/values/values.xml b/core/tests/overlaytests/device_self_targeting/res/values/values.xml new file mode 100644 index 000000000000..f0b4a6fe8969 --- /dev/null +++ b/core/tests/overlaytests/device_self_targeting/res/values/values.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ 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. + --> + +<resources> + <color name="mycolor">#ff112233</color> + <string name="mystring">hello</string> +</resources> diff --git a/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java b/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java new file mode 100644 index 000000000000..ca584106a910 --- /dev/null +++ b/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java @@ -0,0 +1,411 @@ +/* + * 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. + */ + +package com.android.overlaytest; + +import static android.content.Context.MODE_PRIVATE; + +import static com.android.internal.content.om.OverlayManagerImpl.SELF_TARGET; + +import static org.junit.Assert.assertThrows; + +import android.annotation.NonNull; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.om.OverlayInfo; +import android.content.pm.PackageManager; +import android.graphics.Color; +import android.os.FabricatedOverlayInternal; +import android.os.FabricatedOverlayInternalEntry; +import android.os.ParcelFileDescriptor; +import android.os.UserHandle; +import android.util.Log; +import android.util.Pair; +import android.util.TypedValue; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.internal.content.om.OverlayManagerImpl; +import com.android.overlaytest.self_targeting.R; + +import com.google.common.truth.Expect; +import com.google.common.truth.Truth; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.List; + +/** + * This test class verify the interfaces of {@link + * com.android.internal.content.om.OverlayManagerImpl}. + */ +@RunWith(AndroidJUnit4.class) +public class OverlayManagerImplTest { + private static final String TAG = "OverlayManagerImplTest"; + + private static final String TARGET_COLOR_RES = "color/mycolor"; + private static final String TARGET_STRING_RES = "string/mystring"; + private static final String TARGET_DRAWABLE_RES = "drawable/mydrawable"; + + private Context mContext; + private OverlayManagerImpl mOverlayManagerImpl; + private String mOverlayName; + + @Rule public TestName mTestName = new TestName(); + + @Rule public Expect expect = Expect.create(); + + private void clearDir() throws IOException { + final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + final Path basePath = context.getDir(SELF_TARGET, MODE_PRIVATE).toPath(); + Files.walkFileTree( + basePath, + new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + if (!file.toFile().delete()) { + Log.w(TAG, "Failed to delete file " + file); + } + return super.visitFile(file, attrs); + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) + throws IOException { + if (!dir.toFile().delete()) { + Log.w(TAG, "Failed to delete dir " + dir); + } + return super.postVisitDirectory(dir, exc); + } + }); + } + + @Before + public void setUp() throws IOException { + clearDir(); + mOverlayName = mTestName.getMethodName(); + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + mOverlayManagerImpl = new OverlayManagerImpl(mContext); + } + + @After + public void tearDown() throws IOException { + clearDir(); + } + + private <T> void addOverlayEntry( + FabricatedOverlayInternal overlayInternal, + @NonNull List<Pair<String, Pair<String, T>>> entryDefinitions) { + List<FabricatedOverlayInternalEntry> entries = new ArrayList<>(); + for (Pair<String, Pair<String, T>> entryDefinition : entryDefinitions) { + FabricatedOverlayInternalEntry internalEntry = new FabricatedOverlayInternalEntry(); + internalEntry.resourceName = entryDefinition.first; + internalEntry.configuration = entryDefinition.second.first; + if (entryDefinition.second.second instanceof ParcelFileDescriptor) { + internalEntry.binaryData = (ParcelFileDescriptor) entryDefinition.second.second; + } else if (entryDefinition.second.second instanceof String) { + internalEntry.stringData = (String) entryDefinition.second.second; + internalEntry.dataType = TypedValue.TYPE_STRING; + } else { + internalEntry.data = (int) entryDefinition.second.second; + internalEntry.dataType = TypedValue.TYPE_INT_COLOR_ARGB8; + } + entries.add(internalEntry); + overlayInternal.entries = entries; + } + } + + private <T> FabricatedOverlayInternal createOverlayWithName( + @NonNull String overlayName, + @NonNull String targetPackageName, + @NonNull List<Pair<String, Pair<String, T>>> entryDefinitions) { + final String packageName = mContext.getPackageName(); + FabricatedOverlayInternal overlayInternal = new FabricatedOverlayInternal(); + overlayInternal.overlayName = overlayName; + overlayInternal.targetPackageName = targetPackageName; + overlayInternal.packageName = packageName; + + addOverlayEntry(overlayInternal, entryDefinitions); + + return overlayInternal; + } + + @Test + public void registerOverlay_forAndroidPackage_shouldFail() { + FabricatedOverlayInternal overlayInternal = + createOverlayWithName( + mOverlayName, + "android", + List.of(Pair.create("color/white", Pair.create(null, Color.BLACK)))); + + assertThrows( + "Wrong target package name", + IllegalArgumentException.class, + () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal)); + } + + @Test + public void getOverlayInfosForTarget_defaultShouldBeZero() { + List<OverlayInfo> overlayInfos = + mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()); + + Truth.assertThat(overlayInfos.size()).isEqualTo(0); + } + + @Test + public void unregisterNonExistingOverlay_shouldBeOk() { + mOverlayManagerImpl.unregisterFabricatedOverlay("NotExisting"); + } + + @Test + public void registerOverlay_createColorOverlay_shouldBeSavedInAndLoadFromFile() + throws IOException, PackageManager.NameNotFoundException { + FabricatedOverlayInternal overlayInternal = + createOverlayWithName( + mOverlayName, + mContext.getPackageName(), + List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE)))); + + mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal); + final List<OverlayInfo> overlayInfos = + mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()); + + final int firstNumberOfOverlays = overlayInfos.size(); + expect.that(firstNumberOfOverlays).isEqualTo(1); + final OverlayInfo overlayInfo = overlayInfos.get(0); + expect.that(overlayInfo).isNotNull(); + Truth.assertThat(expect.hasFailures()).isFalse(); + expect.that(overlayInfo.isFabricated()).isTrue(); + expect.that(overlayInfo.getOverlayName()).isEqualTo(mOverlayName); + expect.that(overlayInfo.getPackageName()).isEqualTo(mContext.getPackageName()); + expect.that(overlayInfo.getTargetPackageName()).isEqualTo(mContext.getPackageName()); + expect.that(overlayInfo.getUserId()).isEqualTo(mContext.getUserId()); + } + + @Test + public void registerOverlay_createStringOverlay_shouldBeSavedInAndLoadFromFile() + throws IOException, PackageManager.NameNotFoundException { + FabricatedOverlayInternal overlayInternal = + createOverlayWithName( + mOverlayName, + mContext.getPackageName(), + List.of(Pair.create(TARGET_STRING_RES, Pair.create(null, "HELLO")))); + + mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal); + final List<OverlayInfo> overlayInfos = + mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()); + + final int firstNumberOfOverlays = overlayInfos.size(); + expect.that(firstNumberOfOverlays).isEqualTo(1); + final OverlayInfo overlayInfo = overlayInfos.get(0); + expect.that(overlayInfo).isNotNull(); + Truth.assertThat(expect.hasFailures()).isFalse(); + expect.that(overlayInfo.isFabricated()).isTrue(); + expect.that(overlayInfo.getOverlayName()).isEqualTo(mOverlayName); + expect.that(overlayInfo.getPackageName()).isEqualTo(mContext.getPackageName()); + expect.that(overlayInfo.getTargetPackageName()).isEqualTo(mContext.getPackageName()); + expect.that(overlayInfo.getUserId()).isEqualTo(mContext.getUserId()); + } + + @Test + public void registerOverlay_createFileOverlay_shouldBeSavedInAndLoadFromFile() + throws IOException, PackageManager.NameNotFoundException { + ParcelFileDescriptor parcelFileDescriptor = mContext.getResources() + .openRawResourceFd(R.raw.overlay_drawable).getParcelFileDescriptor(); + FabricatedOverlayInternal overlayInternal = + createOverlayWithName( + mOverlayName, + mContext.getPackageName(), + List.of(Pair.create(TARGET_DRAWABLE_RES, + Pair.create(null, parcelFileDescriptor)))); + + mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal); + final List<OverlayInfo> overlayInfos = + mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()); + + final int firstNumberOfOverlays = overlayInfos.size(); + expect.that(firstNumberOfOverlays).isEqualTo(1); + final OverlayInfo overlayInfo = overlayInfos.get(0); + expect.that(overlayInfo).isNotNull(); + Truth.assertThat(expect.hasFailures()).isFalse(); + expect.that(overlayInfo.isFabricated()).isTrue(); + expect.that(overlayInfo.getOverlayName()).isEqualTo(mOverlayName); + expect.that(overlayInfo.getPackageName()).isEqualTo(mContext.getPackageName()); + expect.that(overlayInfo.getTargetPackageName()).isEqualTo(mContext.getPackageName()); + expect.that(overlayInfo.getUserId()).isEqualTo(mContext.getUserId()); + } + + @Test + public void registerOverlay_notExistedResource_shouldFailWithoutSavingAnyFile() + throws IOException { + FabricatedOverlayInternal overlayInternal = + createOverlayWithName( + mOverlayName, + mContext.getPackageName(), + List.of(Pair.create("color/not_existed", Pair.create(null, "HELLO")))); + + assertThrows(IOException.class, + () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal)); + final List<OverlayInfo> overlayInfos = + mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()); + final int firstNumberOfOverlays = overlayInfos.size(); + expect.that(firstNumberOfOverlays).isEqualTo(0); + final int[] fileCounts = new int[1]; + Files.walkFileTree( + mContext.getDir(SELF_TARGET, MODE_PRIVATE).toPath(), + new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + fileCounts[0]++; + return super.visitFile(file, attrs); + } + }); + expect.that(fileCounts[0]).isEqualTo(0); + } + + @Test + public void registerMultipleOverlays_shouldMatchTheNumberOfOverlays() + throws IOException, PackageManager.NameNotFoundException { + final String secondOverlayName = mOverlayName + "2nd"; + final int initNumberOfOverlays = + mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size(); + + FabricatedOverlayInternal overlayInternal = + createOverlayWithName( + mOverlayName, + mContext.getPackageName(), + List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE)))); + mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal); + final int firstNumberOfOverlays = + mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size(); + overlayInternal = + createOverlayWithName( + secondOverlayName, + mContext.getPackageName(), + List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE)))); + mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal); + final int secondNumberOfOverlays = + mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size(); + mOverlayManagerImpl.unregisterFabricatedOverlay(mOverlayName); + mOverlayManagerImpl.unregisterFabricatedOverlay(secondOverlayName); + final int finalNumberOfOverlays = + mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size(); + + expect.that(initNumberOfOverlays).isEqualTo(0); + expect.that(firstNumberOfOverlays).isEqualTo(1); + expect.that(secondNumberOfOverlays).isEqualTo(2); + expect.that(finalNumberOfOverlays).isEqualTo(0); + } + + @Test + public void unregisterOverlay_withIllegalOverlayName_shouldFail() { + assertThrows( + IllegalArgumentException.class, + () -> mOverlayManagerImpl.unregisterFabricatedOverlay("../../etc/password")); + } + + @Test + public void registerTheSameOverlay_shouldNotIncreaseTheNumberOfOverlays() + throws IOException, PackageManager.NameNotFoundException { + final int initNumberOfOverlays = + mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size(); + + FabricatedOverlayInternal overlayInternal = + createOverlayWithName( + mOverlayName, + mContext.getPackageName(), + List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE)))); + mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal); + final int firstNumberOfOverlays = + mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size(); + overlayInternal = + createOverlayWithName( + mOverlayName, + mContext.getPackageName(), + List.of(Pair.create(TARGET_COLOR_RES, Pair.create(null, Color.WHITE)))); + mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal); + final int secondNumberOfOverlays = + mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size(); + mOverlayManagerImpl.unregisterFabricatedOverlay(mOverlayName); + final int finalNumberOfOverlays = + mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName()).size(); + + expect.that(initNumberOfOverlays).isEqualTo(0); + expect.that(firstNumberOfOverlays).isEqualTo(1); + expect.that(secondNumberOfOverlays).isEqualTo(1); + expect.that(finalNumberOfOverlays).isEqualTo(0); + } + + @Test + public void registerOverlay_packageNotOwnedBySelf_shouldFail() { + FabricatedOverlayInternal overlayInternal = new FabricatedOverlayInternal(); + overlayInternal.packageName = "com.android.systemui"; + overlayInternal.overlayName = mOverlayName; + overlayInternal.targetOverlayable = "non-existed-target-overlayable"; + overlayInternal.targetPackageName = mContext.getPackageName(); + addOverlayEntry( + overlayInternal, + List.of(Pair.create("color/white", Pair.create(null, Color.BLACK)))); + + assertThrows( + "The context doesn't own the package", + IllegalArgumentException.class, + () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal)); + } + + @Test + public void ensureBaseDir_forOtherPackage_shouldFail() + throws PackageManager.NameNotFoundException { + final Context fakeContext = + mContext.createPackageContext("com.android.systemui", 0 /* flags */); + final OverlayManagerImpl overlayManagerImpl = new OverlayManagerImpl(fakeContext); + + assertThrows(IllegalArgumentException.class, overlayManagerImpl::ensureBaseDir); + } + + @Test + public void newOverlayManagerImpl_forOtherUser_shouldFail() { + Context fakeContext = + new ContextWrapper(mContext) { + @Override + public UserHandle getUser() { + return UserHandle.of(100); + } + + @Override + public int getUserId() { + return 100; + } + }; + + assertThrows(SecurityException.class, () -> new OverlayManagerImpl(fakeContext)); + } +} diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index c740832522fc..b2b95b72e0e0 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -1830,6 +1830,28 @@ inline ResTable_overlayable_policy_header::PolicyFlags& operator |=( return first; } +using ResourceId = uint32_t; // 0xpptteeee + +using DataType = uint8_t; // Res_value::dataType +using DataValue = uint32_t; // Res_value::data + +struct OverlayManifestInfo { + std::string package_name; // NOLINT(misc-non-private-member-variables-in-classes) + std::string name; // NOLINT(misc-non-private-member-variables-in-classes) + std::string target_package; // NOLINT(misc-non-private-member-variables-in-classes) + std::string target_name; // NOLINT(misc-non-private-member-variables-in-classes) + ResourceId resource_mapping; // NOLINT(misc-non-private-member-variables-in-classes) +}; + +struct FabricatedOverlayEntryParameters { + std::string resource_name; + DataType data_type; + DataValue data_value; + std::string data_string_value; + std::optional<android::base::borrowed_fd> data_binary_value; + std::string configuration; +}; + class AssetManager2; /** |