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 "core_jni_helpers.h" 21 22 #include <ScopedUtfChars.h> 23 #include <UniquePtr.h> 24 #include <androidfw/ZipFileRO.h> 25 #include <androidfw/ZipUtils.h> 26 #include <utils/Log.h> 27 #include <utils/Vector.h> 28 29 #include <zlib.h> 30 31 #include <fcntl.h> 32 #include <stdlib.h> 33 #include <string.h> 34 #include <time.h> 35 #include <unistd.h> 36 #include <inttypes.h> 37 #include <sys/stat.h> 38 #include <sys/types.h> 39 40 41 #define APK_LIB "lib/" 42 #define APK_LIB_LEN (sizeof(APK_LIB) - 1) 43 44 #define LIB_PREFIX "/lib" 45 #define LIB_PREFIX_LEN (sizeof(LIB_PREFIX) - 1) 46 47 #define LIB_SUFFIX ".so" 48 #define LIB_SUFFIX_LEN (sizeof(LIB_SUFFIX) - 1) 49 50 #define RS_BITCODE_SUFFIX ".bc" 51 52 #define GDBSERVER "gdbserver" 53 #define GDBSERVER_LEN (sizeof(GDBSERVER) - 1) 54 55 #define TMP_FILE_PATTERN "/tmp.XXXXXX" 56 #define TMP_FILE_PATTERN_LEN (sizeof(TMP_FILE_PATTERN) - 1) 57 58 namespace android { 59 60 // These match PackageManager.java install codes 61 enum install_status_t { 62 INSTALL_SUCCEEDED = 1, 63 INSTALL_FAILED_INVALID_APK = -2, 64 INSTALL_FAILED_INSUFFICIENT_STORAGE = -4, 65 INSTALL_FAILED_CONTAINER_ERROR = -18, 66 INSTALL_FAILED_INTERNAL_ERROR = -110, 67 INSTALL_FAILED_NO_MATCHING_ABIS = -113, 68 NO_NATIVE_LIBRARIES = -114 69 }; 70 71 typedef install_status_t (*iterFunc)(JNIEnv*, void*, ZipFileRO*, ZipEntryRO, const char*); 72 73 // Equivalent to android.os.FileUtils.isFilenameSafe 74 static bool 75 isFilenameSafe(const char* filename) 76 { 77 off_t offset = 0; 78 for (;;) { 79 switch (*(filename + offset)) { 80 case 0: 81 // Null. 82 // If we've reached the end, all the other characters are good. 83 return true; 84 85 case 'A' ... 'Z': 86 case 'a' ... 'z': 87 case '0' ... '9': 88 case '+': 89 case ',': 90 case '-': 91 case '.': 92 case '/': 93 case '=': 94 case '_': 95 offset++; 96 break; 97 98 default: 99 // We found something that is not good. 100 return false; 101 } 102 } 103 // Should not reach here. 104 } 105 106 static bool 107 isFileDifferent(const char* filePath, uint32_t fileSize, time_t modifiedTime, 108 uint32_t zipCrc, struct stat64* st) 109 { 110 if (lstat64(filePath, st) < 0) { 111 // File is not found or cannot be read. 112 ALOGV("Couldn't stat %s, copying: %s\n", filePath, strerror(errno)); 113 return true; 114 } 115 116 if (!S_ISREG(st->st_mode)) { 117 return true; 118 } 119 120 if (static_cast<uint64_t>(st->st_size) != static_cast<uint64_t>(fileSize)) { 121 return true; 122 } 123 124 // For some reason, bionic doesn't define st_mtime as time_t 125 if (time_t(st->st_mtime) != modifiedTime) { 126 ALOGV("mod time doesn't match: %ld vs. %ld\n", st->st_mtime, modifiedTime); 127 return true; 128 } 129 130 int fd = TEMP_FAILURE_RETRY(open(filePath, O_RDONLY)); 131 if (fd < 0) { 132 ALOGV("Couldn't open file %s: %s", filePath, strerror(errno)); 133 return true; 134 } 135 136 // uLong comes from zlib.h. It's a bit of a wart that they're 137 // potentially using a 64-bit type for a 32-bit CRC. 138 uLong crc = crc32(0L, Z_NULL, 0); 139 unsigned char crcBuffer[16384]; 140 ssize_t numBytes; 141 while ((numBytes = TEMP_FAILURE_RETRY(read(fd, crcBuffer, sizeof(crcBuffer)))) > 0) { 142 crc = crc32(crc, crcBuffer, numBytes); 143 } 144 close(fd); 145 146 ALOGV("%s: crc = %lx, zipCrc = %" PRIu32 "\n", filePath, crc, zipCrc); 147 148 if (crc != static_cast<uLong>(zipCrc)) { 149 return true; 150 } 151 152 return false; 153 } 154 155 static install_status_t 156 sumFiles(JNIEnv*, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntry, const char*) 157 { 158 size_t* total = (size_t*) arg; 159 uint32_t uncompLen; 160 161 if (!zipFile->getEntryInfo(zipEntry, NULL, &uncompLen, NULL, NULL, NULL, NULL)) { 162 return INSTALL_FAILED_INVALID_APK; 163 } 164 165 *total += static_cast<size_t>(uncompLen); 166 167 return INSTALL_SUCCEEDED; 168 } 169 170 /* 171 * Copy the native library if needed. 172 * 173 * This function assumes the library and path names passed in are considered safe. 174 */ 175 static install_status_t 176 copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntry, const char* fileName) 177 { 178 void** args = reinterpret_cast<void**>(arg); 179 jstring* javaNativeLibPath = (jstring*) args[0]; 180 jboolean extractNativeLibs = *(jboolean*) args[1]; 181 jboolean hasNativeBridge = *(jboolean*) args[2]; 182 183 ScopedUtfChars nativeLibPath(env, *javaNativeLibPath); 184 185 uint32_t uncompLen; 186 uint32_t when; 187 uint32_t crc; 188 189 uint16_t method; 190 off64_t offset; 191 192 if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, NULL, &offset, &when, &crc)) { 193 ALOGD("Couldn't read zip entry info\n"); 194 return INSTALL_FAILED_INVALID_APK; 195 } 196 197 if (!extractNativeLibs) { 198 // check if library is uncompressed and page-aligned 199 if (method != ZipFileRO::kCompressStored) { 200 ALOGD("Library '%s' is compressed - will not be able to open it directly from apk.\n", 201 fileName); 202 return INSTALL_FAILED_INVALID_APK; 203 } 204 205 if (offset % PAGE_SIZE != 0) { 206 ALOGD("Library '%s' is not page-aligned - will not be able to open it directly from" 207 " apk.\n", fileName); 208 return INSTALL_FAILED_INVALID_APK; 209 } 210 211 if (!hasNativeBridge) { 212 return INSTALL_SUCCEEDED; 213 } 214 } 215 216 // Build local file path 217 const size_t fileNameLen = strlen(fileName); 218 char localFileName[nativeLibPath.size() + fileNameLen + 2]; 219 220 if (strlcpy(localFileName, nativeLibPath.c_str(), sizeof(localFileName)) != nativeLibPath.size()) { 221 ALOGD("Couldn't allocate local file name for library"); 222 return INSTALL_FAILED_INTERNAL_ERROR; 223 } 224 225 *(localFileName + nativeLibPath.size()) = '/'; 226 227 if (strlcpy(localFileName + nativeLibPath.size() + 1, fileName, sizeof(localFileName) 228 - nativeLibPath.size() - 1) != fileNameLen) { 229 ALOGD("Couldn't allocate local file name for library"); 230 return INSTALL_FAILED_INTERNAL_ERROR; 231 } 232 233 // Only copy out the native file if it's different. 234 struct tm t; 235 ZipUtils::zipTimeToTimespec(when, &t); 236 const time_t modTime = mktime(&t); 237 struct stat64 st; 238 if (!isFileDifferent(localFileName, uncompLen, modTime, crc, &st)) { 239 return INSTALL_SUCCEEDED; 240 } 241 242 char localTmpFileName[nativeLibPath.size() + TMP_FILE_PATTERN_LEN + 2]; 243 if (strlcpy(localTmpFileName, nativeLibPath.c_str(), sizeof(localTmpFileName)) 244 != nativeLibPath.size()) { 245 ALOGD("Couldn't allocate local file name for library"); 246 return INSTALL_FAILED_INTERNAL_ERROR; 247 } 248 249 *(localFileName + nativeLibPath.size()) = '/'; 250 251 if (strlcpy(localTmpFileName + nativeLibPath.size(), TMP_FILE_PATTERN, 252 TMP_FILE_PATTERN_LEN - nativeLibPath.size()) != TMP_FILE_PATTERN_LEN) { 253 ALOGI("Couldn't allocate temporary file name for library"); 254 return INSTALL_FAILED_INTERNAL_ERROR; 255 } 256 257 int fd = mkstemp(localTmpFileName); 258 if (fd < 0) { 259 ALOGI("Couldn't open temporary file name: %s: %s\n", localTmpFileName, strerror(errno)); 260 return INSTALL_FAILED_CONTAINER_ERROR; 261 } 262 263 if (!zipFile->uncompressEntry(zipEntry, fd)) { 264 ALOGI("Failed uncompressing %s to %s\n", fileName, localTmpFileName); 265 close(fd); 266 unlink(localTmpFileName); 267 return INSTALL_FAILED_CONTAINER_ERROR; 268 } 269 270 close(fd); 271 272 // Set the modification time for this file to the ZIP's mod time. 273 struct timeval times[2]; 274 times[0].tv_sec = st.st_atime; 275 times[1].tv_sec = modTime; 276 times[0].tv_usec = times[1].tv_usec = 0; 277 if (utimes(localTmpFileName, times) < 0) { 278 ALOGI("Couldn't change modification time on %s: %s\n", localTmpFileName, strerror(errno)); 279 unlink(localTmpFileName); 280 return INSTALL_FAILED_CONTAINER_ERROR; 281 } 282 283 // Set the mode to 755 284 static const mode_t mode = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; 285 if (chmod(localTmpFileName, mode) < 0) { 286 ALOGI("Couldn't change permissions on %s: %s\n", localTmpFileName, strerror(errno)); 287 unlink(localTmpFileName); 288 return INSTALL_FAILED_CONTAINER_ERROR; 289 } 290 291 // Finally, rename it to the final name. 292 if (rename(localTmpFileName, localFileName) < 0) { 293 ALOGI("Couldn't rename %s to %s: %s\n", localTmpFileName, localFileName, strerror(errno)); 294 unlink(localTmpFileName); 295 return INSTALL_FAILED_CONTAINER_ERROR; 296 } 297 298 ALOGV("Successfully moved %s to %s\n", localTmpFileName, localFileName); 299 300 return INSTALL_SUCCEEDED; 301 } 302 303 /* 304 * An iterator over all shared libraries in a zip file. An entry is 305 * considered to be a shared library if all of the conditions below are 306 * satisfied : 307 * 308 * - The entry is under the lib/ directory. 309 * - The entry name ends with ".so" and the entry name starts with "lib", 310 * an exception is made for entries whose name is "gdbserver". 311 * - The entry filename is "safe" (as determined by isFilenameSafe). 312 * 313 */ 314 class NativeLibrariesIterator { 315 private: 316 NativeLibrariesIterator(ZipFileRO* zipFile, void* cookie) 317 : mZipFile(zipFile), mCookie(cookie), mLastSlash(NULL) { 318 fileName[0] = '\0'; 319 } 320 321 public: 322 static NativeLibrariesIterator* create(ZipFileRO* zipFile) { 323 void* cookie = NULL; 324 // Do not specify a suffix to find both .so files and gdbserver. 325 if (!zipFile->startIteration(&cookie, APK_LIB, NULL /* suffix */)) { 326 return NULL; 327 } 328 329 return new NativeLibrariesIterator(zipFile, cookie); 330 } 331 332 ZipEntryRO next() { 333 ZipEntryRO next = NULL; 334 while ((next = mZipFile->nextEntry(mCookie)) != NULL) { 335 // Make sure this entry has a filename. 336 if (mZipFile->getEntryFileName(next, fileName, sizeof(fileName))) { 337 continue; 338 } 339 340 // Make sure the filename is at least to the minimum library name size. 341 const size_t fileNameLen = strlen(fileName); 342 static const size_t minLength = APK_LIB_LEN + 2 + LIB_PREFIX_LEN + 1 + LIB_SUFFIX_LEN; 343 if (fileNameLen < minLength) { 344 continue; 345 } 346 347 const char* lastSlash = strrchr(fileName, '/'); 348 ALOG_ASSERT(lastSlash != NULL, "last slash was null somehow for %s\n", fileName); 349 350 // Exception: If we find the gdbserver binary, return it. 351 if (!strncmp(lastSlash + 1, GDBSERVER, GDBSERVER_LEN)) { 352 mLastSlash = lastSlash; 353 break; 354 } 355 356 // Make sure the filename starts with lib and ends with ".so". 357 if (strncmp(fileName + fileNameLen - LIB_SUFFIX_LEN, LIB_SUFFIX, LIB_SUFFIX_LEN) 358 || strncmp(lastSlash, LIB_PREFIX, LIB_PREFIX_LEN)) { 359 continue; 360 } 361 362 // Make sure the filename is safe. 363 if (!isFilenameSafe(lastSlash + 1)) { 364 continue; 365 } 366 367 mLastSlash = lastSlash; 368 break; 369 } 370 371 return next; 372 } 373 374 inline const char* currentEntry() const { 375 return fileName; 376 } 377 378 inline const char* lastSlash() const { 379 return mLastSlash; 380 } 381 382 virtual ~NativeLibrariesIterator() { 383 mZipFile->endIteration(mCookie); 384 } 385 private: 386 387 char fileName[PATH_MAX]; 388 ZipFileRO* const mZipFile; 389 void* mCookie; 390 const char* mLastSlash; 391 }; 392 393 static install_status_t 394 iterateOverNativeFiles(JNIEnv *env, jlong apkHandle, jstring javaCpuAbi, 395 iterFunc callFunc, void* callArg) { 396 ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle); 397 if (zipFile == NULL) { 398 return INSTALL_FAILED_INVALID_APK; 399 } 400 401 UniquePtr<NativeLibrariesIterator> it(NativeLibrariesIterator::create(zipFile)); 402 if (it.get() == NULL) { 403 return INSTALL_FAILED_INVALID_APK; 404 } 405 406 const ScopedUtfChars cpuAbi(env, javaCpuAbi); 407 if (cpuAbi.c_str() == NULL) { 408 // This would've thrown, so this return code isn't observable by 409 // Java. 410 return INSTALL_FAILED_INVALID_APK; 411 } 412 ZipEntryRO entry = NULL; 413 while ((entry = it->next()) != NULL) { 414 const char* fileName = it->currentEntry(); 415 const char* lastSlash = it->lastSlash(); 416 417 // Check to make sure the CPU ABI of this file is one we support. 418 const char* cpuAbiOffset = fileName + APK_LIB_LEN; 419 const size_t cpuAbiRegionSize = lastSlash - cpuAbiOffset; 420 421 if (cpuAbi.size() == cpuAbiRegionSize && !strncmp(cpuAbiOffset, cpuAbi.c_str(), cpuAbiRegionSize)) { 422 install_status_t ret = callFunc(env, callArg, zipFile, entry, lastSlash + 1); 423 424 if (ret != INSTALL_SUCCEEDED) { 425 ALOGV("Failure for entry %s", lastSlash + 1); 426 return ret; 427 } 428 } 429 } 430 431 return INSTALL_SUCCEEDED; 432 } 433 434 435 static int findSupportedAbi(JNIEnv *env, jlong apkHandle, jobjectArray supportedAbisArray) { 436 const int numAbis = env->GetArrayLength(supportedAbisArray); 437 Vector<ScopedUtfChars*> supportedAbis; 438 439 for (int i = 0; i < numAbis; ++i) { 440 supportedAbis.add(new ScopedUtfChars(env, 441 (jstring) env->GetObjectArrayElement(supportedAbisArray, i))); 442 } 443 444 ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle); 445 if (zipFile == NULL) { 446 return INSTALL_FAILED_INVALID_APK; 447 } 448 449 UniquePtr<NativeLibrariesIterator> it(NativeLibrariesIterator::create(zipFile)); 450 if (it.get() == NULL) { 451 return INSTALL_FAILED_INVALID_APK; 452 } 453 454 ZipEntryRO entry = NULL; 455 int status = NO_NATIVE_LIBRARIES; 456 while ((entry = it->next()) != NULL) { 457 // We're currently in the lib/ directory of the APK, so it does have some native 458 // code. We should return INSTALL_FAILED_NO_MATCHING_ABIS if none of the 459 // libraries match. 460 if (status == NO_NATIVE_LIBRARIES) { 461 status = INSTALL_FAILED_NO_MATCHING_ABIS; 462 } 463 464 const char* fileName = it->currentEntry(); 465 const char* lastSlash = it->lastSlash(); 466 467 // Check to see if this CPU ABI matches what we are looking for. 468 const char* abiOffset = fileName + APK_LIB_LEN; 469 const size_t abiSize = lastSlash - abiOffset; 470 for (int i = 0; i < numAbis; i++) { 471 const ScopedUtfChars* abi = supportedAbis[i]; 472 if (abi->size() == abiSize && !strncmp(abiOffset, abi->c_str(), abiSize)) { 473 // The entry that comes in first (i.e. with a lower index) has the higher priority. 474 if (((i < status) && (status >= 0)) || (status < 0) ) { 475 status = i; 476 } 477 } 478 } 479 } 480 481 for (int i = 0; i < numAbis; ++i) { 482 delete supportedAbis[i]; 483 } 484 485 return status; 486 } 487 488 static jint 489 com_android_internal_content_NativeLibraryHelper_copyNativeBinaries(JNIEnv *env, jclass clazz, 490 jlong apkHandle, jstring javaNativeLibPath, jstring javaCpuAbi, 491 jboolean extractNativeLibs, jboolean hasNativeBridge) 492 { 493 void* args[] = { &javaNativeLibPath, &extractNativeLibs, &hasNativeBridge }; 494 return (jint) iterateOverNativeFiles(env, apkHandle, javaCpuAbi, 495 copyFileIfChanged, reinterpret_cast<void*>(args)); 496 } 497 498 static jlong 499 com_android_internal_content_NativeLibraryHelper_sumNativeBinaries(JNIEnv *env, jclass clazz, 500 jlong apkHandle, jstring javaCpuAbi) 501 { 502 size_t totalSize = 0; 503 504 iterateOverNativeFiles(env, apkHandle, javaCpuAbi, sumFiles, &totalSize); 505 506 return totalSize; 507 } 508 509 static jint 510 com_android_internal_content_NativeLibraryHelper_findSupportedAbi(JNIEnv *env, jclass clazz, 511 jlong apkHandle, jobjectArray javaCpuAbisToSearch) 512 { 513 return (jint) findSupportedAbi(env, apkHandle, javaCpuAbisToSearch); 514 } 515 516 enum bitcode_scan_result_t { 517 APK_SCAN_ERROR = -1, 518 NO_BITCODE_PRESENT = 0, 519 BITCODE_PRESENT = 1, 520 }; 521 522 static jint 523 com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode(JNIEnv *env, jclass clazz, 524 jlong apkHandle) { 525 ZipFileRO* zipFile = reinterpret_cast<ZipFileRO*>(apkHandle); 526 void* cookie = NULL; 527 if (!zipFile->startIteration(&cookie, NULL /* prefix */, RS_BITCODE_SUFFIX)) { 528 return APK_SCAN_ERROR; 529 } 530 531 char fileName[PATH_MAX]; 532 ZipEntryRO next = NULL; 533 while ((next = zipFile->nextEntry(cookie)) != NULL) { 534 if (zipFile->getEntryFileName(next, fileName, sizeof(fileName))) { 535 continue; 536 } 537 const char* lastSlash = strrchr(fileName, '/'); 538 const char* baseName = (lastSlash == NULL) ? fileName : fileName + 1; 539 if (isFilenameSafe(baseName)) { 540 zipFile->endIteration(cookie); 541 return BITCODE_PRESENT; 542 } 543 } 544 545 zipFile->endIteration(cookie); 546 return NO_BITCODE_PRESENT; 547 } 548 549 static jlong 550 com_android_internal_content_NativeLibraryHelper_openApk(JNIEnv *env, jclass, jstring apkPath) 551 { 552 ScopedUtfChars filePath(env, apkPath); 553 ZipFileRO* zipFile = ZipFileRO::open(filePath.c_str()); 554 555 return reinterpret_cast<jlong>(zipFile); 556 } 557 558 static void 559 com_android_internal_content_NativeLibraryHelper_close(JNIEnv *env, jclass, jlong apkHandle) 560 { 561 delete reinterpret_cast<ZipFileRO*>(apkHandle); 562 } 563 564 static const JNINativeMethod gMethods[] = { 565 {"nativeOpenApk", 566 "(Ljava/lang/String;)J", 567 (void *)com_android_internal_content_NativeLibraryHelper_openApk}, 568 {"nativeClose", 569 "(J)V", 570 (void *)com_android_internal_content_NativeLibraryHelper_close}, 571 {"nativeCopyNativeBinaries", 572 "(JLjava/lang/String;Ljava/lang/String;ZZ)I", 573 (void *)com_android_internal_content_NativeLibraryHelper_copyNativeBinaries}, 574 {"nativeSumNativeBinaries", 575 "(JLjava/lang/String;)J", 576 (void *)com_android_internal_content_NativeLibraryHelper_sumNativeBinaries}, 577 {"nativeFindSupportedAbi", 578 "(J[Ljava/lang/String;)I", 579 (void *)com_android_internal_content_NativeLibraryHelper_findSupportedAbi}, 580 {"hasRenderscriptBitcode", "(J)I", 581 (void *)com_android_internal_content_NativeLibraryHelper_hasRenderscriptBitcode}, 582 }; 583 584 585 int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env) 586 { 587 return RegisterMethodsOrDie(env, 588 "com/android/internal/content/NativeLibraryHelper", gMethods, NELEM(gMethods)); 589 } 590 591 }; 592