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

Commit b402f570 authored by Pawan Wagh's avatar Pawan Wagh Committed by Gerrit Code Review
Browse files

Merge changes from topic "punch_holes_apk" into main

* changes:
  Punch extracted ELF64 files
  Punch holes in extra field in local headers inside apk
  Add Extra field info in ZipFileRO
parents 23311d45 2724e0f6
Loading
Loading
Loading
Loading
+114 −3
Original line number Diff line number Diff line
@@ -42,7 +42,11 @@ namespace android {
bool punchHoles(const char *filePath, const uint64_t offset,
                const std::vector<Elf64_Phdr> &programHeaders) {
    struct stat64 beforePunch;
    lstat64(filePath, &beforePunch);
    if (int result = lstat64(filePath, &beforePunch); result != 0) {
        ALOGE("lstat64 failed for filePath %s, error:%d", filePath, errno);
        return false;
    }

    uint64_t blockSize = beforePunch.st_blksize;
    IF_ALOGD() {
        ALOGD("Total number of LOAD segments %zu", programHeaders.size());
@@ -152,7 +156,10 @@ bool punchHoles(const char *filePath, const uint64_t offset,

    IF_ALOGD() {
        struct stat64 afterPunch;
        lstat64(filePath, &afterPunch);
        if (int result = lstat64(filePath, &afterPunch); result != 0) {
            ALOGD("lstat64 failed for filePath %s, error:%d", filePath, errno);
            return false;
        }
        ALOGD("Size after punching holes st_blocks: %" PRIu64 ", st_blksize: %ld, st_size: %" PRIu64
              "",
              afterPunch.st_blocks, afterPunch.st_blksize,
@@ -177,7 +184,7 @@ bool punchHolesInElf64(const char *filePath, const uint64_t offset) {

    // only consider elf64 for punching holes
    if (ehdr.e_ident[EI_CLASS] != ELFCLASS64) {
        ALOGE("Provided file is not ELF64");
        ALOGW("Provided file is not ELF64");
        return false;
    }

@@ -215,4 +222,108 @@ bool punchHolesInElf64(const char *filePath, const uint64_t offset) {
    return punchHoles(filePath, offset, programHeaders);
}

bool punchHolesInZip(const char *filePath, uint64_t offset, uint16_t extraFieldLen) {
    android::base::unique_fd fd(open(filePath, O_RDWR | O_CLOEXEC));
    if (!fd.ok()) {
        ALOGE("Can't open file to punch %s", filePath);
        return false;
    }

    struct stat64 beforePunch;
    if (int result = lstat64(filePath, &beforePunch); result != 0) {
        ALOGE("lstat64 failed for filePath %s, error:%d", filePath, errno);
        return false;
    }

    uint64_t blockSize = beforePunch.st_blksize;
    IF_ALOGD() {
        ALOGD("Extra field length: %hu,  Size before punching holes st_blocks: %" PRIu64
              ", st_blksize: %ld, st_size: %" PRIu64 "",
              extraFieldLen, beforePunch.st_blocks, beforePunch.st_blksize,
              static_cast<uint64_t>(beforePunch.st_size));
    }

    if (extraFieldLen < blockSize) {
        ALOGD("Skipping punching apk as extra field length is less than block size");
        return false;
    }

    // content is preceded by extra field. Zip offset is offset of exact content.
    // move back by extraFieldLen so that scan can be started at start of extra field.
    uint64_t extraFieldStart;
    if (__builtin_sub_overflow(offset, extraFieldLen, &extraFieldStart)) {
        ALOGE("Overflow occurred when calculating start of extra field");
        return false;
    }

    constexpr uint64_t kMaxSize = 64 * 1024;
    // Use malloc to gracefully handle any oom conditions
    std::unique_ptr<uint8_t, decltype(&free)> buffer(static_cast<uint8_t *>(malloc(kMaxSize)),
                                                     &free);
    if (buffer == nullptr) {
        ALOGE("Failed to allocate read buffer");
        return false;
    }

    // Read the entire extra fields at once and punch file according to zero stretches.
    if (!ReadFullyAtOffset(fd, buffer.get(), extraFieldLen, extraFieldStart)) {
        ALOGE("Failed to read extra field content");
        return false;
    }

    IF_ALOGD() {
        ALOGD("Extra field length: %hu content near offset: %s", extraFieldLen,
              HexString(buffer.get(), extraFieldLen).c_str());
    }

    uint64_t currentSize = 0;
    while (currentSize < extraFieldLen) {
        uint64_t end = currentSize;
        // find zero ranges
        while (end < extraFieldLen && *(buffer.get() + end) == 0) {
            ++end;
        }

        uint64_t punchLen;
        if (__builtin_sub_overflow(end, currentSize, &punchLen)) {
            ALOGW("Overflow occurred when calculating punching length");
            return false;
        }

        // Don't punch for every stretch of zero which is found
        if (punchLen > blockSize) {
            uint64_t punchOffset;
            if (__builtin_add_overflow(extraFieldStart, currentSize, &punchOffset)) {
                ALOGW("Overflow occurred when calculating punch start offset");
                return false;
            }

            ALOGD("Punching hole in apk start: %" PRIu64 " len:%" PRIu64 "", punchOffset, punchLen);

            // Punch hole for this entire stretch.
            int result = fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, punchOffset,
                                   punchLen);
            if (result < 0) {
                ALOGE("fallocate failed to punch hole inside apk, error:%d", errno);
                return false;
            }
        }
        currentSize = end;
        ++currentSize;
    }

    IF_ALOGD() {
        struct stat64 afterPunch;
        if (int result = lstat64(filePath, &afterPunch); result != 0) {
            ALOGD("lstat64 failed for filePath %s, error:%d", filePath, errno);
            return false;
        }
        ALOGD("punchHolesInApk:: Size after punching holes st_blocks: %" PRIu64
              ", st_blksize: %ld, st_size: %" PRIu64 "",
              afterPunch.st_blocks, afterPunch.st_blksize,
              static_cast<uint64_t>(afterPunch.st_size));
    }
    return true;
}

}; // namespace android
+7 −0
Original line number Diff line number Diff line
@@ -28,4 +28,11 @@ namespace android {
 */
bool punchHolesInElf64(const char* filePath, uint64_t offset);

/*
 * This function punches holes in zero segments of Apk file which are introduced during the
 * alignment. Alignment tools add padding inside of extra field in local file header. punch holes in
 * extra field for zero stretches till the actual file content.
 */
bool punchHolesInZip(const char* filePath, uint64_t offset, uint16_t extraFieldLen);

} // namespace android
 No newline at end of file
+29 −2
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/statfs.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
@@ -145,8 +146,9 @@ copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntr

    uint16_t method;
    off64_t offset;

    if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, nullptr, &offset, &when, &crc)) {
    uint16_t extraFieldLength;
    if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, nullptr, &offset, &when, &crc,
                               &extraFieldLength)) {
        ALOGE("Couldn't read zip entry info\n");
        return INSTALL_FAILED_INVALID_APK;
    }
@@ -177,6 +179,12 @@ copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntr
                  "%" PRIu64 "",
                  fileName, zipFile->getZipFileName(), offset);
        }

        // if extra field for this zip file is present with some length, possibility is that it is
        // padding added for zip alignment. Punch holes there too.
        if (!punchHolesInZip(zipFile->getZipFileName(), offset, extraFieldLength)) {
            ALOGW("Failed to punch apk : %s at extra field", zipFile->getZipFileName());
        }
