Bug 1529082 - Locate lib folder within the APK. r=owlish

Before this patch, we would load mozglue using the unqualified
`System.loadLibrary("mozglue")` and then we would try to load all the other
native libraries from the path returned from `nativeLibraryDir`.

Note that since the path is unqualified, we might end up loading a library
that's not inside the APK, potentially a malicious one.

Android App Bundles (or AAB) is a new feature of Android where apps can be
split in multiple APKs (called "splits"). Each APK split contains a functinal
part of the app, e.g. a specific resolution asset folder, all the native
libraries for one CPU architecture etc.

For AAB, `nativeLibraryDir` returns an empty folder as the libraries are
stored, uncompressed, inside one of the APK splits, so GeckoView fails to load
libxul and other libraries when starting up.

The current code also doesn't work for any case where `System.loadLibrary`
fails to load mozglue (we do have code that accounts for that, but I'm not sure
how much of it is funcitonal).

To fix that, we locate the mozglue using code ported from Chromium and use that
to locate all the other native libraries.

Note this still doesn't fix the problem of loading an unqualified mozglue
library, but there's not a lot we can do about that right now (and that's not a
new problem, so we can address it in a separate patch).

Differential Revision: https://phabricator.services.mozilla.com/D122909
This commit is contained in:
Agi Sferro 2021-11-02 18:56:25 +00:00
Родитель a11f894d94
Коммит 088243109c
1 изменённых файлов: 44 добавлений и 73 удалений

Просмотреть файл

@ -5,11 +5,11 @@
package org.mozilla.gecko.mozglue;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import dalvik.system.BaseDexClassLoader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
@ -188,9 +188,45 @@ public final class GeckoLoader {
loadLibsSetupLocked(context);
}
// Adapted from
// https://source.chromium.org/chromium/chromium/src/+/main:base/android/java/src/org/chromium/base/BundleUtils.java;l=196;drc=c0fedddd4a1444653235912cfae3d44b544ded01
private static String getLibraryPath(final String libraryName) {
// Due to b/171269960 isolated split class loaders have an empty library path, so check
// the base module class loader first which loaded GeckoAppShell. If the library is not
// found there, attempt to construct the correct library path from the split.
String path =
((BaseDexClassLoader) GeckoAppShell.class.getClassLoader()).findLibrary(libraryName);
if (path != null) {
return path;
}
// SplitCompat is installed on the application context, so check there for library paths
// which were added to that ClassLoader.
final ClassLoader classLoader = GeckoAppShell.getApplicationContext().getClassLoader();
if (classLoader instanceof BaseDexClassLoader) {
path = ((BaseDexClassLoader) classLoader).findLibrary(libraryName);
if (path != null) {
return path;
}
}
throw new RuntimeException("Could not find mozglue path.");
}
private static String getLibraryBase() {
final String mozglue = getLibraryPath("mozglue");
final int lastSlash = mozglue.lastIndexOf('/');
if (lastSlash < 0) {
throw new IllegalStateException("Invalid library path for libmozglue.so: " + mozglue);
}
final String base = mozglue.substring(0, lastSlash);
Log.i(LOGTAG, "Library base=" + base);
return base;
}
private static void loadLibsSetupLocked(final Context context) {
putenv("GRE_HOME=" + getGREDir(context).getPath());
putenv("MOZ_ANDROID_LIBDIR=" + context.getApplicationInfo().nativeLibraryDir);
putenv("MOZ_ANDROID_LIBDIR=" + getLibraryBase());
}
@RobocopTarget
@ -385,92 +421,27 @@ public final class GeckoLoader {
*
* <p>Returns null or the cause exception.
*/
private static Throwable doLoadLibraryExpected(final Context context, final String lib) {
public static Throwable doLoadLibrary(final Context context, final String lib) {
try {
// Attempt 1: the way that should work.
System.loadLibrary(lib);
return null;
} catch (final Throwable e) {
Log.wtf(LOGTAG, "Couldn't load " + lib + ". Trying native library dir.");
// Attempt 2: use nativeLibraryDir, which should also work.
final String libDir = context.getApplicationInfo().nativeLibraryDir;
final String libPath = libDir + "/lib" + lib + ".so";
final String libPath = getLibraryPath(lib);
// Does it even exist?
if (new File(libPath).exists()) {
if (attemptLoad(libPath)) {
// Success!
return null;
}
Log.wtf(LOGTAG, "Library exists but couldn't load!");
} else {
Log.wtf(LOGTAG, "Library doesn't exist when it should.");
throw new RuntimeException(
"Library exists but couldn't load." + "Path: " + libPath + " lib: " + lib, e);
}
// We failed. Return the original cause.
return e;
throw new RuntimeException(
"Library doesn't exist when it should." + "Path: " + libPath + " lib: " + lib, e);
}
}
@SuppressLint("SdCardPath")
public static void doLoadLibrary(final Context context, final String lib) {
final Throwable e = doLoadLibraryExpected(context, lib);
if (e == null) {
// Success.
return;
}
// If we're in a mismatched UID state (Bug 1042935 Comment 16) there's really
// nothing we can do.
final String nativeLibPath = context.getApplicationInfo().nativeLibraryDir;
if (nativeLibPath.contains("mismatched_uid")) {
throw new RuntimeException("Fatal: mismatched UID: cannot load.");
}
// Attempt 3: try finding the path the pseudo-supported way using .dataDir.
final String dataLibPath = context.getApplicationInfo().dataDir + "/lib/lib" + lib + ".so";
if (attemptLoad(dataLibPath)) {
return;
}
// Attempt 4: use /data/app-lib directly. This is a last-ditch effort.
final String androidPackageName = context.getPackageName();
if (attemptLoad("/data/app-lib/" + androidPackageName + "/lib" + lib + ".so")) {
return;
}
// Attempt 5: even more optimistic.
if (attemptLoad("/data/data/" + androidPackageName + "/lib/lib" + lib + ".so")) {
return;
}
// Look in our files directory, copying from the APK first if necessary.
final String filesLibDir = context.getFilesDir() + "/lib";
final String filesLibPath = filesLibDir + "/lib" + lib + ".so";
if (new File(filesLibPath).exists()) {
if (attemptLoad(filesLibPath)) {
return;
}
} else {
// Try copying.
if (extractLibrary(context, lib, filesLibDir)) {
// Let's try it!
if (attemptLoad(filesLibPath)) {
return;
}
}
}
// Give up loudly, leaking information to debug the failure.
final String message = getLoadDiagnostics(context, lib);
Log.e(LOGTAG, "Load diagnostics: " + message);
// Throw the descriptive message, using the original library load
// failure as the cause.
throw new RuntimeException(message, e);
}
public static synchronized void loadMozGlue(final Context context) {
if (sMozGlueLoaded) {
return;