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;  /**  |