#endif // ENABLE_PUNCH_HOLES

        return INSTALL_SUCCEEDED;
@@ -279,6 +287,25 @@ copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntr
        return INSTALL_FAILED_CONTAINER_ERROR;
    }

#ifdef ENABLE_PUNCH_HOLES
    // punch extracted elf files as well. This will fail where compression is on (like f2fs) but it
    // will be useful for ext4 based systems
    struct statfs64 fsInfo;
    int result = statfs64(localFileName, &fsInfo);
    if (result < 0) {
        ALOGW("Failed to stat file :%s", localFileName);
    }

    if (result == 0 && fsInfo.f_type == EXT4_SUPER_MAGIC) {
        ALOGD("Punching extracted elf file %s on fs:%" PRIu64 "", fileName,
              static_cast<uint64_t>(fsInfo.f_type));
        if (!punchHolesInElf64(localFileName, 0)) {
            ALOGW("Failed to punch extracted elf file :%s from apk : %s", fileName,
                  zipFile->getZipFileName());
        }
    }
#endif // ENABLE_PUNCH_HOLES

    ALOGV("Successfully moved %s to %s\n", localTmpFileName, localFileName);

    return INSTALL_SUCCEEDED;
+18 −7
Original line number Diff line number Diff line
@@ -121,28 +121,39 @@ ZipEntryRO ZipFileRO::findEntryByName(const char* entryName) const
bool ZipFileRO::getEntryInfo(ZipEntryRO entry, uint16_t* pMethod,
 uint32_t* pUncompLen, uint32_t* pCompLen, off64_t* pOffset,
 uint32_t* pModWhen, uint32_t* pCrc32) const
{
     return getEntryInfo(entry, pMethod, pUncompLen, pCompLen, pOffset, pModWhen,
      pCrc32, nullptr);
}

bool ZipFileRO::getEntryInfo(ZipEntryRO entry, uint16_t* pMethod,
    uint32_t* pUncompLen, uint32_t* pCompLen, off64_t* pOffset,
    uint32_t* pModWhen, uint32_t* pCrc32, uint16_t* pExtraFieldSize) const
{
    const _ZipEntryRO* zipEntry = reinterpret_cast<_ZipEntryRO*>(entry);
    const ZipEntry& ze = zipEntry->entry;

    if (pMethod != NULL) {
    if (pMethod != nullptr) {
        *pMethod = ze.method;
    }
    if (pUncompLen != NULL) {
    if (pUncompLen != nullptr) {
        *pUncompLen = ze.uncompressed_length;
    }
    if (pCompLen != NULL) {
    if (pCompLen != nullptr) {
        *pCompLen = ze.compressed_length;
    }
    if (pOffset != NULL) {
    if (pOffset != nullptr) {
        *pOffset = ze.offset;
    }
    if (pModWhen != NULL) {
    if (pModWhen != nullptr) {
        *pModWhen = ze.mod_time;
    }
    if (pCrc32 != NULL) {
    if (pCrc32 != nullptr) {
        *pCrc32 = ze.crc32;
    }
    if (pExtraFieldSize != nullptr) {
        *pExtraFieldSize = ze.extra_field_size;
    }

    return true;
}
+4 −0
Original line number Diff line number Diff line
@@ -151,6 +151,10 @@ public:
        uint32_t* pCompLen, off64_t* pOffset, uint32_t* pModWhen,
        uint32_t* pCrc32) const;

    bool getEntryInfo(ZipEntryRO entry, uint16_t* pMethod,
        uint32_t* pUncompLen, uint32_t* pCompLen, off64_t* pOffset,
        uint32_t* pModWhen, uint32_t* pCrc32, uint16_t* pExtraFieldSize) const;

    /*
     * Create a new FileMap object that maps a subset of the archive. For
     * an uncompressed entry this effectively provides a pointer to the