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