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

Commit c3e47ec1 authored by Pawan Wagh's avatar Pawan Wagh
Browse files

Implement alignment checks in PM

- Adding alignment check functions for APK and ELF.
- Calling the alignment checks when package is getting scanned
- Check alignment for extacted libs

Flag: android.content.pm.app_compat_option_16kb
Test: atest -c FileSystemUtilsTests
Test: Install 4KB aligned app and see the dialog on 16 KB device
Bug: 371049373

Change-Id: Ia6b48248e784139477f4809c023c885ede01a80b
parent 5f3fa672
Loading
Loading
Loading
Loading
+53 −0
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import static android.system.OsConstants.S_IXGRP;
import static android.system.OsConstants.S_IXOTH;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.parsing.ApkLiteParseUtils;
import android.content.pm.parsing.PackageLite;
@@ -177,6 +178,13 @@ public class NativeLibraryHelper {
    private native static int nativeCopyNativeBinaries(long handle, String sharedLibraryPath,
            String abiToCopy, boolean extractNativeLibs, boolean debuggable);

    private static native int nativeCheckAlignment(
            long handle,
            String sharedLibraryPath,
            String abi,
            boolean extractNativeLibs,
            boolean debuggable);

    private static long sumNativeBinaries(Handle handle, String abi) {
        long sum = 0;
        for (long apkHandle : handle.apkHandles) {
@@ -432,6 +440,51 @@ public class NativeLibraryHelper {
        }
    }

    /**
     * Checks alignment of APK and native libraries for 16KB device
     *
     * @param handle APK file to scan for native libraries
     * @param libraryRoot directory for libraries
     * @param abiOverride abiOverride for package
     * @return {@link Modes from ApplicationInfo.PageSizeAppCompat} if successful or error code
     *     which suggests undefined mode
     */
    @ApplicationInfo.PageSizeAppCompatFlags
    public static int checkAlignmentForCompatMode(
            Handle handle,
            String libraryRoot,
            boolean nativeLibraryRootRequiresIsa,
            String abiOverride) {
        // Keep the code below in sync with copyNativeBinariesForSupportedAbi
        int abi = findSupportedAbi(handle, Build.SUPPORTED_64_BIT_ABIS);
        if (abi < 0) {
            return ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
        }

        final String supportedAbi = Build.SUPPORTED_64_BIT_ABIS[abi];
        final String instructionSet = VMRuntime.getInstructionSet(supportedAbi);
        String subDir = libraryRoot;
        if (nativeLibraryRootRequiresIsa) {
            subDir += "/" + instructionSet;
        }

        int mode = ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
        for (long apkHandle : handle.apkHandles) {
            int res =
                    nativeCheckAlignment(
                            apkHandle,
                            subDir,
                            Build.SUPPORTED_64_BIT_ABIS[abi],
                            handle.extractNativeLibs,
                            handle.debuggable);
            if (res == ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_ERROR) {
                return res;
            }
            mode |= res;
        }
        return mode;
    }

    public static long sumNativeBinariesWithOverride(Handle handle, String abiOverride)
            throws IOException {
        long sum = 0;
+184 −26
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@
#include <androidfw/ApkParsing.h>
#include <androidfw/ZipFileRO.h>
#include <androidfw/ZipUtils.h>
#include <elf.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
@@ -38,6 +39,7 @@

#include <memory>
#include <string>
#include <vector>

#include "com_android_internal_content_FileSystemUtils.h"
#include "core_jni_helpers.h"
@@ -60,6 +62,12 @@ enum install_status_t {
    NO_NATIVE_LIBRARIES = -114
};

// These code should match with PageSizeAppCompatFlags inside ApplicationInfo.java
constexpr int PAGE_SIZE_APP_COMPAT_FLAG_ERROR = -1;
constexpr int PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED = 0;
constexpr int PAGE_SIZE_APP_COMPAT_FLAG_UNCOMPRESSED_LIBS_NOT_ALIGNED = 1 << 1;
constexpr int PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED = 1 << 2;

typedef install_status_t (*iterFunc)(JNIEnv*, void*, ZipFileRO*, ZipEntryRO, const char*);

static bool
@@ -524,11 +532,7 @@ static inline bool app_compat_16kb_enabled() {
    static const size_t kPageSize = getpagesize();

    // App compat is only applicable on 16kb-page-size devices.
    if (kPageSize != 0x4000) {
        return false;
    }

    return android::base::GetBoolProperty("bionic.linker.16kb.app_compat.enabled", false);
    return kPageSize == 0x4000;
}

static jint
@@ -626,6 +630,166 @@ com_android_internal_content_NativeLibraryHelper_openApkFd(JNIEnv *env, jclass,
    return reinterpret_cast<jlong>(zipFile);
}

static jint checkLoadSegmentAlignment(const char* fileName, off64_t offset) {
    std::vector<Elf64_Phdr> programHeaders;
    if (!getLoadSegmentPhdrs(fileName, offset, programHeaders)) {
        ALOGE("Failed to read program headers from ELF file.");
        return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
    }

    int mode = PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
    for (auto programHeader : programHeaders) {
        if (programHeader.p_type != PT_LOAD) {
            continue;
        }

        // Set ELF alignment bit if 4 KB aligned LOAD segment is found
        if (programHeader.p_align == 0x1000) {
            ALOGI("Setting page size compat mode PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED");
            mode |= PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED;
            break;
        }
    }

    return mode;
}

static jint checkExtractedLibAlignment(ZipFileRO* zipFile, ZipEntryRO zipEntry,
                                       const char* fileName, const std::string nativeLibPath) {
    // Build local file path
    const size_t fileNameLen = strlen(fileName);
    char localFileName[nativeLibPath.size() + fileNameLen + 2];

    if (strlcpy(localFileName, nativeLibPath.c_str(), sizeof(localFileName)) !=
        nativeLibPath.size()) {
        ALOGE("Couldn't allocate local file name for library");
        return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
    }

    *(localFileName + nativeLibPath.size()) = '/';

    if (strlcpy(localFileName + nativeLibPath.size() + 1, fileName,
                sizeof(localFileName) - nativeLibPath.size() - 1) != fileNameLen) {
        ALOGE("Couldn't allocate local file name for library");
        return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
    }

    struct statfs64 fsInfo;
    int result = statfs64(localFileName, &fsInfo);
    if (result < 0) {
        ALOGE("Failed to stat file :%s", localFileName);
        return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
    }

    return checkLoadSegmentAlignment(localFileName, 0);
}

static jint checkAlignment(JNIEnv* env, jstring javaNativeLibPath, jboolean extractNativeLibs,
                           ZipFileRO* zipFile, ZipEntryRO zipEntry, const char* fileName) {
    int mode = PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
    // Need two separate install status for APK and ELF alignment
    static const size_t kPageSize = getpagesize();
    jint ret = INSTALL_SUCCEEDED;

    ScopedUtfChars nativeLibPath(env, javaNativeLibPath);
    if (extractNativeLibs) {
        ALOGI("extractNativeLibs specified, checking for extracted lib %s", fileName);
        return checkExtractedLibAlignment(zipFile, zipEntry, fileName, nativeLibPath.c_str());
    }

    uint16_t method;
    off64_t offset;
    if (!zipFile->getEntryInfo(zipEntry, &method, nullptr, nullptr, &offset, nullptr, nullptr,
                               nullptr)) {
        ALOGE("Couldn't read zip entry info from zipFile %s", zipFile->getZipFileName());
        return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
    }

    // check if library is uncompressed and page-aligned
    if (method != ZipFileRO::kCompressStored) {
        ALOGE("Library '%s' is compressed - will not be able to open it directly from apk.\n",
              fileName);
        return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
    }

    if (offset % kPageSize != 0) {
        ALOGW("Library '%s' is not PAGE(%zu)-aligned - will not be able to open it directly "
              "from apk.\n",
              fileName, kPageSize);
        mode |= PAGE_SIZE_APP_COMPAT_FLAG_UNCOMPRESSED_LIBS_NOT_ALIGNED;
        ALOGI("Setting page size compat mode "
              "PAGE_SIZE_APP_COMPAT_FLAG_UNCOMPRESSED_LIBS_NOT_ALIGNED for %s",
              zipFile->getZipFileName());
    }

    int loadMode = checkLoadSegmentAlignment(zipFile->getZipFileName(), offset);
    if (loadMode == PAGE_SIZE_APP_COMPAT_FLAG_ERROR) {
        return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
    }
    mode |= loadMode;
    return mode;
}

// TODO(b/371049373): This function is copy of iterateOverNativeFiles with different way of handling
// and combining return values for all ELF and APKs. Find a way to consolidate two functions.
static jint com_android_internal_content_NativeLibraryHelper_checkApkAlignment(
        JNIEnv* env, jclass clazz, jlong apkHandle, jstring javaNativeLibPath, jstring javaCpuAbi,
        jboolean extractNativeLibs, jboolean debuggable) {
    int mode = PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
    ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle);
    if (zipFile == nullptr) {
        ALOGE("zipfile handle is null");
        return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
    }

    auto result = NativeLibrariesIterator::create(zipFile, debuggable);
    if (!result.ok()) {
        ALOGE("Can't iterate over native libs for file:%s", zipFile->getZipFileName());
        return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
    }
    std::unique_ptr<NativeLibrariesIterator> it(std::move(result.value()));

    const ScopedUtfChars cpuAbi(env, javaCpuAbi);
    if (cpuAbi.c_str() == nullptr) {
        ALOGE("cpuAbi is nullptr");
        // This would've thrown, so this return code isn't observable by Java.
        return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
    }

    while (true) {
        auto next = it->next();
        if (!next.ok()) {
            ALOGE("next iterator not found Error:%d", next.error());
            return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
        }
        auto entry = next.value();
        if (entry == nullptr) {
            break;
        }

        const char* fileName = it->currentEntry();
        const char* lastSlash = it->lastSlash();

        // Check to make sure the CPU ABI of this file is one we support.
        const char* cpuAbiOffset = fileName + APK_LIB_LEN;
        const size_t cpuAbiRegionSize = lastSlash - cpuAbiOffset;

        if (cpuAbi.size() == cpuAbiRegionSize &&
            !strncmp(cpuAbiOffset, cpuAbi.c_str(), cpuAbiRegionSize)) {
            int ret = checkAlignment(env, javaNativeLibPath, extractNativeLibs, zipFile, entry,
                                     lastSlash + 1);
            if (ret == PAGE_SIZE_APP_COMPAT_FLAG_ERROR) {
                ALOGE("Alignment check returned for zipfile: %s, entry:%s",
                      zipFile->getZipFileName(), lastSlash + 1);
                return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
            }
            mode |= ret;
        }
    }

    return mode;
}

static void
com_android_internal_content_NativeLibraryHelper_close(JNIEnv *env, jclass, jlong apkHandle)
{
@@ -633,29 +797,23 @@ com_android_internal_content_NativeLibraryHelper_close(JNIEnv *env, jclass, jlon
}

static const JNINativeMethod gMethods[] = {
    {"nativeOpenApk",
            "(Ljava/lang/String;)J",
        {"nativeOpenApk", "(Ljava/lang/String;)J",
         (void*)com_android_internal_content_NativeLibraryHelper_openApk},
    {"nativeOpenApkFd",
            "(Ljava/io/FileDescriptor;Ljava/lang/String;)J",
        {"nativeOpenApkFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;)J",
         (void*)com_android_internal_content_NativeLibraryHelper_openApkFd},
    {"nativeClose",
            "(J)V",
            (void *)com_android_internal_content_NativeLibraryHelper_close},
    {"nativeCopyNativeBinaries",
            "(JLjava/lang/String;Ljava/lang/String;ZZ)I",
        {"nativeClose", "(J)V", (void*)com_android_internal_content_NativeLibraryHelper_close},
        {"nativeCopyNativeBinaries", "(JLjava/lang/String;Ljava/lang/String;ZZ)I",
         (void*)com_android_internal_content_NativeLibraryHelper_copyNativeBinaries},
    {"nativeSumNativeBinaries",
            "(JLjava/lang/String;Z)J",
        {"nativeSumNativeBinaries", "(JLjava/lang/String;Z)J",
         (void*)com_android_internal_content_NativeLibraryHelper_sumNativeBinaries},
    {"nativeFindSupportedAbi",
            "(J[Ljava/lang/String;Z)I",
        {"nativeFindSupportedAbi", "(J[Ljava/lang/String;Z)I",
         (void*)com_android_internal_content_NativeLibraryHelper_findSupportedAbi},
        {"hasRenderscriptBitcode", "(J)I",
         (void*)com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode},
        {"nativeCheckAlignment", "(JLjava/lang/String;Ljava/lang/String;ZZ)I",
         (void*)com_android_internal_content_NativeLibraryHelper_checkApkAlignment},
};


int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env)
{
    return RegisterMethodsOrDie(env,
+18 −2
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.server.pm;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.ApplicationInfo;
import android.util.ArraySet;
import android.util.Pair;

@@ -28,8 +29,6 @@ import com.android.server.pm.pkg.PackageStateInternal;

import java.io.File;



// TODO: Move to .parsing sub-package
@VisibleForTesting
public interface PackageAbiHelper {
@@ -78,6 +77,23 @@ public interface PackageAbiHelper {
    String getAdjustedAbiForSharedUser(ArraySet<? extends PackageStateInternal> packagesForUser,
            AndroidPackage scannedPackage);

    /**
     * Checks alignment of APK and native libraries for 16KB device
     *
     * @param pkg AndroidPackage for which alignment check is being done
     * @param libraryRoot directory for libraries
     * @param nativeLibraryRootRequiresIsa use isa
     * @param cpuAbiOverride ABI override mentioned in package
     * @return {ApplicationInfo.PageSizeAppCompat} if successful or error code
     *     which suggests undefined mode
     */
    @ApplicationInfo.PageSizeAppCompatFlags
    int checkPackageAlignment(
            AndroidPackage pkg,
            String libraryRoot,
            boolean nativeLibraryRootRequiresIsa,
            String cpuAbiOverride);

    /**
     * The native library paths and related properties that should be set on a
     * {@link ParsedPackage}.
+16 −0
Original line number Diff line number Diff line
@@ -625,4 +625,20 @@ final class PackageAbiHelperImpl implements PackageAbiHelper {
        }
        return adjustedAbi;
    }

    @Override
    public int checkPackageAlignment(
            AndroidPackage pkg,
            String libraryRoot,
            boolean nativeLibraryRootRequiresIsa,
            String abiOverride) {
        NativeLibraryHelper.Handle handle = null;
        try {
            handle = AndroidPackageUtils.createNativeLibraryHandle(pkg);
            return NativeLibraryHelper.checkAlignmentForCompatMode(
                            handle, libraryRoot, nativeLibraryRootRequiresIsa, abiOverride);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
+32 −0
Original line number Diff line number Diff line
@@ -52,6 +52,7 @@ import static com.android.server.pm.PackageManagerServiceUtils.getLastModifiedTi
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.ApplicationInfo;
import android.content.pm.Flags;
import android.content.pm.PackageManager;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.SigningDetails;
@@ -63,6 +64,8 @@ import android.os.Process;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.system.Os;
import android.system.OsConstants;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -105,6 +108,9 @@ import java.util.UUID;
 * Helper class that handles package scanning logic
 */
final class ScanPackageUtils {

    public static final int PAGE_SIZE_16KB = 16384;

    /**
     * Just scans the package without any side effects.
     *
@@ -418,6 +424,32 @@ final class ScanPackageUtils {
                    + " abiOverride=" + pkgSetting.getCpuAbiOverride());
        }

        boolean is16KbDevice = Os.sysconf(OsConstants._SC_PAGESIZE) == PAGE_SIZE_16KB;
        if (Flags.appCompatOption16kb() && is16KbDevice) {
            // Alignment checks are used decide whether this app should run in compat mode when
            // nothing was specified in manifest. Manifest should always take precedence over
            // something decided by platform.
            if (parsedPackage.getPageSizeAppCompatFlags()
                    > ApplicationInfo.PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED) {
                pkgSetting.setPageSizeAppCompatFlags(parsedPackage.getPageSizeAppCompatFlags());
            } else {
                // 16 KB is only support for 64 bit ABIs and for apps which are being installed
                // Check alignment. System, Apex and Platform packages should be page-agnostic now
                if ((Build.SUPPORTED_64_BIT_ABIS.length > 0)
                        && !isSystemApp
                        && !isApex
                        && !isPlatformPackage) {
                    int mode =
                            packageAbiHelper.checkPackageAlignment(
                                    parsedPackage,
                                    pkgSetting.getLegacyNativeLibraryPath(),
                                    parsedPackage.isNativeLibraryRootRequiresIsa(),
                                    pkgSetting.getCpuAbiOverride());
                    pkgSetting.setPageSizeAppCompatFlags(mode);
                }
            }
        }

        if ((scanFlags & SCAN_BOOTING) == 0 && oldSharedUserSetting != null) {
            // We don't do this here during boot because we can do it all
            // at once after scanning all existing packages.