xref: /aosp_15_r20/frameworks/base/core/jni/com_android_internal_content_FileSystemUtils.cpp (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #define LOG_TAG "FileSystemUtils"
18 
19 #include "com_android_internal_content_FileSystemUtils.h"
20 
21 #include <android-base/file.h>
22 #include <android-base/hex.h>
23 #include <android-base/unique_fd.h>
24 #include <bionic/macros.h>
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <inttypes.h>
28 #include <linux/fs.h>
29 #include <sys/ioctl.h>
30 #include <sys/stat.h>
31 #include <sys/types.h>
32 #include <utils/Log.h>
33 
34 #include <array>
35 #include <fstream>
36 #include <vector>
37 
38 using android::base::borrowed_fd;
39 using android::base::HexString;
40 using android::base::ReadFullyAtOffset;
41 
42 namespace android {
43 
punchWithBlockAlignment(borrowed_fd fd,uint64_t start,uint64_t length,uint64_t blockSize)44 bool punchWithBlockAlignment(borrowed_fd fd, uint64_t start, uint64_t length, uint64_t blockSize) {
45     uint64_t end;
46     if (__builtin_add_overflow(start, length, &end)) {
47         ALOGE("Overflow occurred when calculating end");
48         return false;
49     }
50 
51     start = align_up(start, blockSize);
52     end = align_down(end, blockSize);
53 
54     uint64_t alignedLength;
55     if (__builtin_sub_overflow(end, start, &alignedLength)) {
56         ALOGE("Overflow occurred when calculating length");
57         return false;
58     }
59 
60     if (alignedLength < blockSize) {
61         ALOGW("Skipping punching hole as aligned length is less than block size");
62         return false;
63     }
64 
65     ALOGD("Punching hole in file - start: %" PRIu64 " len:%" PRIu64 "", start, alignedLength);
66 
67     int result =
68             fallocate(fd.get(), FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, start, alignedLength);
69     if (result < 0) {
70         ALOGE("fallocate failed to punch hole, error:%d", errno);
71         return false;
72     }
73 
74     return true;
75 }
76 
punchHoles(const char * filePath,const uint64_t offset,const std::vector<Elf64_Phdr> & programHeaders)77 bool punchHoles(const char *filePath, const uint64_t offset,
78                 const std::vector<Elf64_Phdr> &programHeaders) {
79     struct stat64 beforePunch;
80     if (int result = lstat64(filePath, &beforePunch); result != 0) {
81         ALOGE("lstat64 failed for filePath %s, error:%d", filePath, errno);
82         return false;
83     }
84 
85     uint64_t blockSize = beforePunch.st_blksize;
86     IF_ALOGD() {
87         ALOGD("Total number of LOAD segments %zu", programHeaders.size());
88 
89         ALOGD("Size before punching holes st_blocks: %" PRIu64 ", st_blksize: %" PRIu64
90               ", st_size: %" PRIu64 "",
91               static_cast<uint64_t>(beforePunch.st_blocks),
92               static_cast<uint64_t>(beforePunch.st_blksize),
93               static_cast<uint64_t>(beforePunch.st_size));
94     }
95 
96     android::base::unique_fd fd(open(filePath, O_RDWR | O_CLOEXEC));
97     if (!fd.ok()) {
98         ALOGE("Can't open file to punch %s", filePath);
99         return false;
100     }
101 
102     // read in chunks of 64KB
103     constexpr uint64_t kChunkSize = 64 * 1024;
104 
105     // malloc is used to gracefully handle oom which might occur during the allocation of buffer.
106     // allocating using new or vector here results in oom/exception on failure where as malloc will
107     // return nullptr.
108     std::unique_ptr<uint8_t, decltype(&free)> buffer(static_cast<uint8_t *>(malloc(kChunkSize)),
109                                                      &free);
110     if (buffer == nullptr) {
111         ALOGE("Failed to allocate read buffer");
112         return false;
113     }
114 
115     for (size_t index = 0; programHeaders.size() >= 2 && index < programHeaders.size() - 1;
116          index++) {
117         // find LOAD segments from program headers, calculate padding and punch holes
118         uint64_t punchOffset;
119         if (__builtin_add_overflow(programHeaders[index].p_offset, programHeaders[index].p_filesz,
120                                    &punchOffset)) {
121             ALOGE("Overflow occurred when adding offset and filesize");
122             return false;
123         }
124 
125         uint64_t punchLen;
126         if (__builtin_sub_overflow(programHeaders[index + 1].p_offset, punchOffset, &punchLen)) {
127             ALOGE("Overflow occurred when calculating length");
128             return false;
129         }
130 
131         if (punchLen < blockSize) {
132             continue;
133         }
134 
135         // if we have a uncompressed file which is being opened from APK, use the offset to
136         // punch native lib inside Apk.
137         uint64_t punchStartOffset;
138         if (__builtin_add_overflow(offset, punchOffset, &punchStartOffset)) {
139             ALOGE("Overflow occurred when calculating length");
140             return false;
141         }
142 
143         uint64_t position = punchStartOffset;
144         uint64_t endPosition;
145         if (__builtin_add_overflow(position, punchLen, &endPosition)) {
146             ALOGE("Overflow occurred when calculating length");
147             return false;
148         }
149 
150         // Read content in kChunkSize and verify it is zero
151         while (position <= endPosition) {
152             uint64_t uncheckedChunkEnd;
153             if (__builtin_add_overflow(position, kChunkSize, &uncheckedChunkEnd)) {
154                 ALOGE("Overflow occurred when calculating uncheckedChunkEnd");
155                 return false;
156             }
157 
158             uint64_t readLength;
159             if (__builtin_sub_overflow(std::min(uncheckedChunkEnd, endPosition), position,
160                                        &readLength)) {
161                 ALOGE("Overflow occurred when calculating readLength");
162                 return false;
163             }
164 
165             if (!ReadFullyAtOffset(fd, buffer.get(), readLength, position)) {
166                 ALOGE("Failed to read content to punch holes");
167                 return false;
168             }
169 
170             IF_ALOGD() {
171                 ALOGD("Punching holes for length:%" PRIu64 " content which should be zero: %s",
172                       readLength, HexString(buffer.get(), readLength).c_str());
173             }
174 
175             bool isZero = std::all_of(buffer.get(), buffer.get() + readLength,
176                                       [](uint8_t i) constexpr { return i == 0; });
177             if (!isZero) {
178                 ALOGE("Found non zero content while trying to punch hole. Skipping operation");
179                 return false;
180             }
181 
182             position = uncheckedChunkEnd;
183         }
184 
185         if (!punchWithBlockAlignment(fd, punchStartOffset, punchLen, blockSize)) {
186             return false;
187         }
188     }
189 
190     IF_ALOGD() {
191         struct stat64 afterPunch;
192         if (int result = lstat64(filePath, &afterPunch); result != 0) {
193             ALOGD("lstat64 failed for filePath %s, error:%d", filePath, errno);
194             return false;
195         }
196         ALOGD("Size after punching holes st_blocks: %" PRIu64 ", st_blksize: %" PRIu64
197               ", st_size: %" PRIu64 "",
198               static_cast<uint64_t>(afterPunch.st_blocks),
199               static_cast<uint64_t>(afterPunch.st_blksize),
200               static_cast<uint64_t>(afterPunch.st_size));
201     }
202 
203     return true;
204 }
205 
getLoadSegmentPhdrs(const char * filePath,const uint64_t offset,std::vector<Elf64_Phdr> & programHeaders)206 bool getLoadSegmentPhdrs(const char *filePath, const uint64_t offset,
207                          std::vector<Elf64_Phdr> &programHeaders) {
208     // Open Elf file
209     Elf64_Ehdr ehdr;
210     std::ifstream inputStream(filePath, std::ifstream::in);
211 
212     // If this is a zip file, set the offset so that we can read elf file directly
213     inputStream.seekg(offset);
214     // read executable headers
215     inputStream.read((char *)&ehdr, sizeof(ehdr));
216     if (!inputStream.good()) {
217         return false;
218     }
219 
220     // only consider elf64 for punching holes
221     if (ehdr.e_ident[EI_CLASS] != ELFCLASS64) {
222         ALOGW("Provided file is not ELF64");
223         return false;
224     }
225 
226     // read the program headers from elf file
227     uint64_t programHeaderOffset = ehdr.e_phoff;
228     uint16_t programHeaderNum = ehdr.e_phnum;
229 
230     // if this is a zip file, also consider elf offset inside a file
231     uint64_t phOffset;
232     if (__builtin_add_overflow(offset, programHeaderOffset, &phOffset)) {
233         ALOGE("Overflow occurred when calculating phOffset");
234         return false;
235     }
236     inputStream.seekg(phOffset);
237 
238     for (int headerIndex = 0; headerIndex < programHeaderNum; headerIndex++) {
239         Elf64_Phdr header;
240         inputStream.read((char *)&header, sizeof(header));
241         if (!inputStream.good()) {
242             return false;
243         }
244 
245         if (header.p_type != PT_LOAD) {
246             continue;
247         }
248         programHeaders.push_back(header);
249     }
250 
251     return true;
252 }
253 
punchHolesInElf64(const char * filePath,const uint64_t offset)254 bool punchHolesInElf64(const char *filePath, const uint64_t offset) {
255     std::vector<Elf64_Phdr> programHeaders;
256     if (!getLoadSegmentPhdrs(filePath, offset, programHeaders)) {
257         ALOGE("Failed to read program headers from ELF file.");
258         return false;
259     }
260     return punchHoles(filePath, offset, programHeaders);
261 }
262 
punchHolesInZip(const char * filePath,uint64_t offset,uint16_t extraFieldLen)263 bool punchHolesInZip(const char *filePath, uint64_t offset, uint16_t extraFieldLen) {
264     android::base::unique_fd fd(open(filePath, O_RDWR | O_CLOEXEC));
265     if (!fd.ok()) {
266         ALOGE("Can't open file to punch %s", filePath);
267         return false;
268     }
269 
270     struct stat64 beforePunch;
271     if (int result = lstat64(filePath, &beforePunch); result != 0) {
272         ALOGE("lstat64 failed for filePath %s, error:%d", filePath, errno);
273         return false;
274     }
275 
276     uint64_t blockSize = beforePunch.st_blksize;
277     IF_ALOGD() {
278         ALOGD("Extra field length: %hu,  Size before punching holes st_blocks: %" PRIu64
279               ", st_blksize: %" PRIu64 ", st_size: %" PRIu64 "",
280               extraFieldLen, static_cast<uint64_t>(beforePunch.st_blocks),
281               static_cast<uint64_t>(beforePunch.st_blksize),
282               static_cast<uint64_t>(beforePunch.st_size));
283     }
284 
285     if (extraFieldLen < blockSize) {
286         ALOGD("Skipping punching apk as extra field length is less than block size");
287         return false;
288     }
289 
290     // content is preceded by extra field. Zip offset is offset of exact content.
291     // move back by extraFieldLen so that scan can be started at start of extra field.
292     uint64_t extraFieldStart;
293     if (__builtin_sub_overflow(offset, extraFieldLen, &extraFieldStart)) {
294         ALOGE("Overflow occurred when calculating start of extra field");
295         return false;
296     }
297 
298     constexpr uint64_t kMaxSize = 64 * 1024;
299     // Use malloc to gracefully handle any oom conditions
300     std::unique_ptr<uint8_t, decltype(&free)> buffer(static_cast<uint8_t *>(malloc(kMaxSize)),
301                                                      &free);
302     if (buffer == nullptr) {
303         ALOGE("Failed to allocate read buffer");
304         return false;
305     }
306 
307     // Read the entire extra fields at once and punch file according to zero stretches.
308     if (!ReadFullyAtOffset(fd, buffer.get(), extraFieldLen, extraFieldStart)) {
309         ALOGE("Failed to read extra field content");
310         return false;
311     }
312 
313     IF_ALOGD() {
314         ALOGD("Extra field length: %hu content near offset: %s", extraFieldLen,
315               HexString(buffer.get(), extraFieldLen).c_str());
316     }
317 
318     uint64_t currentSize = 0;
319     while (currentSize < extraFieldLen) {
320         uint64_t end = currentSize;
321         // find zero ranges
322         while (end < extraFieldLen && *(buffer.get() + end) == 0) {
323             ++end;
324         }
325 
326         uint64_t punchLen;
327         if (__builtin_sub_overflow(end, currentSize, &punchLen)) {
328             ALOGW("Overflow occurred when calculating punching length");
329             return false;
330         }
331 
332         // Don't punch for every stretch of zero which is found
333         if (punchLen > blockSize) {
334             uint64_t punchOffset;
335             if (__builtin_add_overflow(extraFieldStart, currentSize, &punchOffset)) {
336                 ALOGW("Overflow occurred when calculating punch start offset");
337                 return false;
338             }
339 
340             if (!punchWithBlockAlignment(fd, punchOffset, punchLen, blockSize)) {
341                 return false;
342             }
343         }
344         currentSize = end;
345         ++currentSize;
346     }
347 
348     IF_ALOGD() {
349         struct stat64 afterPunch;
350         if (int result = lstat64(filePath, &afterPunch); result != 0) {
351             ALOGD("lstat64 failed for filePath %s, error:%d", filePath, errno);
352             return false;
353         }
354         ALOGD("punchHolesInApk:: Size after punching holes st_blocks: %" PRIu64
355               ", st_blksize: %" PRIu64 ", st_size: %" PRIu64 "",
356               static_cast<uint64_t>(afterPunch.st_blocks),
357               static_cast<uint64_t>(afterPunch.st_blksize),
358               static_cast<uint64_t>(afterPunch.st_size));
359     }
360     return true;
361 }
362 
363 }; // namespace android
364