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.pm.ApplicationInfo;
29 import android.content.pm.PackageManager;
30 import android.content.pm.PackageParser;
31 import android.content.pm.PackageParser.Package;
32 import android.content.pm.PackageParser.PackageLite;
33 import android.content.pm.PackageParser.PackageParserException;
34 import android.os.Build;
35 import android.os.SELinux;
36 import android.os.SystemProperties;
37 import android.system.ErrnoException;
38 import android.system.Os;
39 import android.util.Slog;
40 
41 import dalvik.system.CloseGuard;
42 import dalvik.system.VMRuntime;
43 
44 import java.io.Closeable;
45 import java.io.File;
46 import java.io.IOException;
47 import java.util.List;
48 
49 /**
50  * Native libraries helper.
51  *
52  * @hide
53  */
54 public class NativeLibraryHelper {
55     private static final String TAG = "NativeHelper";
56     private static final boolean DEBUG_NATIVE = false;
57 
58     public static final String LIB_DIR_NAME = "lib";
59     public static final String LIB64_DIR_NAME = "lib64";
60 
61     // Special value for {@code PackageParser.Package#cpuAbiOverride} to indicate
62     // that the cpuAbiOverride must be clear.
63     public static final String CLEAR_ABI_OVERRIDE = "-";
64 
65     /**
66      * A handle to an opened package, consisting of one or more APKs. Used as
67      * input to the various NativeLibraryHelper methods. Allows us to scan and
68      * parse the APKs exactly once instead of doing it multiple times.
69      *
70      * @hide
71      */
72     public static class Handle implements Closeable {
73         private final CloseGuard mGuard = CloseGuard.get();
74         private volatile boolean mClosed;
75 
76         final long[] apkHandles;
77         final boolean multiArch;
78         final boolean extractNativeLibs;
79 
create(File packageFile)80         public static Handle create(File packageFile) throws IOException {
81             try {
82                 final PackageLite lite = PackageParser.parsePackageLite(packageFile, 0);
83                 return create(lite);
84             } catch (PackageParserException e) {
85                 throw new IOException("Failed to parse package: " + packageFile, e);
86             }
87         }
88 
create(Package pkg)89         public static Handle create(Package pkg) throws IOException {
90             return create(pkg.getAllCodePaths(),
91                     (pkg.applicationInfo.flags & ApplicationInfo.FLAG_MULTIARCH) != 0,
92                     (pkg.applicationInfo.flags & ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS) != 0);
93         }
94 
create(PackageLite lite)95         public static Handle create(PackageLite lite) throws IOException {
96             return create(lite.getAllCodePaths(), lite.multiArch, lite.extractNativeLibs);
97         }
98 
create(List<String> codePaths, boolean multiArch, boolean extractNativeLibs)99         private static Handle create(List<String> codePaths, boolean multiArch,
100                 boolean extractNativeLibs) throws IOException {
101             final int size = codePaths.size();
102             final long[] apkHandles = new long[size];
103             for (int i = 0; i < size; i++) {
104                 final String path = codePaths.get(i);
105                 apkHandles[i] = nativeOpenApk(path);
106                 if (apkHandles[i] == 0) {
107                     // Unwind everything we've opened so far
108                     for (int j = 0; j < i; j++) {
109                         nativeClose(apkHandles[j]);
110                     }
111                     throw new IOException("Unable to open APK: " + path);
112                 }
113             }
114 
115             return new Handle(apkHandles, multiArch, extractNativeLibs);
116         }
117 
Handle(long[] apkHandles, boolean multiArch, boolean extractNativeLibs)118         Handle(long[] apkHandles, boolean multiArch, boolean extractNativeLibs) {
119             this.apkHandles = apkHandles;
120             this.multiArch = multiArch;
121             this.extractNativeLibs = extractNativeLibs;
122             mGuard.open("close");
123         }
124 
125         @Override
close()126         public void close() {
127             for (long apkHandle : apkHandles) {
128                 nativeClose(apkHandle);
129             }
130             mGuard.close();
131             mClosed = true;
132         }
133 
134         @Override
finalize()135         protected void finalize() throws Throwable {
136             if (mGuard != null) {
137                 mGuard.warnIfOpen();
138             }
139             try {
140                 if (!mClosed) {
141                     close();
142                 }
143             } finally {
144                 super.finalize();
145             }
146         }
147     }
148 
nativeOpenApk(String path)149     private static native long nativeOpenApk(String path);
nativeClose(long handle)150     private static native void nativeClose(long handle);
151 
nativeSumNativeBinaries(long handle, String cpuAbi)152     private static native long nativeSumNativeBinaries(long handle, String cpuAbi);
153 
nativeCopyNativeBinaries(long handle, String sharedLibraryPath, String abiToCopy, boolean extractNativeLibs, boolean hasNativeBridge)154     private native static int nativeCopyNativeBinaries(long handle, String sharedLibraryPath,
155             String abiToCopy, boolean extractNativeLibs, boolean hasNativeBridge);
156 
sumNativeBinaries(Handle handle, String abi)157     private static long sumNativeBinaries(Handle handle, String abi) {
158         long sum = 0;
159         for (long apkHandle : handle.apkHandles) {
160             sum += nativeSumNativeBinaries(apkHandle, abi);
161         }
162         return sum;
163     }
164 
165     /**
166      * Copies native binaries to a shared library directory.
167      *
168      * @param handle APK file to scan for native libraries
169      * @param sharedLibraryDir directory for libraries to be copied to
170      * @return {@link PackageManager#INSTALL_SUCCEEDED} if successful or another
171      *         error code from that class if not
172      */
copyNativeBinaries(Handle handle, File sharedLibraryDir, String abi)173     public static int copyNativeBinaries(Handle handle, File sharedLibraryDir, String abi) {
174         for (long apkHandle : handle.apkHandles) {
175             int res = nativeCopyNativeBinaries(apkHandle, sharedLibraryDir.getPath(), abi,
176                     handle.extractNativeLibs, HAS_NATIVE_BRIDGE);
177             if (res != INSTALL_SUCCEEDED) {
178                 return res;
179             }
180         }
181         return INSTALL_SUCCEEDED;
182     }
183 
184     /**
185      * Checks if a given APK contains native code for any of the provided
186      * {@code supportedAbis}. Returns an index into {@code supportedAbis} if a matching
187      * ABI is found, {@link PackageManager#NO_NATIVE_LIBRARIES} if the
188      * APK doesn't contain any native code, and
189      * {@link PackageManager#INSTALL_FAILED_NO_MATCHING_ABIS} if none of the ABIs match.
190      */
findSupportedAbi(Handle handle, String[] supportedAbis)191     public static int findSupportedAbi(Handle handle, String[] supportedAbis) {
192         int finalRes = NO_NATIVE_LIBRARIES;
193         for (long apkHandle : handle.apkHandles) {
194             final int res = nativeFindSupportedAbi(apkHandle, supportedAbis);
195             if (res == NO_NATIVE_LIBRARIES) {
196                 // No native code, keep looking through all APKs.
197             } else if (res == INSTALL_FAILED_NO_MATCHING_ABIS) {
198                 // Found some native code, but no ABI match; update our final
199                 // result if we haven't found other valid code.
200                 if (finalRes < 0) {
201                     finalRes = INSTALL_FAILED_NO_MATCHING_ABIS;
202                 }
203             } else if (res >= 0) {
204                 // Found valid native code, track the best ABI match
205                 if (finalRes < 0 || res < finalRes) {
206                     finalRes = res;
207                 }
208             } else {
209                 // Unexpected error; bail
210                 return res;
211             }
212         }
213         return finalRes;
214     }
215 
nativeFindSupportedAbi(long handle, String[] supportedAbis)216     private native static int nativeFindSupportedAbi(long handle, String[] supportedAbis);
217 
218     // Convenience method to call removeNativeBinariesFromDirLI(File)
removeNativeBinariesLI(String nativeLibraryPath)219     public static void removeNativeBinariesLI(String nativeLibraryPath) {
220         if (nativeLibraryPath == null) return;
221         removeNativeBinariesFromDirLI(new File(nativeLibraryPath), false /* delete root dir */);
222     }
223 
224     /**
225      * Remove the native binaries of a given package. This deletes the files
226      */
removeNativeBinariesFromDirLI(File nativeLibraryRoot, boolean deleteRootDir)227     public static void removeNativeBinariesFromDirLI(File nativeLibraryRoot,
228             boolean deleteRootDir) {
229         if (DEBUG_NATIVE) {
230             Slog.w(TAG, "Deleting native binaries from: " + nativeLibraryRoot.getPath());
231         }
232 
233         /*
234          * Just remove any file in the directory. Since the directory is owned
235          * by the 'system' UID, the application is not supposed to have written
236          * anything there.
237          */
238         if (nativeLibraryRoot.exists()) {
239             final File[] files = nativeLibraryRoot.listFiles();
240             if (files != null) {
241                 for (int nn = 0; nn < files.length; nn++) {
242                     if (DEBUG_NATIVE) {
243                         Slog.d(TAG, "    Deleting " + files[nn].getName());
244                     }
245 
246                     if (files[nn].isDirectory()) {
247                         removeNativeBinariesFromDirLI(files[nn], true /* delete root dir */);
248                     } else if (!files[nn].delete()) {
249                         Slog.w(TAG, "Could not delete native binary: " + files[nn].getPath());
250                     }
251                 }
252             }
253             // Do not delete 'lib' directory itself, unless we're specifically
254             // asked to or this will prevent installation of future updates.
255             if (deleteRootDir) {
256                 if (!nativeLibraryRoot.delete()) {
257                     Slog.w(TAG, "Could not delete native binary directory: " +
258                             nativeLibraryRoot.getPath());
259                 }
260             }
261         }
262     }
263 
createNativeLibrarySubdir(File path)264     private static void createNativeLibrarySubdir(File path) throws IOException {
265         if (!path.isDirectory()) {
266             path.delete();
267 
268             if (!path.mkdir()) {
269                 throw new IOException("Cannot create " + path.getPath());
270             }
271 
272             try {
273                 Os.chmod(path.getPath(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
274             } catch (ErrnoException e) {
275                 throw new IOException("Cannot chmod native library directory "
276                         + path.getPath(), e);
277             }
278         } else if (!SELinux.restorecon(path)) {
279             throw new IOException("Cannot set SELinux context for " + path.getPath());
280         }
281     }
282 
sumNativeBinariesForSupportedAbi(Handle handle, String[] abiList)283     private static long sumNativeBinariesForSupportedAbi(Handle handle, String[] abiList) {
284         int abi = findSupportedAbi(handle, abiList);
285         if (abi >= 0) {
286             return sumNativeBinaries(handle, abiList[abi]);
287         } else {
288             return 0;
289         }
290     }
291 
copyNativeBinariesForSupportedAbi(Handle handle, File libraryRoot, String[] abiList, boolean useIsaSubdir)292     public static int copyNativeBinariesForSupportedAbi(Handle handle, File libraryRoot,
293             String[] abiList, boolean useIsaSubdir) throws IOException {
294         createNativeLibrarySubdir(libraryRoot);
295 
296         /*
297          * If this is an internal application or our nativeLibraryPath points to
298          * the app-lib directory, unpack the libraries if necessary.
299          */
300         int abi = findSupportedAbi(handle, abiList);
301         if (abi >= 0) {
302             /*
303              * If we have a matching instruction set, construct a subdir under the native
304              * library root that corresponds to this instruction set.
305              */
306             final String instructionSet = VMRuntime.getInstructionSet(abiList[abi]);
307             final File subDir;
308             if (useIsaSubdir) {
309                 final File isaSubdir = new File(libraryRoot, instructionSet);
310                 createNativeLibrarySubdir(isaSubdir);
311                 subDir = isaSubdir;
312             } else {
313                 subDir = libraryRoot;
314             }
315 
316             int copyRet = copyNativeBinaries(handle, subDir, abiList[abi]);
317             if (copyRet != PackageManager.INSTALL_SUCCEEDED) {
318                 return copyRet;
319             }
320         }
321 
322         return abi;
323     }
324 
copyNativeBinariesWithOverride(Handle handle, File libraryRoot, String abiOverride)325     public static int copyNativeBinariesWithOverride(Handle handle, File libraryRoot,
326             String abiOverride) {
327         try {
328             if (handle.multiArch) {
329                 // Warn if we've set an abiOverride for multi-lib packages..
330                 // By definition, we need to copy both 32 and 64 bit libraries for
331                 // such packages.
332                 if (abiOverride != null && !CLEAR_ABI_OVERRIDE.equals(abiOverride)) {
333                     Slog.w(TAG, "Ignoring abiOverride for multi arch application.");
334                 }
335 
336                 int copyRet = PackageManager.NO_NATIVE_LIBRARIES;
337                 if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
338                     copyRet = copyNativeBinariesForSupportedAbi(handle, libraryRoot,
339                             Build.SUPPORTED_32_BIT_ABIS, true /* use isa specific subdirs */);
340                     if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES &&
341                             copyRet != PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS) {
342                         Slog.w(TAG, "Failure copying 32 bit native libraries; copyRet=" +copyRet);
343                         return copyRet;
344                     }
345                 }
346 
347                 if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
348                     copyRet = copyNativeBinariesForSupportedAbi(handle, libraryRoot,
349                             Build.SUPPORTED_64_BIT_ABIS, true /* use isa specific subdirs */);
350                     if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES &&
351                             copyRet != PackageManager.INSTALL_FAILED_NO_MATCHING_ABIS) {
352                         Slog.w(TAG, "Failure copying 64 bit native libraries; copyRet=" +copyRet);
353                         return copyRet;
354                     }
355                 }
356             } else {
357                 String cpuAbiOverride = null;
358                 if (CLEAR_ABI_OVERRIDE.equals(abiOverride)) {
359                     cpuAbiOverride = null;
360                 } else if (abiOverride != null) {
361                     cpuAbiOverride = abiOverride;
362                 }
363 
364                 String[] abiList = (cpuAbiOverride != null) ?
365                         new String[] { cpuAbiOverride } : Build.SUPPORTED_ABIS;
366                 if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && cpuAbiOverride == null &&
367                         hasRenderscriptBitcode(handle)) {
368                     abiList = Build.SUPPORTED_32_BIT_ABIS;
369                 }
370 
371                 int copyRet = copyNativeBinariesForSupportedAbi(handle, libraryRoot, abiList,
372                         true /* use isa specific subdirs */);
373                 if (copyRet < 0 && copyRet != PackageManager.NO_NATIVE_LIBRARIES) {
374                     Slog.w(TAG, "Failure copying native libraries [errorCode=" + copyRet + "]");
375                     return copyRet;
376                 }
377             }
378 
379             return PackageManager.INSTALL_SUCCEEDED;
380         } catch (IOException e) {
381             Slog.e(TAG, "Copying native libraries failed", e);
382             return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
383         }
384     }
385 
sumNativeBinariesWithOverride(Handle handle, String abiOverride)386     public static long sumNativeBinariesWithOverride(Handle handle, String abiOverride)
387             throws IOException {
388         long sum = 0;
389         if (handle.multiArch) {
390             // Warn if we've set an abiOverride for multi-lib packages..
391             // By definition, we need to copy both 32 and 64 bit libraries for
392             // such packages.
393             if (abiOverride != null && !CLEAR_ABI_OVERRIDE.equals(abiOverride)) {
394                 Slog.w(TAG, "Ignoring abiOverride for multi arch application.");
395             }
396 
397             if (Build.SUPPORTED_32_BIT_ABIS.length > 0) {
398                 sum += sumNativeBinariesForSupportedAbi(handle, Build.SUPPORTED_32_BIT_ABIS);
399             }
400 
401             if (Build.SUPPORTED_64_BIT_ABIS.length > 0) {
402                 sum += sumNativeBinariesForSupportedAbi(handle, Build.SUPPORTED_64_BIT_ABIS);
403             }
404         } else {
405             String cpuAbiOverride = null;
406             if (CLEAR_ABI_OVERRIDE.equals(abiOverride)) {
407                 cpuAbiOverride = null;
408             } else if (abiOverride != null) {
409                 cpuAbiOverride = abiOverride;
410             }
411 
412             String[] abiList = (cpuAbiOverride != null) ?
413                     new String[] { cpuAbiOverride } : Build.SUPPORTED_ABIS;
414             if (Build.SUPPORTED_64_BIT_ABIS.length > 0 && cpuAbiOverride == null &&
415                     hasRenderscriptBitcode(handle)) {
416                 abiList = Build.SUPPORTED_32_BIT_ABIS;
417             }
418 
419             sum += sumNativeBinariesForSupportedAbi(handle, abiList);
420         }
421         return sum;
422     }
423 
424     // We don't care about the other return values for now.
425     private static final int BITCODE_PRESENT = 1;
426 
427     private static final boolean HAS_NATIVE_BRIDGE =
428             !"0".equals(SystemProperties.get("ro.dalvik.vm.native.bridge", "0"));
429 
hasRenderscriptBitcode(long apkHandle)430     private static native int hasRenderscriptBitcode(long apkHandle);
431 
hasRenderscriptBitcode(Handle handle)432     public static boolean hasRenderscriptBitcode(Handle handle) throws IOException {
433         for (long apkHandle : handle.apkHandles) {
434             final int res = hasRenderscriptBitcode(apkHandle);
435             if (res < 0) {
436                 throw new IOException("Error scanning APK, code: " + res);
437             } else if (res == BITCODE_PRESENT) {
438                 return true;
439             }
440         }
441         return false;
442     }
443 }
444