Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a526f37a authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add OverlayManagerImpl to create frro, idmap and get overlayInfos"

parents 453249c2 1039d61b
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -71,6 +71,7 @@ cc_library {
    host_supported: true,
    srcs: [
        "libidmap2/**/*.cpp",
        "self_targeting/*.cpp",
    ],
    export_include_dirs: ["include"],
    target: {
+0 −8
Original line number Diff line number Diff line
@@ -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.
+3 −3
Original line number Diff line number Diff line
@@ -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;
+170 −0
Original line number Diff line number Diff line
/*
 * 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
+323 −0
Original line number Diff line number Diff line
/*
 * 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;
}
Loading