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