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