From 1039d61b3603a2e2bb46afac90d1144629f66045 Mon Sep 17 00:00:00 2001 From: felkachang Date: Tue, 21 Jun 2022 15:09:47 +0800 Subject: [PATCH] Add OverlayManagerImpl to create frro, idmap and get overlayInfos Add OverlayManagerImpl to let applications create frro and idmap files for overlaying itself's android resources. * OverlayManagerImpl.java * check the FabricatedOverlayInternal. * com_android_internal_content_om_OverlayManagerImpl.cpp * convert and read Java objects to native data. * call the APIs in libidmap2 to create frro and idmap files. Add OverlayManagerImpl.cpp into AndroidRuntime by modifying Android.bp * Add register_com_android_internal_content_om_OverlayManagerImpl register JNI methods * add com_android_internal_content_om_OverlayManagerImpl.cpp * It's a adapter to delegate the tasks to SelfTargeting.cpp * dynamic link with libidmap2 by using dlopen and dlsym to call functions in libidmap2. Add SelfTargeting.cpp into libidmap2 * createFrroFile * createIdmapFile * getFabricatedOverlayInfo for interoperability between libandroid_runtime and libidmap2. * move OverlayManifestInfo from libidmap2 to libandroidfw * add FabricatedOverlayEntryParameters into libandroidfw Bug: 205919743 Test: build Test: atest \ OverlayHostTests \ OverlayDeviceTests \ SelfTargetingOverlayDeviceTests \ OverlayRemountedTest \ FrameworksServicesTests:com.android.server.om \ CtsContentTestCases:android.content.om.cts \ idmap2_tests Change-Id: I5425f3229e9a3858e57427ef84e6abaf32e89b6e --- cmds/idmap2/Android.bp | 1 + .../idmap2/include/idmap2/ResourceContainer.h | 8 - cmds/idmap2/include/idmap2/ResourceUtils.h | 6 +- cmds/idmap2/self_targeting/SelfTargeting.cpp | 170 +++++++ .../content/om/OverlayManagerImpl.java | 323 ++++++++++++ core/jni/Android.bp | 1 + core/jni/AndroidRuntime.cpp | 2 + ...internal_content_om_OverlayManagerImpl.cpp | 462 ++++++++++++++++++ .../device_self_targeting/Android.bp | 39 ++ .../device_self_targeting/AndroidManifest.xml | 23 + .../res/drawable/mydrawable.webp | Bin 0 -> 3844 bytes .../res/raw/overlay_drawable.webp | Bin 0 -> 7778 bytes .../res/values/values.xml | 20 + .../overlaytest/OverlayManagerImplTest.java | 411 ++++++++++++++++ .../include/androidfw/ResourceTypes.h | 22 + 15 files changed, 1477 insertions(+), 11 deletions(-) create mode 100644 cmds/idmap2/self_targeting/SelfTargeting.cpp create mode 100644 core/java/com/android/internal/content/om/OverlayManagerImpl.java create mode 100644 core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp create mode 100644 core/tests/overlaytests/device_self_targeting/Android.bp create mode 100644 core/tests/overlaytests/device_self_targeting/AndroidManifest.xml create mode 100644 core/tests/overlaytests/device_self_targeting/res/drawable/mydrawable.webp create mode 100644 core/tests/overlaytests/device_self_targeting/res/raw/overlay_drawable.webp create mode 100644 core/tests/overlaytests/device_self_targeting/res/values/values.xml create mode 100644 core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp index 7a08cbdcddd4a..5f06c971ba987 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 2452ff0784ce2..4d2832113f714 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 2214a83bd2da5..c2b0abed442cd 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 0000000000000..20aa7d32a3c29 --- /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 + +#include +#include + +#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& targetOverlayable, + std::vector& 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( + 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 0000000000000..76e068d11d74c --- /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 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 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 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 d8b91c7dca793..f140e7980f52e 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 f549cd828e7d0..9e563dec3e4ed 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 0000000000000..df55e424f2ed6 --- /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 + +#include + +#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(dlsym(handle_, "CreateIdmapFile")); + if (createIdmapFileFuncPtr_ == nullptr) { + jniThrowNullPointerException(env, "The symbol CreateIdmapFile is not found."); + return; + } + getFabricatedOverlayInfoFuncPtr_ = reinterpret_cast( + dlsym(handle_, "GetFabricatedOverlayInfo")); + if (getFabricatedOverlayInfoFuncPtr_ == nullptr) { + jniThrowNullPointerException(env, "The symbol GetFabricatedOverlayInfo is not found."); + return; + } + createFrroFile_ = reinterpret_cast(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& targetOverlayable, + const std::vector& 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& targetOverlayable, + const std::vector& 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 loader(env); + return *loader; +} + +static std::optional getNullableString(JNIEnv* env, jobject object, jfieldID field) { + auto javaString = reinterpret_cast(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{result.c_str()}; +} + +static std::optional 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 entries_params; + for (jint i = 0; i < size; i++) { + jobject entry = env->CallObjectMethod(entries, gListOffsets.get, i); + auto jsResourceName = reinterpret_cast( + 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(self_targeting::CreateFrroFile)}, + {"createIdmapFile", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V", + reinterpret_cast(self_targeting::CreateIdmapFile)}, + {"getFabricatedOverlayInfo", "(Ljava/lang/String;)Landroid/os/FabricatedOverlayInfo;", + reinterpret_cast(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, "", "()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 0000000000000..82998dbdae278 --- /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 0000000000000..c121bf28fa7af --- /dev/null +++ b/core/tests/overlaytests/device_self_targeting/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + diff --git a/core/tests/overlaytests/device_self_targeting/res/drawable/mydrawable.webp b/core/tests/overlaytests/device_self_targeting/res/drawable/mydrawable.webp new file mode 100644 index 0000000000000000000000000000000000000000..aa7d6427e6fa1074b79ccd52ef67ac15c5637e85 GIT binary patch literal 3844 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

