1 /* 2 * Copyright (C) 2010 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 package com.android.internal.content; 18 19 import static android.content.pm.PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS; 20 import static android.content.pm.PackageManager.INSTALL_SUCCEEDED; 21 import static android.content.pm.PackageManager.NO_NATIVE_LIBRARIES; 22 import static android.system.OsConstants.S_IRGRP; 23 import static android.system.OsConstants.S_IROTH; 24 import static android.system.OsConstants.S_IRWXU; 25 import static android.system.OsConstants.S_IXGRP; 26 import static android.system.OsConstants.S_IXOTH; 27 28 import android.content.Context; 29 import android.content.pm.PackageManager; 30 import android.content.pm.PackageParser; 31 import android.content.pm.PackageParser.PackageLite; 32 import android.content.pm.PackageParser.PackageParserException; 33 import android.os.Build; 34 import android.os.IBinder; 35 import android.os.SELinux; 36 import android.os.ServiceManager; 37 import android.os.incremental.IIncrementalService; 38 import android.os.incremental.IncrementalManager; 39 import android.os.incremental.IncrementalStorage; 40 import android.system.ErrnoException; 41 import android.system.Os; 42 import android.util.ArraySet; 43 import android.util.Slog; 44 45 import dalvik.system.CloseGuard; 46 import dalvik.system.VMRuntime; 47 48 import java.io.Closeable; 49 import java.io.File; 50 import java.io.FileDescriptor; 51 import java.io.IOException; 52 import java.nio.file.Path; 53 import java.util.List; 54 55 /** 56 * Native libraries helper. 57 * 58 * @hide 59 */ 60 public class NativeLibraryHelper { 61 private static final String TAG = "NativeHelper"; 62 private static final boolean DEBUG_NATIVE = false; 63 64 public static final String LIB_DIR_NAME = "lib"; 65 public static final String LIB64_DIR_NAME = "lib64"; 66 67 // Special value for {@code PackageParser.Package#cpuAbiOverride} to indicate 68 // that the cpuAbiOverride must be clear. 69 public static final String CLEAR_ABI_OVERRIDE = "-"; 70 71 /** 72 * A handle to an opened package, consisting of one or more APKs. Used as 73 * input to the various NativeLibraryHelper methods. Allows us to scan and 74 * parse the APKs exactly once instead of doing it multiple times. 75 * 76 * @hide 77 */ 78 public static class Handle implements Closeable { 79 private final CloseGuard mGuard = CloseGuard.get(); 80 private volatile boolean mClosed; 81 82 final String[] apkPaths; 83 final long[] apkHandles; 84 final boolean multiArch; 85 final boolean extractNativeLibs; 86 final boolean debuggable; 87 create(File packageFile)88 public static Handle create(File packageFile) throws IOException { 89 try { 90 final PackageLite lite = PackageParser.parsePackageLite(packageFile, 0); 91 return create(lite); 92 } catch (PackageParserException e) { 93 throw new IOException("Failed to parse package: " + packageFile, e); 94 } 95 } 96 create(PackageLite lite)97 public static Handle create(PackageLite lite) throws IOException { 98 return create(lite.getAllCodePaths(), lite.multiArch, lite.extractNativeLibs, 99 lite.debuggable); 100 } 101 create(List<String> codePaths, boolean multiArch, boolean extractNativeLibs, boolean debuggable)102 public static Handle create(List<String> codePaths, boolean multiArch, 103 boolean extractNativeLibs, boolean debuggable) throws IOException { 104 final int size = codePaths.size(); 105 final String[] apkPaths = new String[size]; 106 final long[] apkHandles = new long[size]; 107 for (int i = 0; i < size; i++) { 108 final String path = codePaths.get(i); 109 apkPaths[i] = path; 110 apkHandles[i] = nativeOpenApk(path); 111 if (apkHandles[i] == 0) { 112 // Unwind everything we've opened so far 113 for (int j = 0; j < i; j++) { 114 nativeClose(apkHandles[j]); 115 } 116 throw new IOException("Unable to open APK: " + path); 117 } 118 } 119 120 return new Handle(apkPaths, apkHandles, multiArch, extractNativeLibs, debuggable); 121 } 122 createFd(PackageLite lite, FileDescriptor fd)123 public static Handle createFd(PackageLite lite, FileDescriptor fd) throws IOException { 124 final long[] apkHandles = new long[1]; 125 final String path = lite.baseCodePath; 126 apkHandles[0] = nativeOpenApkFd(fd, path); 127 if (apkHandles[0] == 0) { 128 throw new IOException("Unable to open APK " + path + " from fd " + fd); 129 } 130 131 return new Handle(new String[]{path}, apkHandles, lite.multiArch, 132 lite.extractNativeLibs, lite.debuggable); 133 } 134 Handle(String[] apkPaths, long[] apkHandles, boolean multiArch, boolean extractNativeLibs, boolean debuggable)135 Handle(String[] apkPaths, long[] apkHandles, boolean multiArch, 136 boolean extractNativeLibs, boolean debuggable) { 137 this.apkPaths = apkPaths; 138 this.apkHandles = apkHandles; 139 this.multiArch = multiArch; 140 this.extractNativeLibs = extractNativeLibs; 141 this.debuggable = debuggable; 142 mGuard.open("close"); 143 } 144 145 @Override close()146 public void close() { 147 for (long apkHandle : apkHandles) { 148 nativeClose(apkHandle); 149 } 150 mGuard.close(); 151 mClosed = true; 152 } 153 154 @Override finalize()155 protected void finalize() throws Throwable { 156 if (mGuard != null) { 157 mGuard.warnIfOpen(); 158 } 159 try { 160 if (!mClosed) { 161 close(); 162 } 163 } finally { 164 super.finalize(); 165 } 166 } 167 } 168 nativeOpenApk(String path)169 private static native long nativeOpenApk(String path); nativeOpenApkFd(FileDescriptor fd, String debugPath)170 private static native long nativeOpenApkFd(FileDescriptor fd, String debugPath); nativeClose(long handle)171 private static native void nativeClose(long handle); 172 nativeSumNativeBinaries(long handle, String cpuAbi, boolean debuggable)173 private static native long nativeSumNativeBinaries(long handle, String cpuAbi, 174 boolean debuggable); 175 nativeCopyNativeBinaries(long handle, String sharedLibraryPath, String abiToCopy, boolean extractNativeLibs, boolean debuggable)176 private native static int nativeCopyNativeBinaries(long handle, String sharedLibraryPath, 177 String abiToCopy, boolean extractNativeLibs, boolean debuggable); 178 sumNativeBinaries(Handle handle, String abi)179 private static long sumNativeBinaries(Handle handle, String abi) { 180 long sum = 0; 181 for (long apkHandle : handle.apkHandles) { 182 sum += nativeSumNativeBinaries(apkHandle, abi, handle.debuggable); 183 } 184 return sum; 185 } 186 187 /** 188 * Copies native binaries to a shared library directory. 189 * 190 * @param handle APK file to scan for native libraries 191 * @param sharedLibraryDir directory for libraries to be copied to 192 * @return {@link PackageManager#INSTALL_SUCCEEDED} if successful or another 193 * error code from that class if not 194 */ copyNativeBinaries(Handle handle, File sharedLibraryDir, String abi)195 public static int copyNativeBinaries(Handle handle, File sharedLibraryDir, String abi) { 196 for (long apkHandle : handle.apkHandles) { 197 int res = nativeCopyNativeBinaries(apkHandle, sharedLibraryDir.getPath(), abi, 198 handle.extractNativeLibs, handle.debuggable); 199 if (res != INSTALL_SUCCEEDED) { 200 return res; 201 } 202 } 203 return INSTALL_SUCCEEDED; 204 } 205 206 /** 207 * Checks if a given APK contains native code for any of the provided 208 * {@code supportedAbis}. Returns an index into {@code supportedAbis} if a matching 209 * ABI is found, {@link PackageManager#NO_NATIVE_LIBRARIES} if the 210 * APK doesn't contain any native code, and 211 * {@link PackageManager#INSTALL_FAILED_NO_MATCHING_ABIS} if none of the ABIs match. 212 */ findSupportedAbi(Handle handle, String[] supportedAbis)213 public static int findSupportedAbi(Handle handle, String[] supportedAbis) { 214 int finalRes = NO_NATIVE_LIBRARIES; 215 for (long apkHandle : handle.apkHandles) { 216 final int res = nativeFindSupportedAbi(apkHandle, supportedAbis, handle.debuggable); 217 if (res == NO_NATIVE_LIBRARIES) { 218 // No native code, keep looking through all APKs. 219 } else if (res == INSTALL_FAILED_NO_MATCHING_ABIS) { 220 // Found some native code, but no ABI match; update our final 221 // result if we haven't found other valid code. 222 if (finalRes < 0) { 223 finalRes = INSTALL_FAILED_NO_MATCHING_ABIS; 224 } 225 } else if (res >= 0) { 226 // Found valid native code, track the best ABI match 227 if (finalRes < 0 || res < finalRes) { 228 finalRes = res; 229 } 230 } else { 231 // Unexpected error; bail 232 return res; 233 } 234 } 235 return finalRes; 236 } 237 nativeFindSupportedAbi(long handle, String[] supportedAbis, boolean debuggable)238 private native static int nativeFindSupportedAbi(long handle, String[] supportedAbis, 239 boolean debuggable); 240 241 // Convenience method to call removeNativeBinariesFromDirLI(File) removeNativeBinariesLI(String nativeLibraryPath)242 public static void removeNativeBinariesLI(String nativeLibraryPath) { 243 if (nativeLibraryPath == null) return; 244 removeNativeBinariesFromDirLI(new File(nativeLibraryPath), false /* delete root dir */); 245 } 246 247 /** 248 * Remove the native binaries of a given package. This deletes the files 249 */ removeNativeBinariesFromDirLI(File nativeLibraryRoot, boolean deleteRootDir)250 public static void removeNativeBinariesFromDirLI(File nativeLibraryRoot, 251 boolean deleteRootDir) { 252 if (DEBUG_NATIVE) { 253 Slog.w(TAG, "Deleting native binaries from: " + nativeLibraryRoot.getPath()); 254 } 255 256 /* 257 * Just remove any file in the directory. Since the directory is owned 258 * by the 'system' UID, the application is not supposed to have written 259 * anything there. 260 */ 261 if (nativeLibraryRoot.exists()) { 262 final File[] files = nativeLibraryRoot.listFiles(); 263 if (files != null) { 264 for (int nn = 0; nn < files.length; nn++) { 265 if (DEBUG_NATIVE) { 266 Slog.d(TAG, " Deleting " + files[nn].getName()); 267 } 268 269 if (files[nn].isDirectory()) { 270 removeNativeBinariesFromDirLI(files[nn], true /* delete root dir */); 271 } else if (!files[nn].delete()) { 272 Slog.w(TAG, "Could not delete native binary: " + files[nn].getPath()); 273 } 274 } 275 } 276 // Do not delete 'lib' directory itself, unless we're specifically 277 // asked to or this will prevent installation of future updates. 278 if (deleteRootDir) { 279 if (!nativeLibraryRoot.delete()) { 280 Slog.w(TAG, "Could not delete native binary directory: " + 281 nativeLibraryRoot.getPath()); 282 } 283 } 284 } 285 } 286 287 /** 288 * @hide 289 */ createNativeLibrarySubdir(File path)290 public static void createNativeLibrarySubdir(File path) throws IOException { 291 if (!path.isDirectory()) { 292 path.delete(); 293 294 if (!path.mkdir()) { 295 throw new IOException("Cannot create " + path.getPath()); 296 } 297 298 try { 299 Os.chmod(path.getPath(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); 300 } catch (ErrnoException e) { 301 throw new IOException("Cannot chmod native library directory " 302 + path.getPath(), e); 303 } 304 } else if (!SELinux.restorecon(path)) { 305 throw new IOException("Cannot set SELinux context for " + path.getPath()); 306 } 307 } 308 sumNativeBinariesForSupportedAbi(Handle handle, String[] abiList)309 private static long sumNativeBinariesForSupportedAbi(Handle handle, String[] abiList) { 310 int abi = findSupportedAbi(handle, abiList); 311 if (abi >= 0) { 312 return sumNativeBinaries(handle, abiList[abi]); 313 } else { 314 return 0; 315 } 316 } 317 copyNativeBinariesForSupportedAbi(Handle handle, File libraryRoot, String[] abiList, boolean useIsaSubdir, boolean isIncremental)318 public static int copyNativeBinariesForSupportedAbi(Handle handle, File libraryRoot, 319 String[] abiList, boolean useIsaSubdir, boolean isIncremental) throws IOException { 320 /* 321 * If this is an internal application or our nativeLibraryPath points to 322 * the app-lib directory, unpack the libraries if necessary. 323 */ 324 int abi = findSupportedAbi(handle, abiList); 325 if (abi < 0) { 326 return abi; 327 } 328 329 /* 330 * If we have a matching instruction set, construct a subdir under the native 331 * library root that corresponds to this instruction set. 332 */ 333 final String supportedAbi = abiList[abi]; 334 final String instructionSet = VMRuntime.getInstructionSet(supportedAbi); 335 final File subDir; 336 if (useIsaSubdir) { 337 subDir = new File(libraryRoot, instructionSet); 338 } else { 339 subDir = libraryRoot; 340 } 341 342 if (isIncremental) { 343 int res = 344 incrementalConfigureNativeBinariesForSupportedAbi(handle, subDir, supportedAbi); 345 if (res != PackageManager.INSTALL_SUCCEEDED) { 346 // TODO(b/133435829): the caller of this function expects that we return the index 347 // to the supported ABI. However, any non-negative integer can be a valid index. 348 // We should fix this function and make sure it doesn't accidentally return an error 349 // code that can also be a valid index. 350 return res; 351 } 352 return abi; 353 } 354 355 // For non-incremental, use regular extraction and copy 356 createNativeLibrarySubdir(libraryRoot); 357 if (subDir != libraryRoot) { 358 createNativeLibrarySubdir(subDir); 359 } 360 361 int copyRet = copyNativeBinaries(handle, subDir, supportedAbi); 362 if (copyRet != PackageManager.INSTALL_SUCCEEDED) { 363 return copyRet; 364 } 365 366 return abi; 367 } 368 copyNativeBinariesWithOverride(Handle handle, File libraryRoot, String abiOverride, boolean isIncremental)369 public static int copyNativeBinariesWithOverride(Handle handle, File libraryRoot, 370 String abiOverride, boolean isIncremental) { 371 try { 372 if (handle.multiArch) { 373 // Warn if we've set an abiOverride for multi-lib packages.. 374 // By definition, we need to copy both 32 and 64 bit libraries for 375 // such packages. 376 if (abiOverride != null && !CLEAR_ABI_OVERRIDE.equals(abiOverride)) { 377 Slog.w(TAG, "Ignoring abiOverride for multi arch application."); 378 } 379 380 int copyRet = PackageManager.NO_NATIVE_LIBRARIES; 381 if (Build.SUPPORTED_32_BIT_ABIS.length > 0) { 382 copyRet = copyNativeBinariesForSupportedAbi(handle, libraryRoot, 383 Build.SUPPORTED_32_BIT_ABIS, true /* use isa specific subdirs */, 384 isIncremental); 385 if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES && 386 copyRet != PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS) { 387 Slog.w(TAG, "Failure copying 32 bit native libraries; copyRet=" +copyRet); 388 return copyRet; 389 } 390 } 391 392 if (Build.SUPPORTED_64_BIT_ABIS.length > 0) { 393 copyRet = copyNativeBinariesForSupportedAbi(handle, libraryRoot, 394 Build.SUPPORTED_64_BIT_ABIS, true /* use isa specific subdirs */, 395 isIncremental); 396 if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES && 397 copyRet != PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS) { 398 Slog.w(TAG, "Failure copying 64 bit native libraries; copyRet=" +copyRet); 399 return copyRet; 400 } 401 } 402 } else { 403 String cpuAbiOverride = null; 404 if (CLEAR_ABI_OVERRIDE.equals(abiOverride)) { 405 cpuAbiOverride = null; 406 } else if (abiOverride != null) { 407 cpuAbiOverride = abiOverride; 408 } 409 410 String[] abiList = (cpuAbiOverride != null) ? 411 new String[] { cpuAbiOverride } : Build.SUPPORTED_ABIS; 412 if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && cpuAbiOverride == null && 413 hasRenderscriptBitcode(handle)) { 414 abiList = Build.SUPPORTED_32_BIT_ABIS; 415 } 416 417 int copyRet = copyNativeBinariesForSupportedAbi(handle, libraryRoot, abiList, 418 true /* use isa specific subdirs */, isIncremental); 419 if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) { 420 Slog.w(TAG, "Failure copying native libraries [errorCode=" + copyRet + "]"); 421 return copyRet; 422 } 423 } 424 425 return PackageManager.INSTALL_SUCCEEDED; 426 } catch (IOException e) { 427 Slog.e(TAG, "Copying native libraries failed", e); 428 return PackageManager.INSTALL_FAILED_INTERNAL_ERROR; 429 } 430 } 431 sumNativeBinariesWithOverride(Handle handle, String abiOverride)432 public static long sumNativeBinariesWithOverride(Handle handle, String abiOverride) 433 throws IOException { 434 long sum = 0; 435 if (handle.multiArch) { 436 // Warn if we've set an abiOverride for multi-lib packages.. 437 // By definition, we need to copy both 32 and 64 bit libraries for 438 // such packages. 439 if (abiOverride != null && !CLEAR_ABI_OVERRIDE.equals(abiOverride)) { 440 Slog.w(TAG, "Ignoring abiOverride for multi arch application."); 441 } 442 443 if (Build.SUPPORTED_32_BIT_ABIS.length > 0) { 444 sum += sumNativeBinariesForSupportedAbi(handle, Build.SUPPORTED_32_BIT_ABIS); 445 } 446 447 if (Build.SUPPORTED_64_BIT_ABIS.length > 0) { 448 sum += sumNativeBinariesForSupportedAbi(handle, Build.SUPPORTED_64_BIT_ABIS); 449 } 450 } else { 451 String cpuAbiOverride = null; 452 if (CLEAR_ABI_OVERRIDE.equals(abiOverride)) { 453 cpuAbiOverride = null; 454 } else if (abiOverride != null) { 455 cpuAbiOverride = abiOverride; 456 } 457 458 String[] abiList = (cpuAbiOverride != null) ? 459 new String[] { cpuAbiOverride } : Build.SUPPORTED_ABIS; 460 if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && cpuAbiOverride == null && 461 hasRenderscriptBitcode(handle)) { 462 abiList = Build.SUPPORTED_32_BIT_ABIS; 463 } 464 465 sum += sumNativeBinariesForSupportedAbi(handle, abiList); 466 } 467 return sum; 468 } 469 470 /** 471 * Configure the native library files managed by Incremental Service. Makes sure Incremental 472 * Service will create native library directories and set up native library binary files in the 473 * same structure as they are in non-incremental installations. 474 * 475 * @param handle The Handle object that contains all apk paths. 476 * @param libSubDir The target directory to put the native library files, e.g., lib/ or lib/arm 477 * @param abi The abi that is supported by the current device. 478 * @return Integer code if installation succeeds or fails. 479 */ incrementalConfigureNativeBinariesForSupportedAbi(Handle handle, File libSubDir, String abi)480 private static int incrementalConfigureNativeBinariesForSupportedAbi(Handle handle, 481 File libSubDir, String abi) { 482 final String[] apkPaths = handle.apkPaths; 483 if (apkPaths == null || apkPaths.length == 0) { 484 Slog.e(TAG, "No apks to extract native libraries from."); 485 return PackageManager.INSTALL_FAILED_INTERNAL_ERROR; 486 } 487 488 final IBinder incrementalService = ServiceManager.getService(Context.INCREMENTAL_SERVICE); 489 if (incrementalService == null) { 490 //TODO(b/133435829): add incremental specific error codes 491 return PackageManager.INSTALL_FAILED_INTERNAL_ERROR; 492 } 493 final IncrementalManager incrementalManager = new IncrementalManager( 494 IIncrementalService.Stub.asInterface(incrementalService)); 495 final File apkParent = new File(apkPaths[0]).getParentFile(); 496 IncrementalStorage incrementalStorage = 497 incrementalManager.openStorage(apkParent.getAbsolutePath()); 498 if (incrementalStorage == null) { 499 Slog.e(TAG, "Failed to find incremental storage"); 500 return PackageManager.INSTALL_FAILED_INTERNAL_ERROR; 501 } 502 503 String libRelativeDir = getRelativePath(apkParent, libSubDir); 504 if (libRelativeDir == null) { 505 return PackageManager.INSTALL_FAILED_INTERNAL_ERROR; 506 } 507 508 for (int i = 0; i < apkPaths.length; i++) { 509 if (!incrementalStorage.configureNativeBinaries(apkPaths[i], libRelativeDir, abi, 510 handle.extractNativeLibs)) { 511 return PackageManager.INSTALL_FAILED_INTERNAL_ERROR; 512 } 513 } 514 return PackageManager.INSTALL_SUCCEEDED; 515 } 516 getRelativePath(File base, File target)517 private static String getRelativePath(File base, File target) { 518 try { 519 final Path basePath = base.toPath(); 520 final Path targetPath = target.toPath(); 521 final Path relativePath = basePath.relativize(targetPath); 522 if (relativePath.toString().isEmpty()) { 523 return ""; 524 } 525 return relativePath.toString(); 526 } catch (IllegalArgumentException ex) { 527 Slog.e(TAG, "Failed to find relative path between: " + base.getAbsolutePath() 528 + " and: " + target.getAbsolutePath()); 529 return null; 530 } 531 } 532 533 // We don't care about the other return values for now. 534 private static final int BITCODE_PRESENT = 1; 535 hasRenderscriptBitcode(long apkHandle)536 private static native int hasRenderscriptBitcode(long apkHandle); 537 hasRenderscriptBitcode(Handle handle)538 public static boolean hasRenderscriptBitcode(Handle handle) throws IOException { 539 for (long apkHandle : handle.apkHandles) { 540 final int res = hasRenderscriptBitcode(apkHandle); 541 if (res < 0) { 542 throw new IOException("Error scanning APK, code: " + res); 543 } else if (res == BITCODE_PRESENT) { 544 return true; 545 } 546 } 547 return false; 548 } 549 550 /** 551 * Wait for all native library extraction to complete for the passed storages. 552 * 553 * @param incrementalStorages A list of the storages to wait for. 554 */ waitForNativeBinariesExtraction( ArraySet<IncrementalStorage> incrementalStorages)555 public static void waitForNativeBinariesExtraction( 556 ArraySet<IncrementalStorage> incrementalStorages) { 557 for (int i = 0; i < incrementalStorages.size(); ++i) { 558 IncrementalStorage storage = incrementalStorages.valueAtUnchecked(i); 559 storage.waitForNativeBinariesExtraction(); 560 } 561 } 562 563 } 564