Loading cmds/idmap2/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -71,6 +71,7 @@ cc_library { host_supported: true, srcs: [ "libidmap2/**/*.cpp", "self_targeting/*.cpp", ], export_include_dirs: ["include"], target: { Loading cmds/idmap2/include/idmap2/ResourceContainer.h +0 −8 Original line number Diff line number Diff line Loading @@ -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. Loading cmds/idmap2/include/idmap2/ResourceUtils.h +3 −3 Original line number Diff line number Diff line Loading @@ -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; Loading cmds/idmap2/self_targeting/SelfTargeting.cpp 0 → 100644 +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 core/java/com/android/internal/content/om/OverlayManagerImpl.java 0 → 100644 +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
cmds/idmap2/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -71,6 +71,7 @@ cc_library { host_supported: true, srcs: [ "libidmap2/**/*.cpp", "self_targeting/*.cpp", ], export_include_dirs: ["include"], target: { Loading
cmds/idmap2/include/idmap2/ResourceContainer.h +0 −8 Original line number Diff line number Diff line Loading @@ -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. Loading
cmds/idmap2/include/idmap2/ResourceUtils.h +3 −3 Original line number Diff line number Diff line Loading @@ -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; Loading
cmds/idmap2/self_targeting/SelfTargeting.cpp 0 → 100644 +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
core/java/com/android/internal/content/om/OverlayManagerImpl.java 0 → 100644 +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; }