xref: /aosp_15_r20/frameworks/base/core/jni/com_android_internal_content_NativeLibraryHelper.cpp (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
1 /*
2  * Copyright (C) 2011 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 "NativeLibraryHelper"
18 //#define LOG_NDEBUG 0
19 
20 #include <android-base/properties.h>
21 #include <androidfw/ApkParsing.h>
22 #include <androidfw/ZipFileRO.h>
23 #include <androidfw/ZipUtils.h>
24 #include <elf.h>
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <inttypes.h>
28 #include <linux/fs.h>
29 #include <nativehelper/ScopedUtfChars.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <sys/stat.h>
33 #include <sys/statfs.h>
34 #include <sys/types.h>
35 #include <time.h>
36 #include <unistd.h>
37 #include <utils/Log.h>
38 #include <zlib.h>
39 
40 #include <memory>
41 #include <string>
42 #include <vector>
43 
44 #include "com_android_internal_content_FileSystemUtils.h"
45 #include "core_jni_helpers.h"
46 
47 #define RS_BITCODE_SUFFIX ".bc"
48 
49 #define TMP_FILE_PATTERN "/tmp.XXXXXX"
50 #define TMP_FILE_PATTERN_LEN (sizeof(TMP_FILE_PATTERN) - 1)
51 
52 namespace android {
53 
54 // These match PackageManager.java install codes
55 enum install_status_t {
56     INSTALL_SUCCEEDED = 1,
57     INSTALL_FAILED_INVALID_APK = -2,
58     INSTALL_FAILED_INSUFFICIENT_STORAGE = -4,
59     INSTALL_FAILED_CONTAINER_ERROR = -18,
60     INSTALL_FAILED_INTERNAL_ERROR = -110,
61     INSTALL_FAILED_NO_MATCHING_ABIS = -113,
62     NO_NATIVE_LIBRARIES = -114
63 };
64 
65 // These code should match with PageSizeAppCompatFlags inside ApplicationInfo.java
66 constexpr int PAGE_SIZE_APP_COMPAT_FLAG_ERROR = -1;
67 constexpr int PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED = 0;
68 constexpr int PAGE_SIZE_APP_COMPAT_FLAG_UNCOMPRESSED_LIBS_NOT_ALIGNED = 1 << 1;
69 constexpr int PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED = 1 << 2;
70 
71 typedef install_status_t (*iterFunc)(JNIEnv*, void*, ZipFileRO*, ZipEntryRO, const char*);
72 
73 static bool
isFileDifferent(const char * filePath,uint32_t fileSize,time_t modifiedTime,uint32_t zipCrc,struct stat64 * st)74 isFileDifferent(const char* filePath, uint32_t fileSize, time_t modifiedTime,
75         uint32_t zipCrc, struct stat64* st)
76 {
77     if (lstat64(filePath, st) < 0) {
78         // File is not found or cannot be read.
79         ALOGV("Couldn't stat %s, copying: %s\n", filePath, strerror(errno));
80         return true;
81     }
82 
83     if (!S_ISREG(st->st_mode)) {
84         return true;
85     }
86 
87     if (static_cast<uint64_t>(st->st_size) != static_cast<uint64_t>(fileSize)) {
88         return true;
89     }
90 
91     // For some reason, bionic doesn't define st_mtime as time_t
92     if (time_t(st->st_mtime) != modifiedTime) {
93         ALOGV("mod time doesn't match: %ld vs. %ld\n", st->st_mtime, modifiedTime);
94         return true;
95     }
96 
97     int fd = TEMP_FAILURE_RETRY(open(filePath, O_RDONLY | O_CLOEXEC));
98     if (fd < 0) {
99         ALOGV("Couldn't open file %s: %s", filePath, strerror(errno));
100         return true;
101     }
102 
103     // uLong comes from zlib.h. It's a bit of a wart that they're
104     // potentially using a 64-bit type for a 32-bit CRC.
105     uLong crc = crc32(0L, Z_NULL, 0);
106     unsigned char crcBuffer[16384];
107     ssize_t numBytes;
108     while ((numBytes = TEMP_FAILURE_RETRY(read(fd, crcBuffer, sizeof(crcBuffer)))) > 0) {
109         crc = crc32(crc, crcBuffer, numBytes);
110     }
111     close(fd);
112 
113     ALOGV("%s: crc = %lx, zipCrc = %" PRIu32 "\n", filePath, crc, zipCrc);
114 
115     if (crc != static_cast<uLong>(zipCrc)) {
116         return true;
117     }
118 
119     return false;
120 }
121 
122 static install_status_t
sumFiles(JNIEnv *,void * arg,ZipFileRO * zipFile,ZipEntryRO zipEntry,const char *)123 sumFiles(JNIEnv*, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntry, const char*)
124 {
125     size_t* total = (size_t*) arg;
126     uint32_t uncompLen;
127 
128     if (!zipFile->getEntryInfo(zipEntry, nullptr, &uncompLen, nullptr, nullptr, nullptr, nullptr,
129                                nullptr)) {
130         return INSTALL_FAILED_INVALID_APK;
131     }
132 
133     *total += static_cast<size_t>(uncompLen);
134 
135     return INSTALL_SUCCEEDED;
136 }
137 
extractNativeLibFromApk(ZipFileRO * zipFile,ZipEntryRO zipEntry,const char * fileName,const std::string nativeLibPath,uint32_t when,uint32_t uncompLen,uint32_t crc)138 static install_status_t extractNativeLibFromApk(ZipFileRO* zipFile, ZipEntryRO zipEntry,
139                                                 const char* fileName,
140                                                 const std::string nativeLibPath, uint32_t when,
141                                                 uint32_t uncompLen, uint32_t crc) {
142     // Build local file path
143     const size_t fileNameLen = strlen(fileName);
144     char localFileName[nativeLibPath.size() + fileNameLen + 2];
145 
146     if (strlcpy(localFileName, nativeLibPath.c_str(), sizeof(localFileName)) != nativeLibPath.size()) {
147         ALOGE("Couldn't allocate local file name for library");
148         return INSTALL_FAILED_INTERNAL_ERROR;
149     }
150 
151     *(localFileName + nativeLibPath.size()) = '/';
152 
153     if (strlcpy(localFileName + nativeLibPath.size() + 1, fileName, sizeof(localFileName)
154                     - nativeLibPath.size() - 1) != fileNameLen) {
155         ALOGE("Couldn't allocate local file name for library");
156         return INSTALL_FAILED_INTERNAL_ERROR;
157     }
158 
159     // Only copy out the native file if it's different.
160     struct tm t;
161     ZipUtils::zipTimeToTimespec(when, &t);
162     const time_t modTime = mktime(&t);
163     struct stat64 st;
164     if (!isFileDifferent(localFileName, uncompLen, modTime, crc, &st)) {
165         return INSTALL_SUCCEEDED;
166     }
167 
168     char localTmpFileName[nativeLibPath.size() + TMP_FILE_PATTERN_LEN + 1];
169     if (strlcpy(localTmpFileName, nativeLibPath.c_str(), sizeof(localTmpFileName))
170             != nativeLibPath.size()) {
171         ALOGE("Couldn't allocate local file name for library");
172         return INSTALL_FAILED_INTERNAL_ERROR;
173     }
174 
175     if (strlcpy(localTmpFileName + nativeLibPath.size(), TMP_FILE_PATTERN,
176                     TMP_FILE_PATTERN_LEN + 1) != TMP_FILE_PATTERN_LEN) {
177         ALOGE("Couldn't allocate temporary file name for library");
178         return INSTALL_FAILED_INTERNAL_ERROR;
179     }
180 
181     int fd = mkstemp(localTmpFileName);
182     if (fd < 0) {
183         ALOGE("Couldn't open temporary file name: %s: %s\n", localTmpFileName, strerror(errno));
184         return INSTALL_FAILED_CONTAINER_ERROR;
185     }
186 
187     // If a filesystem like f2fs supports per-file compression, set the compression bit before data
188     // writes
189     unsigned int flags;
190     if (ioctl(fd, FS_IOC_GETFLAGS, &flags) == -1) {
191         ALOGE("Failed to call FS_IOC_GETFLAGS on %s: %s\n", localTmpFileName, strerror(errno));
192     } else if ((flags & FS_COMPR_FL) == 0) {
193         flags |= FS_COMPR_FL;
194         ioctl(fd, FS_IOC_SETFLAGS, &flags);
195     }
196 
197     if (!zipFile->uncompressEntry(zipEntry, fd)) {
198         ALOGE("Failed uncompressing %s to %s\n", fileName, localTmpFileName);
199         close(fd);
200         unlink(localTmpFileName);
201         return INSTALL_FAILED_CONTAINER_ERROR;
202     }
203 
204     if (fsync(fd) < 0) {
205         ALOGE("Coulnd't fsync temporary file name: %s: %s\n", localTmpFileName, strerror(errno));
206         close(fd);
207         unlink(localTmpFileName);
208         return INSTALL_FAILED_INTERNAL_ERROR;
209     }
210 
211     close(fd);
212 
213     // Set the modification time for this file to the ZIP's mod time.
214     struct timeval times[2];
215     times[0].tv_sec = st.st_atime;
216     times[1].tv_sec = modTime;
217     times[0].tv_usec = times[1].tv_usec = 0;
218     if (utimes(localTmpFileName, times) < 0) {
219         ALOGE("Couldn't change modification time on %s: %s\n", localTmpFileName, strerror(errno));
220         unlink(localTmpFileName);
221         return INSTALL_FAILED_CONTAINER_ERROR;
222     }
223 
224     // Set the mode to 755
225     static const mode_t mode = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP |  S_IXGRP | S_IROTH | S_IXOTH;
226     if (chmod(localTmpFileName, mode) < 0) {
227         ALOGE("Couldn't change permissions on %s: %s\n", localTmpFileName, strerror(errno));
228         unlink(localTmpFileName);
229         return INSTALL_FAILED_CONTAINER_ERROR;
230     }
231 
232     // Finally, rename it to the final name.
233     if (rename(localTmpFileName, localFileName) < 0) {
234         ALOGE("Couldn't rename %s to %s: %s\n", localTmpFileName, localFileName, strerror(errno));
235         unlink(localTmpFileName);
236         return INSTALL_FAILED_CONTAINER_ERROR;
237     }
238 
239 #ifdef ENABLE_PUNCH_HOLES
240     // punch extracted elf files as well. This will fail where compression is on (like f2fs) but it
241     // will be useful for ext4 based systems
242     struct statfs64 fsInfo;
243     int result = statfs64(localFileName, &fsInfo);
244     if (result < 0) {
245         ALOGW("Failed to stat file :%s", localFileName);
246     }
247 
248     if (result == 0 && fsInfo.f_type == EXT4_SUPER_MAGIC) {
249         ALOGD("Punching extracted elf file %s on fs:%" PRIu64 "", fileName,
250               static_cast<uint64_t>(fsInfo.f_type));
251         if (!punchHolesInElf64(localFileName, 0)) {
252             ALOGW("Failed to punch extracted elf file :%s from apk : %s", fileName,
253                   zipFile->getZipFileName());
254         }
255     }
256 #endif // ENABLE_PUNCH_HOLES
257 
258     ALOGV("Successfully moved %s to %s\n", localTmpFileName, localFileName);
259 
260     return INSTALL_SUCCEEDED;
261 }
262 
263 /*
264  * Copy the native library if needed.
265  *
266  * This function assumes the library and path names passed in are considered safe.
267  */
copyFileIfChanged(JNIEnv * env,void * arg,ZipFileRO * zipFile,ZipEntryRO zipEntry,const char * fileName)268 static install_status_t copyFileIfChanged(JNIEnv* env, void* arg, ZipFileRO* zipFile,
269                                           ZipEntryRO zipEntry, const char* fileName) {
270     static const size_t kPageSize = getpagesize();
271     void** args = reinterpret_cast<void**>(arg);
272     jstring* javaNativeLibPath = (jstring*)args[0];
273     jboolean extractNativeLibs = *(jboolean*)args[1];
274     jboolean debuggable = *(jboolean*)args[2];
275     jboolean app_compat_16kb = *(jboolean*)args[3];
276     install_status_t ret = INSTALL_SUCCEEDED;
277 
278     ScopedUtfChars nativeLibPath(env, *javaNativeLibPath);
279 
280     uint32_t uncompLen;
281     uint32_t when;
282     uint32_t crc;
283 
284     uint16_t method;
285     off64_t offset;
286     uint16_t extraFieldLength;
287     if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, nullptr, &offset, &when, &crc,
288                                &extraFieldLength)) {
289         ALOGE("Couldn't read zip entry info\n");
290         return INSTALL_FAILED_INVALID_APK;
291     }
292 
293     // Always extract wrap.sh for debuggable, even if extractNativeLibs=false. This makes it
294     // easier to use wrap.sh because it only works when it is extracted, see
295     // frameworks/base/services/core/java/com/android/server/am/ProcessList.java.
296     bool forceExtractCurrentFile = debuggable && strcmp(fileName, "wrap.sh") == 0;
297 
298     if (!extractNativeLibs && !forceExtractCurrentFile) {
299         // check if library is uncompressed and page-aligned
300         if (method != ZipFileRO::kCompressStored) {
301             ALOGE("Library '%s' is compressed - will not be able to open it directly from apk.\n",
302                   fileName);
303             return INSTALL_FAILED_INVALID_APK;
304         }
305 
306         if (offset % kPageSize != 0) {
307             // If the library is zip-aligned correctly for 4kb devices and app compat is
308             // enabled, on 16kb devices fallback to extraction
309             if (offset % 0x1000 == 0 && app_compat_16kb) {
310                 ALOGI("16kB AppCompat: Library '%s' is not PAGE(%zu)-aligned - falling back to "
311                       "extraction from apk\n",
312                       fileName, kPageSize);
313                 return extractNativeLibFromApk(zipFile, zipEntry, fileName, nativeLibPath.c_str(),
314                                                when, uncompLen, crc);
315             }
316 
317             ALOGE("Library '%s' is not PAGE(%zu)-aligned - will not be able to open it directly "
318                   "from apk.\n",
319                   fileName, kPageSize);
320             return INSTALL_FAILED_INVALID_APK;
321         }
322 
323 #ifdef ENABLE_PUNCH_HOLES
324         // if library is uncompressed, punch hole in it in place
325         if (!punchHolesInElf64(zipFile->getZipFileName(), offset)) {
326             ALOGW("Failed to punch uncompressed elf file :%s inside apk : %s at offset: "
327                   "%" PRIu64 "",
328                   fileName, zipFile->getZipFileName(), offset);
329         }
330 
331         // if extra field for this zip file is present with some length, possibility is that it is
332         // padding added for zip alignment. Punch holes there too.
333         if (!punchHolesInZip(zipFile->getZipFileName(), offset, extraFieldLength)) {
334             ALOGW("Failed to punch apk : %s at extra field", zipFile->getZipFileName());
335         }
336 #endif // ENABLE_PUNCH_HOLES
337 
338         return INSTALL_SUCCEEDED;
339     }
340 
341     return extractNativeLibFromApk(zipFile, zipEntry, fileName, nativeLibPath.c_str(), when,
342                                    uncompLen, crc);
343 }
344 
345 /*
346  * An iterator over all shared libraries in a zip file. An entry is
347  * considered to be a shared library if all of the conditions below are
348  * satisfied :
349  *
350  * - The entry is under the lib/ directory.
351  * - The entry name ends with ".so" and the entry name starts with "lib",
352  *   an exception is made for debuggable apps.
353  * - The entry filename is "safe" (as determined by isFilenameSafe).
354  *
355  */
356 class NativeLibrariesIterator {
357 private:
NativeLibrariesIterator(ZipFileRO * zipFile,bool debuggable,void * cookie)358     NativeLibrariesIterator(ZipFileRO* zipFile, bool debuggable, void* cookie)
359           : mZipFile(zipFile), mDebuggable(debuggable), mCookie(cookie), mLastSlash(nullptr) {
360         fileName[0] = '\0';
361     }
362 
363 public:
create(ZipFileRO * zipFile,bool debuggable)364     static base::expected<std::unique_ptr<NativeLibrariesIterator>, int32_t> create(
365             ZipFileRO* zipFile, bool debuggable) {
366         // Do not specify a suffix to find both .so files and gdbserver.
367         auto result = zipFile->startIterationOrError(APK_LIB.data(), nullptr /* suffix */);
368         if (!result.ok()) {
369             return base::unexpected(result.error());
370         }
371 
372         return std::unique_ptr<NativeLibrariesIterator>(
373                 new NativeLibrariesIterator(zipFile, debuggable, result.value()));
374     }
375 
next()376     base::expected<ZipEntryRO, int32_t> next() {
377         ZipEntryRO nextEntry;
378         while (true) {
379             auto next = mZipFile->nextEntryOrError(mCookie);
380             if (!next.ok()) {
381                 return base::unexpected(next.error());
382             }
383             nextEntry = next.value();
384             if (nextEntry == nullptr) {
385                 break;
386             }
387             // Make sure this entry has a filename.
388             if (mZipFile->getEntryFileName(nextEntry, fileName, sizeof(fileName))) {
389                 continue;
390             }
391 
392             const char* lastSlash = util::ValidLibraryPathLastSlash(fileName, false, mDebuggable);
393             if (lastSlash) {
394                 mLastSlash = lastSlash;
395                 break;
396             }
397         }
398 
399         return nextEntry;
400     }
401 
currentEntry() const402     inline const char* currentEntry() const {
403         return fileName;
404     }
405 
lastSlash() const406     inline const char* lastSlash() const {
407         return mLastSlash;
408     }
409 
~NativeLibrariesIterator()410     virtual ~NativeLibrariesIterator() {
411         mZipFile->endIteration(mCookie);
412     }
413 private:
414 
415     char fileName[PATH_MAX];
416     ZipFileRO* const mZipFile;
417     const bool mDebuggable;
418     void* mCookie;
419     const char* mLastSlash;
420 };
421 
422 static install_status_t
iterateOverNativeFiles(JNIEnv * env,jlong apkHandle,jstring javaCpuAbi,jboolean debuggable,iterFunc callFunc,void * callArg)423 iterateOverNativeFiles(JNIEnv *env, jlong apkHandle, jstring javaCpuAbi,
424                        jboolean debuggable, iterFunc callFunc, void* callArg) {
425     ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle);
426     if (zipFile == nullptr) {
427         return INSTALL_FAILED_INVALID_APK;
428     }
429 
430     auto result = NativeLibrariesIterator::create(zipFile, debuggable);
431     if (!result.ok()) {
432         return INSTALL_FAILED_INVALID_APK;
433     }
434     std::unique_ptr<NativeLibrariesIterator> it(std::move(result.value()));
435 
436     const ScopedUtfChars cpuAbi(env, javaCpuAbi);
437     if (cpuAbi.c_str() == nullptr) {
438         // This would've thrown, so this return code isn't observable by Java.
439         return INSTALL_FAILED_INVALID_APK;
440     }
441 
442     while (true) {
443         auto next = it->next();
444         if (!next.ok()) {
445             return INSTALL_FAILED_INVALID_APK;
446         }
447         auto entry = next.value();
448         if (entry == nullptr) {
449             break;
450         }
451 
452         const char* fileName = it->currentEntry();
453         const char* lastSlash = it->lastSlash();
454 
455         // Check to make sure the CPU ABI of this file is one we support.
456         const char* cpuAbiOffset = fileName + APK_LIB_LEN;
457         const size_t cpuAbiRegionSize = lastSlash - cpuAbiOffset;
458 
459         if (cpuAbi.size() == cpuAbiRegionSize && !strncmp(cpuAbiOffset, cpuAbi.c_str(), cpuAbiRegionSize)) {
460             install_status_t ret = callFunc(env, callArg, zipFile, entry, lastSlash + 1);
461 
462             if (ret != INSTALL_SUCCEEDED) {
463                 ALOGV("Failure for entry %s", lastSlash + 1);
464                 return ret;
465             }
466         }
467     }
468 
469     return INSTALL_SUCCEEDED;
470 }
471 
findSupportedAbi(JNIEnv * env,jlong apkHandle,jobjectArray supportedAbisArray,jboolean debuggable)472 static int findSupportedAbi(JNIEnv* env, jlong apkHandle, jobjectArray supportedAbisArray,
473                             jboolean debuggable) {
474     ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle);
475     if (zipFile == nullptr) {
476         return INSTALL_FAILED_INVALID_APK;
477     }
478 
479     auto result = NativeLibrariesIterator::create(zipFile, debuggable);
480     if (!result.ok()) {
481         return INSTALL_FAILED_INVALID_APK;
482     }
483     std::unique_ptr<NativeLibrariesIterator> it(std::move(result.value()));
484 
485     const int numAbis = env->GetArrayLength(supportedAbisArray);
486 
487     std::vector<ScopedUtfChars> supportedAbis;
488     supportedAbis.reserve(numAbis);
489     for (int i = 0; i < numAbis; ++i) {
490         supportedAbis.emplace_back(env, (jstring)env->GetObjectArrayElement(supportedAbisArray, i));
491     }
492 
493     int status = NO_NATIVE_LIBRARIES;
494     while (true) {
495         auto next = it->next();
496         if (!next.ok()) {
497             return INSTALL_FAILED_INVALID_APK;
498         }
499         auto entry = next.value();
500         if (entry == nullptr) {
501             break;
502         }
503 
504         // We're currently in the lib/ directory of the APK, so it does have some native
505         // code. We should return INSTALL_FAILED_NO_MATCHING_ABIS if none of the
506         // libraries match.
507         if (status == NO_NATIVE_LIBRARIES) {
508             status = INSTALL_FAILED_NO_MATCHING_ABIS;
509         }
510 
511         const char* fileName = it->currentEntry();
512         const char* lastSlash = it->lastSlash();
513 
514         // Check to see if this CPU ABI matches what we are looking for.
515         const char* abiOffset = fileName + APK_LIB_LEN;
516         const size_t abiSize = lastSlash - abiOffset;
517         for (int i = 0; i < numAbis; i++) {
518             const ScopedUtfChars& abi = supportedAbis[i];
519             if (abi.size() == abiSize && !strncmp(abiOffset, abi.c_str(), abiSize)) {
520                 // The entry that comes in first (i.e. with a lower index) has the higher priority.
521                 if (((i < status) && (status >= 0)) || (status < 0) ) {
522                     status = i;
523                 }
524             }
525         }
526     }
527 
528     return status;
529 }
530 
app_compat_16kb_enabled()531 static inline bool app_compat_16kb_enabled() {
532     static const size_t kPageSize = getpagesize();
533 
534     // App compat is only applicable on 16kb-page-size devices.
535     if (kPageSize != 0x4000) {
536         return false;
537     }
538 
539     // Explicit disabled status for app compat
540     return !android::base::GetBoolProperty("pm.16kb.app_compat.disabled", false);
541 }
542 
543 static jint
com_android_internal_content_NativeLibraryHelper_copyNativeBinaries(JNIEnv * env,jclass clazz,jlong apkHandle,jstring javaNativeLibPath,jstring javaCpuAbi,jboolean extractNativeLibs,jboolean debuggable)544 com_android_internal_content_NativeLibraryHelper_copyNativeBinaries(JNIEnv *env, jclass clazz,
545         jlong apkHandle, jstring javaNativeLibPath, jstring javaCpuAbi,
546         jboolean extractNativeLibs, jboolean debuggable)
547 {
548     jboolean app_compat_16kb = app_compat_16kb_enabled();
549     void* args[] = { &javaNativeLibPath, &extractNativeLibs, &debuggable, &app_compat_16kb };
550     return (jint) iterateOverNativeFiles(env, apkHandle, javaCpuAbi, debuggable,
551             copyFileIfChanged, reinterpret_cast<void*>(args));
552 }
553 
554 static jlong
com_android_internal_content_NativeLibraryHelper_sumNativeBinaries(JNIEnv * env,jclass clazz,jlong apkHandle,jstring javaCpuAbi,jboolean debuggable)555 com_android_internal_content_NativeLibraryHelper_sumNativeBinaries(JNIEnv *env, jclass clazz,
556         jlong apkHandle, jstring javaCpuAbi, jboolean debuggable)
557 {
558     size_t totalSize = 0;
559 
560     iterateOverNativeFiles(env, apkHandle, javaCpuAbi, debuggable, sumFiles, &totalSize);
561 
562     return totalSize;
563 }
564 
565 static jint
com_android_internal_content_NativeLibraryHelper_findSupportedAbi(JNIEnv * env,jclass clazz,jlong apkHandle,jobjectArray javaCpuAbisToSearch,jboolean debuggable)566 com_android_internal_content_NativeLibraryHelper_findSupportedAbi(JNIEnv *env, jclass clazz,
567         jlong apkHandle, jobjectArray javaCpuAbisToSearch, jboolean debuggable)
568 {
569     return (jint) findSupportedAbi(env, apkHandle, javaCpuAbisToSearch, debuggable);
570 }
571 
572 enum bitcode_scan_result_t {
573   APK_SCAN_ERROR = -1,
574   NO_BITCODE_PRESENT = 0,
575   BITCODE_PRESENT = 1,
576 };
577 
578 static jint
com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode(JNIEnv * env,jclass clazz,jlong apkHandle)579 com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode(JNIEnv *env, jclass clazz,
580         jlong apkHandle) {
581     ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle);
582     void* cookie = nullptr;
583     if (!zipFile->startIteration(&cookie, nullptr /* prefix */, RS_BITCODE_SUFFIX)) {
584         return APK_SCAN_ERROR;
585     }
586 
587     char fileName[PATH_MAX];
588     ZipEntryRO next = nullptr;
589     while ((next = zipFile->nextEntry(cookie)) != nullptr) {
590         if (zipFile->getEntryFileName(next, fileName, sizeof(fileName))) {
591             continue;
592         }
593         const char* lastSlash = strrchr(fileName, '/');
594         const char* baseName = (lastSlash == nullptr) ? fileName : fileName + 1;
595         if (util::isFilenameSafe(baseName)) {
596             zipFile->endIteration(cookie);
597             return BITCODE_PRESENT;
598         }
599     }
600 
601     zipFile->endIteration(cookie);
602     return NO_BITCODE_PRESENT;
603 }
604 
605 static jlong
com_android_internal_content_NativeLibraryHelper_openApk(JNIEnv * env,jclass,jstring apkPath)606 com_android_internal_content_NativeLibraryHelper_openApk(JNIEnv *env, jclass, jstring apkPath)
607 {
608     ScopedUtfChars filePath(env, apkPath);
609     ZipFileRO* zipFile = ZipFileRO::open(filePath.c_str());
610 
611     return reinterpret_cast<jlong>(zipFile);
612 }
613 
614 static jlong
com_android_internal_content_NativeLibraryHelper_openApkFd(JNIEnv * env,jclass,jobject fileDescriptor,jstring debugPathName)615 com_android_internal_content_NativeLibraryHelper_openApkFd(JNIEnv *env, jclass,
616         jobject fileDescriptor, jstring debugPathName)
617 {
618     ScopedUtfChars debugFilePath(env, debugPathName);
619 
620     int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
621     if (fd < 0) {
622         jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor");
623         return 0;
624     }
625 
626     int dupedFd = fcntl(fd, F_DUPFD_CLOEXEC, 0);
627     if (dupedFd == -1) {
628         jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
629                              "Failed to dup FileDescriptor: %s", strerror(errno));
630         return 0;
631     }
632 
633     ZipFileRO* zipFile = ZipFileRO::openFd(dupedFd, debugFilePath.c_str());
634 
635     return reinterpret_cast<jlong>(zipFile);
636 }
637 
checkLoadSegmentAlignment(const char * fileName,off64_t offset)638 static jint checkLoadSegmentAlignment(const char* fileName, off64_t offset) {
639     std::vector<Elf64_Phdr> programHeaders;
640     if (!getLoadSegmentPhdrs(fileName, offset, programHeaders)) {
641         ALOGE("Failed to read program headers from ELF file.");
642         return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
643     }
644 
645     int mode = PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
646     for (auto programHeader : programHeaders) {
647         if (programHeader.p_type != PT_LOAD) {
648             continue;
649         }
650 
651         // Set ELF alignment bit if 4 KB aligned LOAD segment is found
652         if (programHeader.p_align == 0x1000) {
653             ALOGI("Setting page size compat mode PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED");
654             mode |= PAGE_SIZE_APP_COMPAT_FLAG_ELF_NOT_ALIGNED;
655             break;
656         }
657     }
658 
659     return mode;
660 }
661 
checkExtractedLibAlignment(ZipFileRO * zipFile,ZipEntryRO zipEntry,const char * fileName,const std::string nativeLibPath)662 static jint checkExtractedLibAlignment(ZipFileRO* zipFile, ZipEntryRO zipEntry,
663                                        const char* fileName, const std::string nativeLibPath) {
664     // Build local file path
665     const size_t fileNameLen = strlen(fileName);
666     char localFileName[nativeLibPath.size() + fileNameLen + 2];
667 
668     if (strlcpy(localFileName, nativeLibPath.c_str(), sizeof(localFileName)) !=
669         nativeLibPath.size()) {
670         ALOGE("Couldn't allocate local file name for library");
671         return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
672     }
673 
674     *(localFileName + nativeLibPath.size()) = '/';
675 
676     if (strlcpy(localFileName + nativeLibPath.size() + 1, fileName,
677                 sizeof(localFileName) - nativeLibPath.size() - 1) != fileNameLen) {
678         ALOGE("Couldn't allocate local file name for library");
679         return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
680     }
681 
682     struct statfs64 fsInfo;
683     int result = statfs64(localFileName, &fsInfo);
684     if (result < 0) {
685         ALOGE("Failed to stat file :%s", localFileName);
686         return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
687     }
688 
689     return checkLoadSegmentAlignment(localFileName, 0);
690 }
691 
checkAlignment(JNIEnv * env,jstring javaNativeLibPath,jboolean extractNativeLibs,ZipFileRO * zipFile,ZipEntryRO zipEntry,const char * fileName)692 static jint checkAlignment(JNIEnv* env, jstring javaNativeLibPath, jboolean extractNativeLibs,
693                            ZipFileRO* zipFile, ZipEntryRO zipEntry, const char* fileName) {
694     int mode = PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
695     // Need two separate install status for APK and ELF alignment
696     static const size_t kPageSize = getpagesize();
697     jint ret = INSTALL_SUCCEEDED;
698 
699     ScopedUtfChars nativeLibPath(env, javaNativeLibPath);
700     if (extractNativeLibs) {
701         ALOGI("extractNativeLibs specified, checking for extracted lib %s", fileName);
702         return checkExtractedLibAlignment(zipFile, zipEntry, fileName, nativeLibPath.c_str());
703     }
704 
705     uint16_t method;
706     off64_t offset;
707     if (!zipFile->getEntryInfo(zipEntry, &method, nullptr, nullptr, &offset, nullptr, nullptr,
708                                nullptr)) {
709         ALOGE("Couldn't read zip entry info from zipFile %s", zipFile->getZipFileName());
710         return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
711     }
712 
713     // check if library is uncompressed and page-aligned
714     if (method != ZipFileRO::kCompressStored) {
715         ALOGE("Library '%s' is compressed - will not be able to open it directly from apk.\n",
716               fileName);
717         return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
718     }
719 
720     if (offset % kPageSize != 0) {
721         ALOGW("Library '%s' is not PAGE(%zu)-aligned - will not be able to open it directly "
722               "from apk.\n",
723               fileName, kPageSize);
724         mode |= PAGE_SIZE_APP_COMPAT_FLAG_UNCOMPRESSED_LIBS_NOT_ALIGNED;
725         ALOGI("Setting page size compat mode "
726               "PAGE_SIZE_APP_COMPAT_FLAG_UNCOMPRESSED_LIBS_NOT_ALIGNED for %s",
727               zipFile->getZipFileName());
728     }
729 
730     int loadMode = checkLoadSegmentAlignment(zipFile->getZipFileName(), offset);
731     if (loadMode == PAGE_SIZE_APP_COMPAT_FLAG_ERROR) {
732         return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
733     }
734     mode |= loadMode;
735     return mode;
736 }
737 
738 // TODO(b/371049373): This function is copy of iterateOverNativeFiles with different way of handling
739 // and combining return values for all ELF and APKs. Find a way to consolidate two functions.
com_android_internal_content_NativeLibraryHelper_checkApkAlignment(JNIEnv * env,jclass clazz,jlong apkHandle,jstring javaNativeLibPath,jstring javaCpuAbi,jboolean extractNativeLibs,jboolean debuggable)740 static jint com_android_internal_content_NativeLibraryHelper_checkApkAlignment(
741         JNIEnv* env, jclass clazz, jlong apkHandle, jstring javaNativeLibPath, jstring javaCpuAbi,
742         jboolean extractNativeLibs, jboolean debuggable) {
743     int mode = PAGE_SIZE_APP_COMPAT_FLAG_UNDEFINED;
744     ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle);
745     if (zipFile == nullptr) {
746         ALOGE("zipfile handle is null");
747         return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
748     }
749 
750     auto result = NativeLibrariesIterator::create(zipFile, debuggable);
751     if (!result.ok()) {
752         ALOGE("Can't iterate over native libs for file:%s", zipFile->getZipFileName());
753         return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
754     }
755     std::unique_ptr<NativeLibrariesIterator> it(std::move(result.value()));
756 
757     const ScopedUtfChars cpuAbi(env, javaCpuAbi);
758     if (cpuAbi.c_str() == nullptr) {
759         ALOGE("cpuAbi is nullptr");
760         // This would've thrown, so this return code isn't observable by Java.
761         return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
762     }
763 
764     while (true) {
765         auto next = it->next();
766         if (!next.ok()) {
767             ALOGE("next iterator not found Error:%d", next.error());
768             return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
769         }
770         auto entry = next.value();
771         if (entry == nullptr) {
772             break;
773         }
774 
775         const char* fileName = it->currentEntry();
776         const char* lastSlash = it->lastSlash();
777 
778         // Check to make sure the CPU ABI of this file is one we support.
779         const char* cpuAbiOffset = fileName + APK_LIB_LEN;
780         const size_t cpuAbiRegionSize = lastSlash - cpuAbiOffset;
781 
782         if (cpuAbi.size() == cpuAbiRegionSize &&
783             !strncmp(cpuAbiOffset, cpuAbi.c_str(), cpuAbiRegionSize)) {
784             int ret = checkAlignment(env, javaNativeLibPath, extractNativeLibs, zipFile, entry,
785                                      lastSlash + 1);
786             if (ret == PAGE_SIZE_APP_COMPAT_FLAG_ERROR) {
787                 ALOGE("Alignment check returned for zipfile: %s, entry:%s",
788                       zipFile->getZipFileName(), lastSlash + 1);
789                 return PAGE_SIZE_APP_COMPAT_FLAG_ERROR;
790             }
791             mode |= ret;
792         }
793     }
794 
795     return mode;
796 }
797 
798 static void
com_android_internal_content_NativeLibraryHelper_close(JNIEnv * env,jclass,jlong apkHandle)799 com_android_internal_content_NativeLibraryHelper_close(JNIEnv *env, jclass, jlong apkHandle)
800 {
801     delete reinterpret_cast<ZipFileRO*>(apkHandle);
802 }
803 
804 static const JNINativeMethod gMethods[] = {
805         {"nativeOpenApk", "(Ljava/lang/String;)J",
806          (void*)com_android_internal_content_NativeLibraryHelper_openApk},
807         {"nativeOpenApkFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;)J",
808          (void*)com_android_internal_content_NativeLibraryHelper_openApkFd},
809         {"nativeClose", "(J)V", (void*)com_android_internal_content_NativeLibraryHelper_close},
810         {"nativeCopyNativeBinaries", "(JLjava/lang/String;Ljava/lang/String;ZZ)I",
811          (void*)com_android_internal_content_NativeLibraryHelper_copyNativeBinaries},
812         {"nativeSumNativeBinaries", "(JLjava/lang/String;Z)J",
813          (void*)com_android_internal_content_NativeLibraryHelper_sumNativeBinaries},
814         {"nativeFindSupportedAbi", "(J[Ljava/lang/String;Z)I",
815          (void*)com_android_internal_content_NativeLibraryHelper_findSupportedAbi},
816         {"hasRenderscriptBitcode", "(J)I",
817          (void*)com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode},
818         {"nativeCheckAlignment", "(JLjava/lang/String;Ljava/lang/String;ZZ)I",
819          (void*)com_android_internal_content_NativeLibraryHelper_checkApkAlignment},
820 };
821 
register_com_android_internal_content_NativeLibraryHelper(JNIEnv * env)822 int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env)
823 {
824     return RegisterMethodsOrDie(env,
825             "com/android/internal/content/NativeLibraryHelper", gMethods, NELEM(gMethods));
826 }
827 
828 };
829