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