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

Commit 4b1184df authored by Pawan Wagh's avatar Pawan Wagh
Browse files

Punch holes in extra field in local headers inside apk

Apk files are essentially zip files. Zip files contain
central directory which has local file header offsets.
Local file header contains 'extra' field and its length info.

This info can be seen with zipinfo -v <apk_name>

Example zip entry:

Central directory entry #3:
---------------------------

  lib/x86_64/libpunchtest.so

  offset of local header from start of archive:   202
                                                  (00000000000000CAh) bytes
  file system or operating system of origin:      Unix
  version of encoding software:                   1.0
  minimum file system compatibility required:     MS-DOS, OS/2 or NT FAT
  minimum software version required to extract:   1.0
  compression method:                             none (stored)
  file security status:                           not encrypted
  extended local header:                          no
  file last modified on (DOS date/time):          2009 Jan 1 00:00:00
  32-bit CRC value (hex):                         1f180260
  compressed size:                                133376 bytes
  uncompressed size:                              133376 bytes
  length of filename:                             26 characters
  length of extra field:                          16126 bytes
  length of file comment:                         0 characters

Note the length of extra field which is 16126.

Extra field is followed by actual content of the zip entry.
When aligning Apks, Alignment tool add padding inside extra field so that
uncompressed shared libs are page aligned. ZipFileRO already provides
offset to content of a zip entry. For uncompressed libs, padding can be found
by going to the start of the extra field and looking for zero ranges.

This ranges are punched with help of fallocate(2)
Size before punching holes st_blocks: 1832, st_blksize: 4096, st_size: 1058429
Size after punching holes st_blocks: 1808, st_blksize: 4096, st_size: 1058429

Test: acloud delete --all && m && acloud create --local-instance --local-image && adb logcat -c && m FileSystemUtilsTests && atest -c FileSystemUtilsTests
Bug: 301631861

Change-Id: I35ac4a061828d84401d6f2d38ceb5b1a6907c00a
parent cb47bdac
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
+9 −2
Original line number Diff line number Diff line
@@ -145,8 +145,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 +178,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;