Loading core/jni/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -123,6 +123,7 @@ cc_library_shared_for_libandroid_runtime { srcs: [ "AndroidRuntime.cpp", "com_android_internal_content_F2fsUtils.cpp", "com_android_internal_content_FileSystemUtils.cpp", "com_android_internal_content_NativeLibraryHelper.cpp", "com_google_android_gles_jni_EGLImpl.cpp", "com_google_android_gles_jni_GLImpl.cpp", // TODO: .arm Loading core/jni/com_android_internal_content_FileSystemUtils.cpp 0 → 100644 +218 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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. */ #define LOG_TAG "FileSystemUtils" #include "com_android_internal_content_FileSystemUtils.h" #include <android-base/file.h> #include <android-base/hex.h> #include <android-base/unique_fd.h> #include <elf.h> #include <errno.h> #include <fcntl.h> #include <inttypes.h> #include <linux/fs.h> #include <sys/ioctl.h> #include <sys/stat.h> #include <sys/types.h> #include <utils/Log.h> #include <array> #include <fstream> #include <vector> using android::base::HexString; using android::base::ReadFullyAtOffset; namespace android { bool punchHoles(const char *filePath, const uint64_t offset, const std::vector<Elf64_Phdr> &programHeaders) { struct stat64 beforePunch; lstat64(filePath, &beforePunch); uint64_t blockSize = beforePunch.st_blksize; IF_ALOGD() { ALOGD("Total number of LOAD segments %zu", programHeaders.size()); ALOGD("Size before punching holes st_blocks: %" PRIu64 ", st_blksize: %ld, st_size: %" PRIu64 "", beforePunch.st_blocks, beforePunch.st_blksize, static_cast<uint64_t>(beforePunch.st_size)); } 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; } // read in chunks of 64KB constexpr uint64_t kChunkSize = 64 * 1024; // malloc is used to gracefully handle oom which might occur during the allocation of buffer. // allocating using new or vector here results in oom/exception on failure where as malloc will // return nullptr. std::unique_ptr<uint8_t, decltype(&free)> buffer(static_cast<uint8_t *>(malloc(kChunkSize)), &free); if (buffer == nullptr) { ALOGE("Failed to allocate read buffer"); return false; } for (size_t index = 0; programHeaders.size() >= 2 && index < programHeaders.size() - 1; index++) { // find LOAD segments from program headers, calculate padding and punch holes uint64_t punchOffset; if (__builtin_add_overflow(programHeaders[index].p_offset, programHeaders[index].p_filesz, &punchOffset)) { ALOGE("Overflow occurred when adding offset and filesize"); return false; } uint64_t punchLen; if (__builtin_sub_overflow(programHeaders[index + 1].p_offset, punchOffset, &punchLen)) { ALOGE("Overflow occurred when calculating length"); return false; } if (punchLen < blockSize) { continue; } uint64_t punchStartOffset; if (__builtin_add_overflow(offset, punchOffset, &punchStartOffset)) { ALOGE("Overflow occurred when calculating length"); return false; } uint64_t position = punchStartOffset; uint64_t endPosition; if (__builtin_add_overflow(position, punchLen, &endPosition)) { ALOGE("Overflow occurred when calculating length"); return false; } // Read content in kChunkSize and verify it is zero while (position <= endPosition) { uint64_t uncheckedChunkEnd; if (__builtin_add_overflow(position, kChunkSize, &uncheckedChunkEnd)) { ALOGE("Overflow occurred when calculating uncheckedChunkEnd"); return false; } uint64_t readLength; if (__builtin_sub_overflow(std::min(uncheckedChunkEnd, endPosition), position, &readLength)) { ALOGE("Overflow occurred when calculating readLength"); return false; } if (!ReadFullyAtOffset(fd, buffer.get(), readLength, position)) { ALOGE("Failed to read content to punch holes"); return false; } IF_ALOGD() { ALOGD("Punching holes for length:%" PRIu64 " content which should be zero: %s", readLength, HexString(buffer.get(), readLength).c_str()); } bool isZero = std::all_of(buffer.get(), buffer.get() + readLength, [](uint8_t i) constexpr { return i == 0; }); if (!isZero) { ALOGE("Found non zero content while trying to punch hole. Skipping operation"); return false; } position = uncheckedChunkEnd; } // if we have a uncompressed file which is being opened from APK, use the offset to // punch native lib inside Apk. int result = fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, punchStartOffset, punchLen); if (result < 0) { ALOGE("fallocate failed to punch hole, error:%d", errno); return false; } } IF_ALOGD() { struct stat64 afterPunch; lstat64(filePath, &afterPunch); ALOGD("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; } bool punchHolesInElf64(const char *filePath, const uint64_t offset) { // Open Elf file Elf64_Ehdr ehdr; std::ifstream inputStream(filePath, std::ifstream::in); // If this is a zip file, set the offset so that we can read elf file directly inputStream.seekg(offset); // read executable headers inputStream.read((char *)&ehdr, sizeof(ehdr)); if (!inputStream.good()) { return false; } // only consider elf64 for punching holes if (ehdr.e_ident[EI_CLASS] != ELFCLASS64) { ALOGE("Provided file is not ELF64"); return false; } // read the program headers from elf file uint64_t programHeaderOffset = ehdr.e_phoff; uint16_t programHeaderNum = ehdr.e_phnum; IF_ALOGD() { ALOGD("Punching holes in file: %s programHeaderOffset: %" PRIu64 " programHeaderNum: %hu", filePath, programHeaderOffset, programHeaderNum); } // if this is a zip file, also consider elf offset inside a file uint64_t phOffset; if (__builtin_add_overflow(offset, programHeaderOffset, &phOffset)) { ALOGE("Overflow occurred when calculating phOffset"); return false; } inputStream.seekg(phOffset); std::vector<Elf64_Phdr> programHeaders; for (int headerIndex = 0; headerIndex < programHeaderNum; headerIndex++) { Elf64_Phdr header; inputStream.read((char *)&header, sizeof(header)); if (!inputStream.good()) { return false; } if (header.p_type != PT_LOAD) { continue; } programHeaders.push_back(header); } return punchHoles(filePath, offset, programHeaders); } }; // namespace android core/jni/com_android_internal_content_FileSystemUtils.h 0 → 100644 +31 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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. */ #pragma once #include <sys/types.h> namespace android { /* * This function deallocates space used by zero padding at the end of LOAD segments in given * uncompressed ELF file. Read ELF headers and find out the offset and sizes of LOAD segments. * [fallocate(2)](http://man7.org/linux/man-pages/man2/fallocate.2.html) is used to deallocate the * zero ranges at the end of LOAD segments. If ELF file is present inside of ApK/Zip file, offset to * the start of the ELF file should be provided. */ bool punchHolesInElf64(const char* filePath, uint64_t offset); } // namespace android No newline at end of file core/tests/FileSystemUtilsTest/Android.bp 0 → 100644 +78 −0 Original line number Diff line number Diff line // Copyright (C) 2024 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 { default_applicable_licenses: ["frameworks_base_license"], default_team: "trendy_team_android_kernel", } cc_library { name: "libpunchtest", stl: "none", host_supported: true, srcs: ["jni/android_test_jni_source.cpp"], header_libs: ["jni_headers"], } android_test_helper_app { name: "embedded_native_libs_test_app", srcs: ["apk_embedded_native_libs/src/**/*.java"], manifest: "apk_embedded_native_libs/embedded_native_libs_test_app.xml", compile_multilib: "64", jni_libs: [ "libpunchtest", ], static_libs: [ "androidx.test.rules", "platform-test-annotations", ], use_embedded_native_libs: true, } android_test_helper_app { name: "extract_native_libs_test_app", srcs: ["apk_extract_native_libs/src/**/*.java"], manifest: "apk_extract_native_libs/extract_native_libs_test_app.xml", compile_multilib: "64", jni_libs: [ "libpunchtest", ], static_libs: [ "androidx.test.rules", "platform-test-annotations", ], use_embedded_native_libs: false, } java_test_host { name: "FileSystemUtilsTests", // Include all test java files srcs: ["src/**/*.java"], static_libs: [ "junit", "platform-test-annotations", "truth", ], libs: [ "tradefed", "compatibility-host-util", "compatibility-tradefed", ], data: [ ":embedded_native_libs_test_app", ":extract_native_libs_test_app", ], test_suites: ["general-tests"], test_config: "AndroidTest.xml", } core/tests/FileSystemUtilsTest/AndroidManifest.xml 0 → 100644 +27 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- ~ Copyright (C) 2024 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. --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="internalOnly" package="com.android.internal.content.fstests"> <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.internal.content.fstests" android:label="Frameworks FileSystemUtils Tests" /> </manifest> Loading
core/jni/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -123,6 +123,7 @@ cc_library_shared_for_libandroid_runtime { srcs: [ "AndroidRuntime.cpp", "com_android_internal_content_F2fsUtils.cpp", "com_android_internal_content_FileSystemUtils.cpp", "com_android_internal_content_NativeLibraryHelper.cpp", "com_google_android_gles_jni_EGLImpl.cpp", "com_google_android_gles_jni_GLImpl.cpp", // TODO: .arm Loading
core/jni/com_android_internal_content_FileSystemUtils.cpp 0 → 100644 +218 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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. */ #define LOG_TAG "FileSystemUtils" #include "com_android_internal_content_FileSystemUtils.h" #include <android-base/file.h> #include <android-base/hex.h> #include <android-base/unique_fd.h> #include <elf.h> #include <errno.h> #include <fcntl.h> #include <inttypes.h> #include <linux/fs.h> #include <sys/ioctl.h> #include <sys/stat.h> #include <sys/types.h> #include <utils/Log.h> #include <array> #include <fstream> #include <vector> using android::base::HexString; using android::base::ReadFullyAtOffset; namespace android { bool punchHoles(const char *filePath, const uint64_t offset, const std::vector<Elf64_Phdr> &programHeaders) { struct stat64 beforePunch; lstat64(filePath, &beforePunch); uint64_t blockSize = beforePunch.st_blksize; IF_ALOGD() { ALOGD("Total number of LOAD segments %zu", programHeaders.size()); ALOGD("Size before punching holes st_blocks: %" PRIu64 ", st_blksize: %ld, st_size: %" PRIu64 "", beforePunch.st_blocks, beforePunch.st_blksize, static_cast<uint64_t>(beforePunch.st_size)); } 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; } // read in chunks of 64KB constexpr uint64_t kChunkSize = 64 * 1024; // malloc is used to gracefully handle oom which might occur during the allocation of buffer. // allocating using new or vector here results in oom/exception on failure where as malloc will // return nullptr. std::unique_ptr<uint8_t, decltype(&free)> buffer(static_cast<uint8_t *>(malloc(kChunkSize)), &free); if (buffer == nullptr) { ALOGE("Failed to allocate read buffer"); return false; } for (size_t index = 0; programHeaders.size() >= 2 && index < programHeaders.size() - 1; index++) { // find LOAD segments from program headers, calculate padding and punch holes uint64_t punchOffset; if (__builtin_add_overflow(programHeaders[index].p_offset, programHeaders[index].p_filesz, &punchOffset)) { ALOGE("Overflow occurred when adding offset and filesize"); return false; } uint64_t punchLen; if (__builtin_sub_overflow(programHeaders[index + 1].p_offset, punchOffset, &punchLen)) { ALOGE("Overflow occurred when calculating length"); return false; } if (punchLen < blockSize) { continue; } uint64_t punchStartOffset; if (__builtin_add_overflow(offset, punchOffset, &punchStartOffset)) { ALOGE("Overflow occurred when calculating length"); return false; } uint64_t position = punchStartOffset; uint64_t endPosition; if (__builtin_add_overflow(position, punchLen, &endPosition)) { ALOGE("Overflow occurred when calculating length"); return false; } // Read content in kChunkSize and verify it is zero while (position <= endPosition) { uint64_t uncheckedChunkEnd; if (__builtin_add_overflow(position, kChunkSize, &uncheckedChunkEnd)) { ALOGE("Overflow occurred when calculating uncheckedChunkEnd"); return false; } uint64_t readLength; if (__builtin_sub_overflow(std::min(uncheckedChunkEnd, endPosition), position, &readLength)) { ALOGE("Overflow occurred when calculating readLength"); return false; } if (!ReadFullyAtOffset(fd, buffer.get(), readLength, position)) { ALOGE("Failed to read content to punch holes"); return false; } IF_ALOGD() { ALOGD("Punching holes for length:%" PRIu64 " content which should be zero: %s", readLength, HexString(buffer.get(), readLength).c_str()); } bool isZero = std::all_of(buffer.get(), buffer.get() + readLength, [](uint8_t i) constexpr { return i == 0; }); if (!isZero) { ALOGE("Found non zero content while trying to punch hole. Skipping operation"); return false; } position = uncheckedChunkEnd; } // if we have a uncompressed file which is being opened from APK, use the offset to // punch native lib inside Apk. int result = fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, punchStartOffset, punchLen); if (result < 0) { ALOGE("fallocate failed to punch hole, error:%d", errno); return false; } } IF_ALOGD() { struct stat64 afterPunch; lstat64(filePath, &afterPunch); ALOGD("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; } bool punchHolesInElf64(const char *filePath, const uint64_t offset) { // Open Elf file Elf64_Ehdr ehdr; std::ifstream inputStream(filePath, std::ifstream::in); // If this is a zip file, set the offset so that we can read elf file directly inputStream.seekg(offset); // read executable headers inputStream.read((char *)&ehdr, sizeof(ehdr)); if (!inputStream.good()) { return false; } // only consider elf64 for punching holes if (ehdr.e_ident[EI_CLASS] != ELFCLASS64) { ALOGE("Provided file is not ELF64"); return false; } // read the program headers from elf file uint64_t programHeaderOffset = ehdr.e_phoff; uint16_t programHeaderNum = ehdr.e_phnum; IF_ALOGD() { ALOGD("Punching holes in file: %s programHeaderOffset: %" PRIu64 " programHeaderNum: %hu", filePath, programHeaderOffset, programHeaderNum); } // if this is a zip file, also consider elf offset inside a file uint64_t phOffset; if (__builtin_add_overflow(offset, programHeaderOffset, &phOffset)) { ALOGE("Overflow occurred when calculating phOffset"); return false; } inputStream.seekg(phOffset); std::vector<Elf64_Phdr> programHeaders; for (int headerIndex = 0; headerIndex < programHeaderNum; headerIndex++) { Elf64_Phdr header; inputStream.read((char *)&header, sizeof(header)); if (!inputStream.good()) { return false; } if (header.p_type != PT_LOAD) { continue; } programHeaders.push_back(header); } return punchHoles(filePath, offset, programHeaders); } }; // namespace android
core/jni/com_android_internal_content_FileSystemUtils.h 0 → 100644 +31 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 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. */ #pragma once #include <sys/types.h> namespace android { /* * This function deallocates space used by zero padding at the end of LOAD segments in given * uncompressed ELF file. Read ELF headers and find out the offset and sizes of LOAD segments. * [fallocate(2)](http://man7.org/linux/man-pages/man2/fallocate.2.html) is used to deallocate the * zero ranges at the end of LOAD segments. If ELF file is present inside of ApK/Zip file, offset to * the start of the ELF file should be provided. */ bool punchHolesInElf64(const char* filePath, uint64_t offset); } // namespace android No newline at end of file
core/tests/FileSystemUtilsTest/Android.bp 0 → 100644 +78 −0 Original line number Diff line number Diff line // Copyright (C) 2024 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 { default_applicable_licenses: ["frameworks_base_license"], default_team: "trendy_team_android_kernel", } cc_library { name: "libpunchtest", stl: "none", host_supported: true, srcs: ["jni/android_test_jni_source.cpp"], header_libs: ["jni_headers"], } android_test_helper_app { name: "embedded_native_libs_test_app", srcs: ["apk_embedded_native_libs/src/**/*.java"], manifest: "apk_embedded_native_libs/embedded_native_libs_test_app.xml", compile_multilib: "64", jni_libs: [ "libpunchtest", ], static_libs: [ "androidx.test.rules", "platform-test-annotations", ], use_embedded_native_libs: true, } android_test_helper_app { name: "extract_native_libs_test_app", srcs: ["apk_extract_native_libs/src/**/*.java"], manifest: "apk_extract_native_libs/extract_native_libs_test_app.xml", compile_multilib: "64", jni_libs: [ "libpunchtest", ], static_libs: [ "androidx.test.rules", "platform-test-annotations", ], use_embedded_native_libs: false, } java_test_host { name: "FileSystemUtilsTests", // Include all test java files srcs: ["src/**/*.java"], static_libs: [ "junit", "platform-test-annotations", "truth", ], libs: [ "tradefed", "compatibility-host-util", "compatibility-tradefed", ], data: [ ":embedded_native_libs_test_app", ":extract_native_libs_test_app", ], test_suites: ["general-tests"], test_config: "AndroidTest.xml", }
core/tests/FileSystemUtilsTest/AndroidManifest.xml 0 → 100644 +27 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- ~ Copyright (C) 2024 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. --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="internalOnly" package="com.android.internal.content.fstests"> <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" android:targetPackage="com.android.internal.content.fstests" android:label="Frameworks FileSystemUtils Tests" /> </manifest>