java/com/facebook/soloader/ApkSoSource.java (128 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.facebook.soloader; import android.content.Context; import android.os.Parcel; import android.util.Log; import java.io.File; import java.io.IOException; import java.util.zip.ZipEntry; /** {@link SoSource} that extracts libraries from an APK to the filesystem. */ public class ApkSoSource extends ExtractFromZipSoSource { private static final String TAG = "ApkSoSource"; /** * If this flag is given, do not extract libraries that appear to be correctly extracted to the * application libs directory. */ public static final int PREFER_ANDROID_LIBS_DIRECTORY = (1 << 0); private static final byte APK_SO_SOURCE_SIGNATURE_VERSION = 2; private static final byte LIBS_DIR_DONT_CARE = 0; private static final byte LIBS_DIR_DOESNT_EXIST = 1; private static final byte LIBS_DIR_SNAPSHOT = 2; private final int mFlags; public ApkSoSource(Context context, String name, int flags) { this(context, new File(context.getApplicationInfo().sourceDir), name, flags); } public ApkSoSource(Context context, File apkPath, String name, int flags) { super( context, name, apkPath, // The regular expression matches libraries that would ordinarily be unpacked // during installation. "^lib/([^/]+)/([^/]+\\.so)$"); mFlags = flags; } @Override protected Unpacker makeUnpacker(byte state) throws IOException { return new ApkUnpacker(this); } protected class ApkUnpacker extends ZipUnpacker { private final File mLibDir; private final int mFlags; ApkUnpacker(ExtractFromZipSoSource soSource) throws IOException { super(soSource); mLibDir = new File(mContext.getApplicationInfo().nativeLibraryDir); mFlags = ApkSoSource.this.mFlags; } @Override protected boolean shouldExtract(ZipEntry ze, String soName) { String msg = ""; boolean result = false; String zipPath = ze.getName(); if (soName.equals(mCorruptedLib)) { mCorruptedLib = null; msg = String.format("allowing consideration of corrupted lib %s", soName); result = true; } else if ((mFlags & PREFER_ANDROID_LIBS_DIRECTORY) == 0) { msg = "allowing consideration of " + zipPath + ": self-extraction preferred"; result = true; } else { boolean validPath = true; File sysLibFile = new File(mLibDir, soName); try { if (!sysLibFile.getCanonicalPath().startsWith(mLibDir.getCanonicalPath())) { validPath = false; msg = String.format( "not allowing consideration of %s: %s not in lib dir", zipPath, soName); result = false; } } catch (IOException e) { validPath = false; result = false; msg = String.format( "not allowing consideration of %s: %s, IOException when constructing path: %s", zipPath, soName, e.toString()); } if (validPath) { if (!sysLibFile.isFile()) { msg = String.format( "allowing consideration of %s: %s not in system lib dir", zipPath, soName); result = true; } else { long sysLibLength = sysLibFile.length(); long apkLibLength = ze.getSize(); if (sysLibLength != apkLibLength) { msg = String.format( "allowing consideration of %s: sysdir file length is %s, but " + "the file is %s bytes long in the APK", sysLibFile, sysLibLength, apkLibLength); result = true; } else { msg = "not allowing consideration of " + zipPath + ": deferring to libdir"; result = false; } } } } Log.d(TAG, msg); return result; } } @Override protected byte[] getDepsBlock() throws IOException { File apkFile = mZipFileName.getCanonicalFile(); Parcel parcel = Parcel.obtain(); try { parcel.writeByte(APK_SO_SOURCE_SIGNATURE_VERSION); parcel.writeString(apkFile.getPath()); parcel.writeLong(apkFile.lastModified()); parcel.writeInt(SysUtil.getAppVersionCode(mContext)); if ((mFlags & PREFER_ANDROID_LIBS_DIRECTORY) == 0) { parcel.writeByte(LIBS_DIR_DONT_CARE); return parcel.marshall(); } String nativeLibraryDir = mContext.getApplicationInfo().nativeLibraryDir; if (nativeLibraryDir == null) { parcel.writeByte(LIBS_DIR_DOESNT_EXIST); return parcel.marshall(); } File canonicalFile = new File(nativeLibraryDir).getCanonicalFile(); if (!canonicalFile.exists()) { parcel.writeByte(LIBS_DIR_DOESNT_EXIST); return parcel.marshall(); } parcel.writeByte(LIBS_DIR_SNAPSHOT); parcel.writeString(canonicalFile.getPath()); parcel.writeLong(canonicalFile.lastModified()); return parcel.marshall(); } finally { parcel.recycle(); } } }