UmY2RIYtNPVDpE$%vda+HD#3m z&VuXJ{BK&Qe+rBa7eq}Q(bq|tn(RrJAk|ztj2(i{d>nmQnM?;HF2k&9sA6up5tmjl z7lySlzMbifH17-m-Lwa_F&e7nOH?ESi3#ckR3tsM+jsck3`oG!uMS}|eAwVXv>}qxwq?QY%QJ0}r@^;fhuUA9W z*BVl>TGo&N004@xSiwDUXUvp51sVmqO3m)=B55aPwf@0=e}cN+$-BdKxY`YrT_4)0 z_d10#i44Q*rFr8MC>*)v$EJvz``(pb{e&*6k+b zsMz%($|1+8hn8c2?P(l@;Rb&CsZeYoCI3?2!LqjbwPXW3z4G$Qfj=cT5Yb%vY0(AX oeb?AaKtwrnc|$|zzw9vfvn^aJJ!zd)XFXqqy0000001=f@-~a#s literal 0 HcmV?d00001 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 0000000000000..f0b4a6fe89696 --- /dev/null +++ b/core/tests/overlaytests/device_self_targeting/res/values/values.xml @@ -0,0 +1,20 @@ + + + + #ff112233 + hello + 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 0000000000000..ca584106a910b --- /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 void addOverlayEntry( + FabricatedOverlayInternal overlayInternal, + @NonNull List>> entryDefinitions) { + List entries = new ArrayList<>(); + for (Pair> 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 FabricatedOverlayInternal createOverlayWithName( + @NonNull String overlayName, + @NonNull String targetPackageName, + @NonNull List>> 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 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 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 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 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 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 c740832522fc3..b2b95b72e0e0d 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 data_binary_value; + std::string configuration; +}; + class AssetManager2; /**