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