diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 2f4f2715f2b63..df83d11b2a261 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -4788,7 +4788,7 @@ public class ComputerEngine implements Computer { @Override public List findSharedNonSystemLibraries( @NonNull PackageStateInternal pkgSetting) { - List deps = SharedLibraryHelper.findSharedLibraries(pkgSetting); + List deps = SharedLibraryUtils.findSharedLibraries(pkgSetting); if (!deps.isEmpty()) { List retValue = new ArrayList<>(); for (SharedLibraryInfo info : deps) { diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java index eac38af57784d..dcad3ecba6aff 100644 --- a/services/core/java/com/android/server/pm/DexOptHelper.java +++ b/services/core/java/com/android/server/pm/DexOptHelper.java @@ -379,7 +379,7 @@ final class DexOptHelper { // at boot, or background job), the passed 'targetCompilerFilter' stays the same, // and the first package that uses the library will dexopt it. The // others will see that the compiled code for the library is up to date. - Collection deps = SharedLibraryHelper.findSharedLibraries(pkgSetting); + Collection deps = SharedLibraryUtils.findSharedLibraries(pkgSetting); final String[] instructionSets = getAppDexInstructionSets( AndroidPackageUtils.getPrimaryCpuAbi(p, pkgSetting), AndroidPackageUtils.getSecondaryCpuAbi(p, pkgSetting)); diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 27b6282055a28..9302aaddcdb24 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -894,8 +894,6 @@ final class InstallPackageHelper { final Map installResults = new ArrayMap<>(requests.size()); final Map prepareResults = new ArrayMap<>(requests.size()); final Map versionInfos = new ArrayMap<>(requests.size()); - final Map lastStaticSharedLibSettings = - new ArrayMap<>(requests.size()); final Map createdAppId = new ArrayMap<>(requests.size()); boolean success = false; try { @@ -955,35 +953,22 @@ final class InstallPackageHelper { createdAppId.put(packageName, optimisticallyRegisterAppId(result)); versionInfos.put(result.mPkgSetting.getPkg().getPackageName(), mPm.getSettingsVersionForPackage(result.mPkgSetting.getPkg())); - if (result.mStaticSharedLibraryInfo != null) { - final PackageSetting staticSharedLibLatestVersionSetting = - mSharedLibraries.getStaticSharedLibLatestVersionSetting(result); - if (staticSharedLibLatestVersionSetting != null) { - lastStaticSharedLibSettings.put( - result.mPkgSetting.getPkg().getPackageName(), - staticSharedLibLatestVersionSetting); - } - } } catch (PackageManagerException e) { request.mInstallResult.setError("Scanning Failed.", e); return; } } - ReconcileRequest - reconcileRequest = new ReconcileRequest(preparedScans, installArgs, - installResults, - prepareResults, - mSharedLibraries.getAll(), - Collections.unmodifiableMap(mPm.mPackages), versionInfos, - lastStaticSharedLibSettings); + ReconcileRequest reconcileRequest = new ReconcileRequest(preparedScans, installArgs, + installResults, prepareResults, + Collections.unmodifiableMap(mPm.mPackages), versionInfos); CommitRequest commitRequest = null; synchronized (mPm.mLock) { Map reconciledPackages; try { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "reconcilePackages"); reconciledPackages = ReconcilePackageUtils.reconcilePackages( - reconcileRequest, mPm.mSettings.getKeySetManagerService(), - mPm.mInjector); + reconcileRequest, mSharedLibraries, + mPm.mSettings.getKeySetManagerService()); } catch (ReconcileFailure e) { for (InstallRequest request : requests) { request.mInstallResult.setError("Reconciliation failed...", e); @@ -3586,15 +3571,12 @@ final class InstallPackageHelper { final String pkgName = scanResult.mPkgSetting.getPackageName(); final ReconcileRequest reconcileRequest = new ReconcileRequest( Collections.singletonMap(pkgName, scanResult), - mSharedLibraries.getAll(), mPm.mPackages, + mPm.mPackages, Collections.singletonMap(pkgName, - mPm.getSettingsVersionForPackage(parsedPackage)), - Collections.singletonMap(pkgName, - mSharedLibraries.getStaticSharedLibLatestVersionSetting( - scanResult))); + mPm.getSettingsVersionForPackage(parsedPackage))); final Map reconcileResult = ReconcilePackageUtils.reconcilePackages(reconcileRequest, - mPm.mSettings.getKeySetManagerService(), mPm.mInjector); + mSharedLibraries, mPm.mSettings.getKeySetManagerService()); appIdCreated = optimisticallyRegisterAppId(scanResult); commitReconciledScanResultLocked(reconcileResult.get(pkgName), mPm.mUserManager.getUserIds()); diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java index 5a250047da8f3..67f6b123d99bd 100644 --- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java +++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java @@ -42,8 +42,8 @@ import java.util.Map; final class ReconcilePackageUtils { public static Map reconcilePackages( - final ReconcileRequest request, KeySetManagerService ksms, - PackageManagerServiceInjector injector) + final ReconcileRequest request, SharedLibrariesImpl sharedLibraries, + KeySetManagerService ksms) throws ReconcileFailure { final Map scannedPackages = request.mScannedPackages; @@ -67,11 +67,10 @@ final class ReconcilePackageUtils { // in the first pass, we'll build up the set of incoming shared libraries final List allowedSharedLibInfos = - SharedLibraryHelper.getAllowedSharedLibInfos(scanResult, - request.mSharedLibrarySource); + sharedLibraries.getAllowedSharedLibInfos(scanResult); if (allowedSharedLibInfos != null) { for (SharedLibraryInfo info : allowedSharedLibInfos) { - if (!SharedLibraryHelper.addSharedLibraryToPackageVersionMap( + if (!SharedLibraryUtils.addSharedLibraryToPackageVersionMap( incomingSharedLibraries, info)) { throw new ReconcileFailure("Shared Library " + info.getName() + " is being installed twice in this set!"); @@ -113,7 +112,8 @@ final class ReconcilePackageUtils { final PackageSetting disabledPkgSetting = scanResult.mRequest.mDisabledPkgSetting; final PackageSetting lastStaticSharedLibSetting = - request.mLastStaticSharedLibSettings.get(installPackageName); + scanResult.mStaticSharedLibraryInfo == null ? null + : sharedLibraries.getStaticSharedLibLatestVersionSetting(scanResult); final PackageSetting signatureCheckPs = (prepareResult != null && lastStaticSharedLibSetting != null) ? lastStaticSharedLibSetting @@ -264,11 +264,9 @@ final class ReconcilePackageUtils { } try { result.get(installPackageName).mCollectedSharedLibraryInfos = - SharedLibraryHelper.collectSharedLibraryInfos( - scanResult.mRequest.mParsedPackage, - combinedPackages, request.mSharedLibrarySource, - incomingSharedLibraries, injector.getCompatibility()); - + sharedLibraries.collectSharedLibraryInfos( + scanResult.mRequest.mParsedPackage, combinedPackages, + incomingSharedLibraries); } catch (PackageManagerException e) { throw new ReconcileFailure(e.error, e.getMessage()); } diff --git a/services/core/java/com/android/server/pm/ReconcileRequest.java b/services/core/java/com/android/server/pm/ReconcileRequest.java index 31881388e6ec0..9e4e986d1fb55 100644 --- a/services/core/java/com/android/server/pm/ReconcileRequest.java +++ b/services/core/java/com/android/server/pm/ReconcileRequest.java @@ -16,10 +16,7 @@ package com.android.server.pm; -import android.content.pm.SharedLibraryInfo; - import com.android.server.pm.parsing.pkg.AndroidPackage; -import com.android.server.utils.WatchedLongSparseArray; import java.util.Collections; import java.util.Map; @@ -37,38 +34,29 @@ final class ReconcileRequest { public final Map mScannedPackages; public final Map mAllPackages; - public final Map> mSharedLibrarySource; public final Map mInstallArgs; public final Map mInstallResults; public final Map mPreparedPackages; public final Map mVersionInfos; - public final Map mLastStaticSharedLibSettings; ReconcileRequest(Map scannedPackages, Map installArgs, Map installResults, Map preparedPackages, - Map> sharedLibrarySource, Map allPackages, - Map versionInfos, - Map lastStaticSharedLibSettings) { + Map versionInfos) { mScannedPackages = scannedPackages; mInstallArgs = installArgs; mInstallResults = installResults; mPreparedPackages = preparedPackages; - mSharedLibrarySource = sharedLibrarySource; mAllPackages = allPackages; mVersionInfos = versionInfos; - mLastStaticSharedLibSettings = lastStaticSharedLibSettings; } ReconcileRequest(Map scannedPackages, - Map> sharedLibrarySource, Map allPackages, - Map versionInfos, - Map lastStaticSharedLibSettings) { + Map versionInfos) { this(scannedPackages, Collections.emptyMap(), Collections.emptyMap(), - Collections.emptyMap(), sharedLibrarySource, allPackages, versionInfos, - lastStaticSharedLibSettings); + Collections.emptyMap(), allPackages, versionInfos); } } diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java index 0055f4eb6446a..aa230508287ec 100644 --- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java +++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java @@ -16,19 +16,27 @@ package com.android.server.pm; +import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY; + import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; import static com.android.server.pm.PackageManagerService.TAG; import android.annotation.NonNull; import android.annotation.Nullable; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledAfter; import android.content.pm.PackageManager; import android.content.pm.SharedLibraryInfo; +import android.content.pm.Signature; +import android.content.pm.SigningDetails; import android.content.pm.VersionedPackage; +import android.os.Build; import android.os.Process; import android.os.UserHandle; import android.os.storage.StorageManager; import android.service.pm.PackageServiceDumpProto; import android.util.ArraySet; +import android.util.PackageUtils; import android.util.Pair; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -36,8 +44,10 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; import com.android.server.SystemConfig; +import com.android.server.compat.PlatformCompat; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; +import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.utils.Snappable; import com.android.server.utils.SnapshotCache; import com.android.server.utils.Watchable; @@ -47,10 +57,13 @@ import com.android.server.utils.WatchedArrayMap; import com.android.server.utils.WatchedLongSparseArray; import com.android.server.utils.Watcher; +import libcore.util.HexEncoding; + import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; @@ -62,6 +75,24 @@ import java.util.function.BiConsumer; * Current known shared libraries on the device. */ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable, Snappable { + private static final boolean DEBUG_SHARED_LIBRARIES = false; + + /** + * Apps targeting Android S and above need to declare dependencies to the public native + * shared libraries that are defined by the device maker using {@code uses-native-library} tag + * in its {@code AndroidManifest.xml}. + * + * If any of the dependencies cannot be satisfied, i.e. one of the dependency doesn't exist, + * the package manager rejects to install the app. The dependency can be specified as optional + * using {@code android:required} attribute in the tag, in which case failing to satisfy the + * dependency doesn't stop the installation. + *

Once installed, an app is provided with only the native shared libraries that are + * specified in the app manifest. {@code dlopen}ing a native shared library that doesn't appear + * in the app manifest will fail even if it actually exists on the device. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R) + private static final long ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES = 142191088; // TODO(b/200588896): remove PMS dependency private final PackageManagerService mPm; @@ -493,10 +524,8 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable @Nullable AndroidPackage changingLib, @Nullable PackageSetting changingLibSetting, @NonNull Map availablePackages) throws PackageManagerException { - final ArrayList sharedLibraryInfos = - SharedLibraryHelper.collectSharedLibraryInfos( - pkgSetting.getPkg(), availablePackages, mSharedLibraries, - null /* newLibraries */, mInjector.getCompatibility()); + final ArrayList sharedLibraryInfos = collectSharedLibraryInfos( + pkgSetting.getPkg(), availablePackages, null /* newLibraries */); executeSharedLibrariesUpdateLPw(pkg, pkgSetting, changingLib, changingLibSetting, sharedLibraryInfos, mPm.mUserManager.getUserIds()); } @@ -734,6 +763,234 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable return true; } + /** + * Compare the newly scanned package with current system state to see which of its declared + * shared libraries should be allowed to be added to the system. + */ + List getAllowedSharedLibInfos(ScanResult scanResult) { + // Let's used the parsed package as scanResult.pkgSetting may be null + final ParsedPackage parsedPackage = scanResult.mRequest.mParsedPackage; + if (scanResult.mSdkSharedLibraryInfo == null && scanResult.mStaticSharedLibraryInfo == null + && scanResult.mDynamicSharedLibraryInfos == null) { + return null; + } + + // Any app can add new SDKs and static shared libraries. + if (scanResult.mSdkSharedLibraryInfo != null) { + return Collections.singletonList(scanResult.mSdkSharedLibraryInfo); + } + if (scanResult.mStaticSharedLibraryInfo != null) { + return Collections.singletonList(scanResult.mStaticSharedLibraryInfo); + } + final boolean hasDynamicLibraries = parsedPackage.isSystem() + && scanResult.mDynamicSharedLibraryInfos != null; + if (!hasDynamicLibraries) { + return null; + } + final boolean isUpdatedSystemApp = scanResult.mPkgSetting.getPkgState() + .isUpdatedSystemApp(); + // We may not yet have disabled the updated package yet, so be sure to grab the + // current setting if that's the case. + final PackageSetting updatedSystemPs = isUpdatedSystemApp + ? scanResult.mRequest.mDisabledPkgSetting == null + ? scanResult.mRequest.mOldPkgSetting + : scanResult.mRequest.mDisabledPkgSetting + : null; + if (isUpdatedSystemApp && (updatedSystemPs.getPkg() == null + || updatedSystemPs.getPkg().getLibraryNames() == null)) { + Slog.w(TAG, "Package " + parsedPackage.getPackageName() + + " declares libraries that are not declared on the system image; skipping"); + return null; + } + final ArrayList infos = + new ArrayList<>(scanResult.mDynamicSharedLibraryInfos.size()); + for (SharedLibraryInfo info : scanResult.mDynamicSharedLibraryInfos) { + final String name = info.getName(); + if (isUpdatedSystemApp) { + // New library entries can only be added through the + // system image. This is important to get rid of a lot + // of nasty edge cases: for example if we allowed a non- + // system update of the app to add a library, then uninstalling + // the update would make the library go away, and assumptions + // we made such as through app install filtering would now + // have allowed apps on the device which aren't compatible + // with it. Better to just have the restriction here, be + // conservative, and create many fewer cases that can negatively + // impact the user experience. + if (!updatedSystemPs.getPkg().getLibraryNames().contains(name)) { + Slog.w(TAG, "Package " + parsedPackage.getPackageName() + + " declares library " + name + + " that is not declared on system image; skipping"); + continue; + } + } + synchronized (mPm.mLock) { + if (getSharedLibraryInfo(name, SharedLibraryInfo.VERSION_UNDEFINED) != null) { + Slog.w(TAG, "Package " + parsedPackage.getPackageName() + " declares library " + + name + " that already exists; skipping"); + continue; + } + } + infos.add(info); + } + return infos; + } + + /** + * Collects shared library infos that are being used by the given package. + * + * @param pkg The package using shared libraries. + * @param availablePackages The available packages which are installed and being installed, + * @param newLibraries Shared libraries defined by packages which are being installed. + * @return A list of shared library infos + */ + ArrayList collectSharedLibraryInfos(@Nullable AndroidPackage pkg, + @NonNull Map availablePackages, + @Nullable final Map> newLibraries) + throws PackageManagerException { + if (pkg == null) { + return null; + } + final PlatformCompat platformCompat = mInjector.getCompatibility(); + // The collection used here must maintain the order of addition (so + // that libraries are searched in the correct order) and must have no + // duplicates. + ArrayList usesLibraryInfos = null; + if (!pkg.getUsesLibraries().isEmpty()) { + usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesLibraries(), null, null, + pkg.getPackageName(), "shared", true, pkg.getTargetSdkVersion(), null, + availablePackages, newLibraries); + } + if (!pkg.getUsesStaticLibraries().isEmpty()) { + usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesStaticLibraries(), + pkg.getUsesStaticLibrariesVersions(), pkg.getUsesStaticLibrariesCertDigests(), + pkg.getPackageName(), "static shared", true, pkg.getTargetSdkVersion(), + usesLibraryInfos, availablePackages, newLibraries); + } + if (!pkg.getUsesOptionalLibraries().isEmpty()) { + usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalLibraries(), null, null, + pkg.getPackageName(), "shared", false, pkg.getTargetSdkVersion(), + usesLibraryInfos, availablePackages, newLibraries); + } + if (platformCompat.isChangeEnabledInternal(ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES, + pkg.getPackageName(), pkg.getTargetSdkVersion())) { + if (!pkg.getUsesNativeLibraries().isEmpty()) { + usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesNativeLibraries(), null, + null, pkg.getPackageName(), "native shared", true, + pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, + newLibraries); + } + if (!pkg.getUsesOptionalNativeLibraries().isEmpty()) { + usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalNativeLibraries(), + null, null, pkg.getPackageName(), "native shared", false, + pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, + newLibraries); + } + } + if (!pkg.getUsesSdkLibraries().isEmpty()) { + usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesSdkLibraries(), + pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesCertDigests(), + pkg.getPackageName(), "sdk", true, pkg.getTargetSdkVersion(), usesLibraryInfos, + availablePackages, newLibraries); + } + return usesLibraryInfos; + } + + private ArrayList collectSharedLibraryInfos( + @NonNull List requestedLibraries, + @Nullable long[] requiredVersions, @Nullable String[][] requiredCertDigests, + @NonNull String packageName, @NonNull String libraryType, boolean required, + int targetSdk, @Nullable ArrayList outUsedLibraries, + @NonNull final Map availablePackages, + @Nullable final Map> newLibraries) + throws PackageManagerException { + final int libCount = requestedLibraries.size(); + for (int i = 0; i < libCount; i++) { + final String libName = requestedLibraries.get(i); + final long libVersion = requiredVersions != null ? requiredVersions[i] + : SharedLibraryInfo.VERSION_UNDEFINED; + final SharedLibraryInfo libraryInfo; + synchronized (mPm.mLock) { + libraryInfo = SharedLibraryUtils.getSharedLibraryInfo( + libName, libVersion, mSharedLibraries, newLibraries); + } + if (libraryInfo == null) { + if (required) { + throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY, + "Package " + packageName + " requires unavailable " + libraryType + + " library " + libName + "; failing!"); + } else if (DEBUG_SHARED_LIBRARIES) { + Slog.i(TAG, "Package " + packageName + " desires unavailable " + libraryType + + " library " + libName + "; ignoring!"); + } + } else { + if (requiredVersions != null && requiredCertDigests != null) { + if (libraryInfo.getLongVersion() != requiredVersions[i]) { + throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY, + "Package " + packageName + " requires unavailable " + libraryType + + " library " + libName + " version " + + libraryInfo.getLongVersion() + "; failing!"); + } + AndroidPackage pkg = availablePackages.get(libraryInfo.getPackageName()); + SigningDetails libPkg = pkg == null ? null : pkg.getSigningDetails(); + if (libPkg == null) { + throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY, + "Package " + packageName + " requires unavailable " + libraryType + + " library; failing!"); + } + final String[] expectedCertDigests = requiredCertDigests[i]; + if (expectedCertDigests.length > 1) { + // For apps targeting O MR1 we require explicit enumeration of all certs. + final String[] libCertDigests = (targetSdk >= Build.VERSION_CODES.O_MR1) + ? PackageUtils.computeSignaturesSha256Digests( + libPkg.getSignatures()) + : PackageUtils.computeSignaturesSha256Digests( + new Signature[]{libPkg.getSignatures()[0]}); + + // Take a shortcut if sizes don't match. Note that if an app doesn't + // target O we don't parse the "additional-certificate" tags similarly + // how we only consider all certs only for apps targeting O (see above). + // Therefore, the size check is safe to make. + if (expectedCertDigests.length != libCertDigests.length) { + throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY, + "Package " + packageName + " requires differently signed " + + libraryType + " library; failing!"); + } + + // Use a predictable order as signature order may vary + Arrays.sort(libCertDigests); + Arrays.sort(expectedCertDigests); + + final int certCount = libCertDigests.length; + for (int j = 0; j < certCount; j++) { + if (!libCertDigests[j].equalsIgnoreCase(expectedCertDigests[j])) { + throw new PackageManagerException( + INSTALL_FAILED_MISSING_SHARED_LIBRARY, + "Package " + packageName + " requires differently signed " + + libraryType + " library; failing!"); + } + } + } else { + // lib signing cert could have rotated beyond the one expected, check to see + // if the new one has been blessed by the old + byte[] digestBytes = HexEncoding.decode( + expectedCertDigests[0], false /* allowSingleChar */); + if (!libPkg.hasSha256Certificate(digestBytes)) { + throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY, + "Package " + packageName + " requires differently signed " + + libraryType + " library; failing!"); + } + } + } + if (outUsedLibraries == null) { + outUsedLibraries = new ArrayList<>(); + } + outUsedLibraries.add(libraryInfo); + } + } + return outUsedLibraries; + } + /** * Dump all shared libraries. */ diff --git a/services/core/java/com/android/server/pm/SharedLibraryHelper.java b/services/core/java/com/android/server/pm/SharedLibraryHelper.java deleted file mode 100644 index dd8fad0970fa4..0000000000000 --- a/services/core/java/com/android/server/pm/SharedLibraryHelper.java +++ /dev/null @@ -1,366 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * 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.android.server.pm; - -import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY; - -import static com.android.server.pm.PackageManagerService.TAG; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.compat.annotation.ChangeId; -import android.compat.annotation.EnabledAfter; -import android.content.pm.SharedLibraryInfo; -import android.content.pm.Signature; -import android.content.pm.SigningDetails; -import android.os.Build; -import android.util.PackageUtils; -import android.util.Slog; - -import com.android.server.compat.PlatformCompat; -import com.android.server.pm.parsing.pkg.AndroidPackage; -import com.android.server.pm.parsing.pkg.ParsedPackage; -import com.android.server.pm.pkg.PackageStateInternal; -import com.android.server.utils.WatchedLongSparseArray; - -import libcore.util.HexEncoding; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -final class SharedLibraryHelper { - private static final boolean DEBUG_SHARED_LIBRARIES = false; - - /** - * Apps targeting Android S and above need to declare dependencies to the public native - * shared libraries that are defined by the device maker using {@code uses-native-library} tag - * in its {@code AndroidManifest.xml}. - * - * If any of the dependencies cannot be satisfied, i.e. one of the dependency doesn't exist, - * the package manager rejects to install the app. The dependency can be specified as optional - * using {@code android:required} attribute in the tag, in which case failing to satisfy the - * dependency doesn't stop the installation. - *

Once installed, an app is provided with only the native shared libraries that are - * specified in the app manifest. {@code dlopen}ing a native shared library that doesn't appear - * in the app manifest will fail even if it actually exists on the device. - */ - @ChangeId - @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R) - private static final long ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES = 142191088; - - /** - * Compare the newly scanned package with current system state to see which of its declared - * shared libraries should be allowed to be added to the system. - */ - public static List getAllowedSharedLibInfos( - ScanResult scanResult, - Map> existingSharedLibraries) { - // Let's used the parsed package as scanResult.pkgSetting may be null - final ParsedPackage parsedPackage = scanResult.mRequest.mParsedPackage; - if (scanResult.mSdkSharedLibraryInfo == null && scanResult.mStaticSharedLibraryInfo == null - && scanResult.mDynamicSharedLibraryInfos == null) { - return null; - } - - // Any app can add new SDKs and static shared libraries. - if (scanResult.mSdkSharedLibraryInfo != null) { - return Collections.singletonList(scanResult.mSdkSharedLibraryInfo); - } - if (scanResult.mStaticSharedLibraryInfo != null) { - return Collections.singletonList(scanResult.mStaticSharedLibraryInfo); - } - final boolean hasDynamicLibraries = parsedPackage.isSystem() - && scanResult.mDynamicSharedLibraryInfos != null; - if (!hasDynamicLibraries) { - return null; - } - final boolean isUpdatedSystemApp = scanResult.mPkgSetting.getPkgState() - .isUpdatedSystemApp(); - // We may not yet have disabled the updated package yet, so be sure to grab the - // current setting if that's the case. - final PackageSetting updatedSystemPs = isUpdatedSystemApp - ? scanResult.mRequest.mDisabledPkgSetting == null - ? scanResult.mRequest.mOldPkgSetting - : scanResult.mRequest.mDisabledPkgSetting - : null; - if (isUpdatedSystemApp && (updatedSystemPs.getPkg() == null - || updatedSystemPs.getPkg().getLibraryNames() == null)) { - Slog.w(TAG, "Package " + parsedPackage.getPackageName() - + " declares libraries that are not declared on the system image; skipping"); - return null; - } - final ArrayList infos = - new ArrayList<>(scanResult.mDynamicSharedLibraryInfos.size()); - for (SharedLibraryInfo info : scanResult.mDynamicSharedLibraryInfos) { - final String name = info.getName(); - if (isUpdatedSystemApp) { - // New library entries can only be added through the - // system image. This is important to get rid of a lot - // of nasty edge cases: for example if we allowed a non- - // system update of the app to add a library, then uninstalling - // the update would make the library go away, and assumptions - // we made such as through app install filtering would now - // have allowed apps on the device which aren't compatible - // with it. Better to just have the restriction here, be - // conservative, and create many fewer cases that can negatively - // impact the user experience. - if (!updatedSystemPs.getPkg().getLibraryNames().contains(name)) { - Slog.w(TAG, "Package " + parsedPackage.getPackageName() - + " declares library " + name - + " that is not declared on system image; skipping"); - continue; - } - } - if (sharedLibExists( - name, SharedLibraryInfo.VERSION_UNDEFINED, existingSharedLibraries)) { - Slog.w(TAG, "Package " + parsedPackage.getPackageName() + " declares library " - + name + " that already exists; skipping"); - continue; - } - infos.add(info); - } - return infos; - } - - public static boolean sharedLibExists(final String name, final long version, - Map> librarySource) { - WatchedLongSparseArray versionedLib = librarySource.get(name); - return versionedLib != null && versionedLib.indexOfKey(version) >= 0; - } - - /** - * Returns false if the adding shared library already exists in the map and so could not be - * added. - */ - public static boolean addSharedLibraryToPackageVersionMap( - Map> target, - SharedLibraryInfo library) { - final String name = library.getName(); - if (target.containsKey(name)) { - if (library.getType() != SharedLibraryInfo.TYPE_STATIC) { - // We've already added this non-version-specific library to the map. - return false; - } else if (target.get(name).indexOfKey(library.getLongVersion()) >= 0) { - // We've already added this version of a version-specific library to the map. - return false; - } - } else { - target.put(name, new WatchedLongSparseArray<>()); - } - target.get(name).put(library.getLongVersion(), library); - return true; - } - - public static ArrayList collectSharedLibraryInfos(AndroidPackage pkg, - Map availablePackages, - @NonNull final Map> existingLibraries, - @Nullable final Map> newLibraries, - PlatformCompat platformCompat) throws PackageManagerException { - if (pkg == null) { - return null; - } - // The collection used here must maintain the order of addition (so - // that libraries are searched in the correct order) and must have no - // duplicates. - ArrayList usesLibraryInfos = null; - if (!pkg.getUsesLibraries().isEmpty()) { - usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesLibraries(), null, null, - pkg.getPackageName(), "shared", true, pkg.getTargetSdkVersion(), null, - availablePackages, existingLibraries, newLibraries); - } - if (!pkg.getUsesStaticLibraries().isEmpty()) { - usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesStaticLibraries(), - pkg.getUsesStaticLibrariesVersions(), pkg.getUsesStaticLibrariesCertDigests(), - pkg.getPackageName(), "static shared", true, pkg.getTargetSdkVersion(), - usesLibraryInfos, availablePackages, existingLibraries, newLibraries); - } - if (!pkg.getUsesOptionalLibraries().isEmpty()) { - usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalLibraries(), null, null, - pkg.getPackageName(), "shared", false, pkg.getTargetSdkVersion(), - usesLibraryInfos, availablePackages, existingLibraries, newLibraries); - } - if (platformCompat.isChangeEnabledInternal(ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES, - pkg.getPackageName(), pkg.getTargetSdkVersion())) { - if (!pkg.getUsesNativeLibraries().isEmpty()) { - usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesNativeLibraries(), null, - null, pkg.getPackageName(), "native shared", true, - pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, - existingLibraries, newLibraries); - } - if (!pkg.getUsesOptionalNativeLibraries().isEmpty()) { - usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalNativeLibraries(), - null, null, pkg.getPackageName(), "native shared", false, - pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, - existingLibraries, newLibraries); - } - } - if (!pkg.getUsesSdkLibraries().isEmpty()) { - usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesSdkLibraries(), - pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesCertDigests(), - pkg.getPackageName(), "sdk", true, pkg.getTargetSdkVersion(), usesLibraryInfos, - availablePackages, existingLibraries, newLibraries); - } - return usesLibraryInfos; - } - - public static ArrayList collectSharedLibraryInfos( - @NonNull List requestedLibraries, - @Nullable long[] requiredVersions, @Nullable String[][] requiredCertDigests, - @NonNull String packageName, @NonNull String libraryType, boolean required, - int targetSdk, @Nullable ArrayList outUsedLibraries, - @NonNull final Map availablePackages, - @NonNull final Map> existingLibraries, - @Nullable final Map> newLibraries) - throws PackageManagerException { - final int libCount = requestedLibraries.size(); - for (int i = 0; i < libCount; i++) { - final String libName = requestedLibraries.get(i); - final long libVersion = requiredVersions != null ? requiredVersions[i] - : SharedLibraryInfo.VERSION_UNDEFINED; - final SharedLibraryInfo libraryInfo = - getSharedLibraryInfo(libName, libVersion, existingLibraries, newLibraries); - if (libraryInfo == null) { - if (required) { - throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY, - "Package " + packageName + " requires unavailable " + libraryType - + " library " + libName + "; failing!"); - } else if (DEBUG_SHARED_LIBRARIES) { - Slog.i(TAG, "Package " + packageName + " desires unavailable " + libraryType - + " library " + libName + "; ignoring!"); - } - } else { - if (requiredVersions != null && requiredCertDigests != null) { - if (libraryInfo.getLongVersion() != requiredVersions[i]) { - throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY, - "Package " + packageName + " requires unavailable " + libraryType - + " library " + libName + " version " - + libraryInfo.getLongVersion() + "; failing!"); - } - AndroidPackage pkg = availablePackages.get(libraryInfo.getPackageName()); - SigningDetails libPkg = pkg == null ? null : pkg.getSigningDetails(); - if (libPkg == null) { - throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY, - "Package " + packageName + " requires unavailable " + libraryType - + " library; failing!"); - } - final String[] expectedCertDigests = requiredCertDigests[i]; - if (expectedCertDigests.length > 1) { - // For apps targeting O MR1 we require explicit enumeration of all certs. - final String[] libCertDigests = (targetSdk >= Build.VERSION_CODES.O_MR1) - ? PackageUtils.computeSignaturesSha256Digests( - libPkg.getSignatures()) - : PackageUtils.computeSignaturesSha256Digests( - new Signature[]{libPkg.getSignatures()[0]}); - - // Take a shortcut if sizes don't match. Note that if an app doesn't - // target O we don't parse the "additional-certificate" tags similarly - // how we only consider all certs only for apps targeting O (see above). - // Therefore, the size check is safe to make. - if (expectedCertDigests.length != libCertDigests.length) { - throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY, - "Package " + packageName + " requires differently signed " - + libraryType + " library; failing!"); - } - - // Use a predictable order as signature order may vary - Arrays.sort(libCertDigests); - Arrays.sort(expectedCertDigests); - - final int certCount = libCertDigests.length; - for (int j = 0; j < certCount; j++) { - if (!libCertDigests[j].equalsIgnoreCase(expectedCertDigests[j])) { - throw new PackageManagerException( - INSTALL_FAILED_MISSING_SHARED_LIBRARY, - "Package " + packageName + " requires differently signed " - + libraryType + " library; failing!"); - } - } - } else { - // lib signing cert could have rotated beyond the one expected, check to see - // if the new one has been blessed by the old - byte[] digestBytes = HexEncoding.decode( - expectedCertDigests[0], false /* allowSingleChar */); - if (!libPkg.hasSha256Certificate(digestBytes)) { - throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY, - "Package " + packageName + " requires differently signed " - + libraryType + " library; failing!"); - } - } - } - if (outUsedLibraries == null) { - outUsedLibraries = new ArrayList<>(); - } - outUsedLibraries.add(libraryInfo); - } - } - return outUsedLibraries; - } - - @Nullable - public static SharedLibraryInfo getSharedLibraryInfo(String name, long version, - Map> existingLibraries, - @Nullable Map> newLibraries) { - if (newLibraries != null) { - final WatchedLongSparseArray versionedLib = newLibraries.get(name); - SharedLibraryInfo info = null; - if (versionedLib != null) { - info = versionedLib.get(version); - } - if (info != null) { - return info; - } - } - final WatchedLongSparseArray versionedLib = existingLibraries.get(name); - if (versionedLib == null) { - return null; - } - return versionedLib.get(version); - } - - public static List findSharedLibraries(PackageStateInternal pkgSetting) { - if (!pkgSetting.getTransientState().getUsesLibraryInfos().isEmpty()) { - ArrayList retValue = new ArrayList<>(); - Set collectedNames = new HashSet<>(); - for (SharedLibraryInfo info : pkgSetting.getTransientState().getUsesLibraryInfos()) { - findSharedLibrariesRecursive(info, retValue, collectedNames); - } - return retValue; - } else { - return Collections.emptyList(); - } - } - - private static void findSharedLibrariesRecursive(SharedLibraryInfo info, - ArrayList collected, Set collectedNames) { - if (!collectedNames.contains(info.getName())) { - collectedNames.add(info.getName()); - collected.add(info); - - if (info.getDependencies() != null) { - for (SharedLibraryInfo dep : info.getDependencies()) { - findSharedLibrariesRecursive(dep, collected, collectedNames); - } - } - } - } - -} diff --git a/services/core/java/com/android/server/pm/SharedLibraryUtils.java b/services/core/java/com/android/server/pm/SharedLibraryUtils.java new file mode 100644 index 0000000000000..274870dad6f06 --- /dev/null +++ b/services/core/java/com/android/server/pm/SharedLibraryUtils.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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.android.server.pm; + +import android.annotation.Nullable; +import android.content.pm.SharedLibraryInfo; + +import com.android.server.pm.pkg.PackageStateInternal; +import com.android.server.utils.WatchedLongSparseArray; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +final class SharedLibraryUtils { + + /** + * Returns false if the adding shared library already exists in the map and so could not be + * added. + */ + public static boolean addSharedLibraryToPackageVersionMap( + Map> target, + SharedLibraryInfo library) { + final String name = library.getName(); + if (target.containsKey(name)) { + if (library.getType() != SharedLibraryInfo.TYPE_STATIC) { + // We've already added this non-version-specific library to the map. + return false; + } else if (target.get(name).indexOfKey(library.getLongVersion()) >= 0) { + // We've already added this version of a version-specific library to the map. + return false; + } + } else { + target.put(name, new WatchedLongSparseArray<>()); + } + target.get(name).put(library.getLongVersion(), library); + return true; + } + + @Nullable + public static SharedLibraryInfo getSharedLibraryInfo(String name, long version, + Map> existingLibraries, + @Nullable Map> newLibraries) { + if (newLibraries != null) { + final WatchedLongSparseArray versionedLib = newLibraries.get(name); + SharedLibraryInfo info = null; + if (versionedLib != null) { + info = versionedLib.get(version); + } + if (info != null) { + return info; + } + } + final WatchedLongSparseArray versionedLib = existingLibraries.get(name); + if (versionedLib == null) { + return null; + } + return versionedLib.get(version); + } + + public static List findSharedLibraries(PackageStateInternal pkgSetting) { + if (!pkgSetting.getTransientState().getUsesLibraryInfos().isEmpty()) { + ArrayList retValue = new ArrayList<>(); + Set collectedNames = new HashSet<>(); + for (SharedLibraryInfo info : pkgSetting.getTransientState().getUsesLibraryInfos()) { + findSharedLibrariesRecursive(info, retValue, collectedNames); + } + return retValue; + } else { + return Collections.emptyList(); + } + } + + private static void findSharedLibrariesRecursive(SharedLibraryInfo info, + ArrayList collected, Set collectedNames) { + if (!collectedNames.contains(info.getName())) { + collectedNames.add(info.getName()); + collected.add(info); + + if (info.getDependencies() != null) { + for (SharedLibraryInfo dep : info.getDependencies()) { + findSharedLibrariesRecursive(dep, collected, collectedNames); + } + } + } + } + +} diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt index 44a8b3001a710..04a6eeecb320b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt @@ -74,6 +74,7 @@ import com.android.server.testutils.mock import com.android.server.testutils.nullable import com.android.server.testutils.whenever import com.android.server.utils.WatchedArrayMap +import libcore.util.HexEncoding import org.junit.Assert import org.junit.rules.TestRule import org.junit.runner.Description @@ -140,6 +141,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { .mockStatic(EventLog::class.java) .mockStatic(LocalServices::class.java) .mockStatic(DeviceConfig::class.java) + .mockStatic(HexEncoding::class.java) .apply(withSession) session = apply.startMocking() whenever(mocks.settings.insertPackageSettingLPw( diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt new file mode 100644 index 0000000000000..6de12cb987198 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt @@ -0,0 +1,449 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * 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.android.server.pm + +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.content.pm.SharedLibraryInfo +import android.content.pm.VersionedPackage +import android.os.Build +import android.os.storage.StorageManager +import android.util.ArrayMap +import android.util.PackageUtils +import com.android.server.SystemConfig.SharedLibraryEntry +import com.android.server.compat.PlatformCompat +import com.android.server.extendedtestutils.wheneverStatic +import com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME +import com.android.server.pm.parsing.pkg.AndroidPackage +import com.android.server.pm.parsing.pkg.PackageImpl +import com.android.server.pm.parsing.pkg.ParsedPackage +import com.android.server.testutils.any +import com.android.server.testutils.eq +import com.android.server.testutils.mock +import com.android.server.testutils.nullable +import com.android.server.testutils.spy +import com.android.server.testutils.whenever +import com.android.server.utils.WatchedLongSparseArray +import com.google.common.truth.Truth.assertThat +import libcore.util.HexEncoding +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.doAnswer +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import java.io.File +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +@RunWith(JUnit4::class) +class SharedLibrariesImplTest { + + companion object { + const val TEST_LIB_NAME = "test.lib" + const val TEST_LIB_PACKAGE_NAME = "com.android.lib.test" + const val BUILTIN_LIB_NAME = "builtin.lib" + const val STATIC_LIB_NAME = "static.lib" + const val STATIC_LIB_VERSION = 7L + const val STATIC_LIB_PACKAGE_NAME = "com.android.lib.static.provider" + const val DYNAMIC_LIB_NAME = "dynamic.lib" + const val DYNAMIC_LIB_PACKAGE_NAME = "com.android.lib.dynamic.provider" + const val CONSUMER_PACKAGE_NAME = "com.android.lib.consumer" + const val VERSION_UNDEFINED = SharedLibraryInfo.VERSION_UNDEFINED.toLong() + } + + @Rule + @JvmField + val mRule = MockSystemRule() + + private val mExistingPackages: ArrayMap = ArrayMap() + private val mExistingSettings: MutableMap = mutableMapOf() + + private lateinit var mSharedLibrariesImpl: SharedLibrariesImpl + private lateinit var mPms: PackageManagerService + private lateinit var mSettings: Settings + + @Mock + private lateinit var mDeletePackageHelper: DeletePackageHelper + @Mock + private lateinit var mStorageManager: StorageManager + @Mock + private lateinit var mFile: File + @Mock + private lateinit var mPlatformCompat: PlatformCompat + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + mRule.system().stageNominalSystemState() + addExistingPackages() + + val testParams = PackageManagerServiceTestParams().apply { + packages = mExistingPackages + } + mPms = spy(PackageManagerService(mRule.mocks().injector, testParams)) + mSettings = mRule.mocks().injector.settings + mSharedLibrariesImpl = SharedLibrariesImpl(mPms, mRule.mocks().injector) + mSharedLibrariesImpl.setDeletePackageHelper(mDeletePackageHelper) + addExistingSharedLibraries() + + whenever(mSettings.getPackageLPr(any())) { mExistingSettings[arguments[0]] } + whenever(mRule.mocks().injector.getSystemService(StorageManager::class.java)) + .thenReturn(mStorageManager) + whenever(mStorageManager.findPathForUuid(nullable())).thenReturn(mFile) + doAnswer { it.arguments[0] }.`when`(mPms).resolveInternalPackageNameLPr(any(), any()) + whenever(mDeletePackageHelper.deletePackageX(any(), any(), any(), any(), any())) + .thenReturn(PackageManager.DELETE_SUCCEEDED) + whenever(mRule.mocks().injector.compatibility).thenReturn(mPlatformCompat) + wheneverStatic { HexEncoding.decode(STATIC_LIB_NAME, false) } + .thenReturn(PackageUtils.computeSha256DigestBytes( + mExistingSettings[STATIC_LIB_PACKAGE_NAME]!! + .pkg.signingDetails.signatures!![0].toByteArray())) + } + + @Test + fun snapshot_shouldSealed() { + val builtinLibs = mSharedLibrariesImpl.snapshot().all[BUILTIN_LIB_NAME] + assertThat(builtinLibs).isNotNull() + + assertFailsWith(IllegalStateException::class) { + mSharedLibrariesImpl.snapshot().all[BUILTIN_LIB_NAME] = WatchedLongSparseArray() + } + assertFailsWith(IllegalStateException::class) { + builtinLibs!!.put(VERSION_UNDEFINED, libOfBuiltin(BUILTIN_LIB_NAME)) + } + } + + @Test + fun addBuiltInSharedLibrary() { + mSharedLibrariesImpl.addBuiltInSharedLibraryLPw(libEntry(TEST_LIB_NAME)) + + assertThat(mSharedLibrariesImpl.getSharedLibraryInfos(TEST_LIB_NAME)).isNotNull() + assertThat(mSharedLibrariesImpl.getSharedLibraryInfo(TEST_LIB_NAME, VERSION_UNDEFINED)) + .isNotNull() + } + + @Test + fun addBuiltInSharedLibrary_withDuplicateLibName() { + val duplicate = libEntry(BUILTIN_LIB_NAME, "duplicate.path") + mSharedLibrariesImpl.addBuiltInSharedLibraryLPw(duplicate) + val sharedLibInfo = mSharedLibrariesImpl + .getSharedLibraryInfo(BUILTIN_LIB_NAME, VERSION_UNDEFINED) + + assertThat(sharedLibInfo).isNotNull() + assertThat(sharedLibInfo!!.path).isNotEqualTo(duplicate.filename) + } + + @Test + fun commitSharedLibraryInfo_withStaticSharedLib() { + val testInfo = libOfStatic(TEST_LIB_PACKAGE_NAME, TEST_LIB_NAME, 1L) + mSharedLibrariesImpl.commitSharedLibraryInfoLPw(testInfo) + val sharedLibInfos = mSharedLibrariesImpl + .getStaticLibraryInfos(testInfo.declaringPackage.packageName) + + assertThat(mSharedLibrariesImpl.getSharedLibraryInfos(TEST_LIB_NAME)) + .isNotNull() + assertThat(mSharedLibrariesImpl.getSharedLibraryInfo(testInfo.name, testInfo.longVersion)) + .isNotNull() + assertThat(sharedLibInfos).isNotNull() + assertThat(sharedLibInfos.get(testInfo.longVersion)).isNotNull() + } + + @Test + fun removeSharedLibrary() { + doAnswer { mutableListOf(VersionedPackage(CONSUMER_PACKAGE_NAME, 1L)) }.`when`(mPms) + .getPackagesUsingSharedLibrary(any(), any(), any(), any()) + val staticInfo = mSharedLibrariesImpl + .getSharedLibraryInfo(STATIC_LIB_NAME, STATIC_LIB_VERSION)!! + + mSharedLibrariesImpl.removeSharedLibraryLPw(STATIC_LIB_NAME, STATIC_LIB_VERSION) + + assertThat(mSharedLibrariesImpl.getSharedLibraryInfos(STATIC_LIB_NAME)).isNull() + assertThat(mSharedLibrariesImpl + .getStaticLibraryInfos(staticInfo.declaringPackage.packageName)).isNull() + verify(mExistingSettings[CONSUMER_PACKAGE_NAME]!!) + .setOverlayPathsForLibrary(any(), nullable(), any()) + } + + @Test + fun pruneUnusedStaticSharedLibraries() { + mSharedLibrariesImpl.pruneUnusedStaticSharedLibraries(Long.MAX_VALUE, 0) + + verify(mDeletePackageHelper) + .deletePackageX(eq(STATIC_LIB_PACKAGE_NAME), any(), any(), any(), any()) + } + + @Test + fun getLatestSharedLibraVersion() { + val newLibSetting = addPackage(STATIC_LIB_PACKAGE_NAME + "_" + 10, 10L, + staticLibrary = STATIC_LIB_NAME, staticLibraryVersion = 10L) + + val latestInfo = mSharedLibrariesImpl.getLatestSharedLibraVersionLPr(newLibSetting.pkg)!! + + assertThat(latestInfo).isNotNull() + assertThat(latestInfo.name).isEqualTo(STATIC_LIB_NAME) + assertThat(latestInfo.longVersion).isEqualTo(STATIC_LIB_VERSION) + } + + @Test + fun getStaticSharedLibLatestVersionSetting() { + val pair = createBasicAndroidPackage(STATIC_LIB_PACKAGE_NAME + "_" + 10, 10L, + staticLibrary = STATIC_LIB_NAME, staticLibraryVersion = 10L) + val parsedPackage = pair.second as ParsedPackage + val scanRequest = ScanRequest(parsedPackage, null, null, null, + null, null, null, 0, 0, false, null, null) + val scanResult = ScanResult(scanRequest, true, null, null, false, 0, null, null, null) + + val latestInfoSetting = + mSharedLibrariesImpl.getStaticSharedLibLatestVersionSetting(scanResult)!! + + assertThat(latestInfoSetting).isNotNull() + assertThat(latestInfoSetting.packageName).isEqualTo(STATIC_LIB_PACKAGE_NAME) + } + + @Test + fun updateSharedLibraries_withDynamicLibPackage() { + val testPackageSetting = mExistingSettings[DYNAMIC_LIB_PACKAGE_NAME]!! + assertThat(testPackageSetting.usesLibraryFiles).isEmpty() + + mSharedLibrariesImpl.updateSharedLibrariesLPw(testPackageSetting.pkg, testPackageSetting, + null /* changingLib */, null /* changingLibSetting */, mExistingPackages) + + assertThat(testPackageSetting.usesLibraryFiles).hasSize(1) + assertThat(testPackageSetting.usesLibraryFiles).contains(builtinLibPath(BUILTIN_LIB_NAME)) + } + + @Test + fun updateSharedLibraries_withStaticLibPackage() { + val testPackageSetting = mExistingSettings[STATIC_LIB_PACKAGE_NAME]!! + assertThat(testPackageSetting.usesLibraryFiles).isEmpty() + + mSharedLibrariesImpl.updateSharedLibrariesLPw(testPackageSetting.pkg, testPackageSetting, + null /* changingLib */, null /* changingLibSetting */, mExistingPackages) + + assertThat(testPackageSetting.usesLibraryFiles).hasSize(1) + assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(DYNAMIC_LIB_PACKAGE_NAME)) + } + + @Test + fun updateSharedLibraries_withConsumerPackage() { + val testPackageSetting = mExistingSettings[CONSUMER_PACKAGE_NAME]!! + assertThat(testPackageSetting.usesLibraryFiles).isEmpty() + + mSharedLibrariesImpl.updateSharedLibrariesLPw(testPackageSetting.pkg, testPackageSetting, + null /* changingLib */, null /* changingLibSetting */, mExistingPackages) + + assertThat(testPackageSetting.usesLibraryFiles).hasSize(2) + assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(DYNAMIC_LIB_PACKAGE_NAME)) + assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(STATIC_LIB_PACKAGE_NAME)) + } + + @Test + fun updateAllSharedLibraries() { + mExistingSettings.forEach { + assertThat(it.value.usesLibraryFiles).isEmpty() + } + + mSharedLibrariesImpl.updateAllSharedLibrariesLPw( + null /* updatedPkg */, null /* updatedPkgSetting */, mExistingPackages) + + var testPackageSetting = mExistingSettings[DYNAMIC_LIB_PACKAGE_NAME]!! + assertThat(testPackageSetting.usesLibraryFiles).hasSize(1) + assertThat(testPackageSetting.usesLibraryFiles).contains(builtinLibPath(BUILTIN_LIB_NAME)) + + testPackageSetting = mExistingSettings[STATIC_LIB_PACKAGE_NAME]!! + assertThat(testPackageSetting.usesLibraryFiles).hasSize(2) + assertThat(testPackageSetting.usesLibraryFiles).contains(builtinLibPath(BUILTIN_LIB_NAME)) + assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(DYNAMIC_LIB_PACKAGE_NAME)) + + testPackageSetting = mExistingSettings[CONSUMER_PACKAGE_NAME]!! + assertThat(testPackageSetting.usesLibraryFiles).hasSize(3) + assertThat(testPackageSetting.usesLibraryFiles).contains(builtinLibPath(BUILTIN_LIB_NAME)) + assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(DYNAMIC_LIB_PACKAGE_NAME)) + assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(STATIC_LIB_PACKAGE_NAME)) + } + + @Test + fun getAllowedSharedLibInfos_withStaticSharedLibInfo() { + val testInfo = libOfStatic(TEST_LIB_PACKAGE_NAME, TEST_LIB_NAME, 1L) + val scanResult = ScanResult(mock(), true, null, null, + false, 0, null, testInfo, null) + + val allowedInfos = mSharedLibrariesImpl.getAllowedSharedLibInfos(scanResult) + + assertThat(allowedInfos).hasSize(1) + assertThat(allowedInfos[0].name).isEqualTo(TEST_LIB_NAME) + } + + @Test + fun getAllowedSharedLibInfos_withDynamicSharedLibInfo() { + val testInfo = libOfDynamic(TEST_LIB_PACKAGE_NAME, TEST_LIB_NAME) + val pair = createBasicAndroidPackage( + TEST_LIB_PACKAGE_NAME, 10L, libraries = arrayOf(TEST_LIB_NAME)) + val parsedPackage = pair.second.apply { + isSystem = true + } as ParsedPackage + val packageSetting = mRule.system() + .createBasicSettingBuilder(pair.first.parentFile, parsedPackage.hideAsFinal()) + .setPkgFlags(ApplicationInfo.FLAG_SYSTEM).build() + val scanRequest = ScanRequest(parsedPackage, null, null, null, + null, null, null, 0, 0, false, null, null) + val scanResult = ScanResult(scanRequest, true, packageSetting, null, + false, 0, null, null, listOf(testInfo)) + + val allowedInfos = mSharedLibrariesImpl.getAllowedSharedLibInfos(scanResult) + + assertThat(allowedInfos).hasSize(1) + assertThat(allowedInfos[0].name).isEqualTo(TEST_LIB_NAME) + } + + private fun addExistingPackages() { + // add a dynamic shared library that is using the builtin library + addPackage(DYNAMIC_LIB_PACKAGE_NAME, 1L, + libraries = arrayOf(DYNAMIC_LIB_NAME), + usesLibraries = arrayOf(BUILTIN_LIB_NAME)) + + // add a static shared library v7 that is using the dynamic shared library + addPackage(STATIC_LIB_PACKAGE_NAME, STATIC_LIB_VERSION, + staticLibrary = STATIC_LIB_NAME, staticLibraryVersion = STATIC_LIB_VERSION, + usesLibraries = arrayOf(DYNAMIC_LIB_NAME)) + + // add a consumer package that is using the dynamic and static shared library + addPackage(CONSUMER_PACKAGE_NAME, 1L, + usesLibraries = arrayOf(DYNAMIC_LIB_NAME), + usesStaticLibraries = arrayOf(STATIC_LIB_NAME), + usesStaticLibraryVersions = arrayOf(STATIC_LIB_VERSION)) + } + + private fun addExistingSharedLibraries() { + mSharedLibrariesImpl.addBuiltInSharedLibraryLPw(libEntry(BUILTIN_LIB_NAME)) + mSharedLibrariesImpl.commitSharedLibraryInfoLPw( + libOfDynamic(DYNAMIC_LIB_PACKAGE_NAME, DYNAMIC_LIB_NAME)) + mSharedLibrariesImpl.commitSharedLibraryInfoLPw( + libOfStatic(STATIC_LIB_PACKAGE_NAME, STATIC_LIB_NAME, STATIC_LIB_VERSION)) + } + + private fun addPackage( + packageName: String, + version: Long, + libraries: Array? = null, + staticLibrary: String? = null, + staticLibraryVersion: Long = 0L, + usesLibraries: Array? = null, + usesStaticLibraries: Array? = null, + usesStaticLibraryVersions: Array? = null + ): PackageSetting { + val pair = createBasicAndroidPackage(packageName, version, libraries, staticLibrary, + staticLibraryVersion, usesLibraries, usesStaticLibraries, usesStaticLibraryVersions) + val apkPath = pair.first + val parsingPackage = pair.second + val spyPkg = spy((parsingPackage as ParsedPackage).hideAsFinal()) + mExistingPackages[packageName] = spyPkg + + val spyPackageSetting = spy(mRule.system() + .createBasicSettingBuilder(apkPath.parentFile, spyPkg).build()) + mExistingSettings[spyPackageSetting.packageName] = spyPackageSetting + + return spyPackageSetting + } + + private fun createBasicAndroidPackage( + packageName: String, + version: Long, + libraries: Array? = null, + staticLibrary: String? = null, + staticLibraryVersion: Long = 0L, + usesLibraries: Array? = null, + usesStaticLibraries: Array? = null, + usesStaticLibraryVersions: Array? = null + ): Pair { + assertFalse { libraries != null && staticLibrary != null } + assertTrue { (usesStaticLibraries?.size ?: -1) == (usesStaticLibraryVersions?.size ?: -1) } + + val pair = mRule.system() + .createBasicAndroidPackage(mRule.system().dataAppDirectory, packageName, version) + pair.second.apply { + setTargetSdkVersion(Build.VERSION_CODES.S) + libraries?.forEach { addLibraryName(it) } + staticLibrary?.let { + setStaticSharedLibName(it) + setStaticSharedLibVersion(staticLibraryVersion) + setStaticSharedLibrary(true) + } + usesLibraries?.forEach { addUsesLibrary(it) } + usesStaticLibraries?.forEachIndexed { index, s -> + addUsesStaticLibrary(s, + usesStaticLibraryVersions?.get(index) ?: 0L, + arrayOf(s)) + } + } + return pair + } + + private fun libEntry(libName: String, path: String? = null): SharedLibraryEntry = + SharedLibraryEntry(libName, path ?: builtinLibPath(libName), + arrayOfNulls(0), false /* isNative */) + + private fun libOfBuiltin(libName: String): SharedLibraryInfo = + SharedLibraryInfo(builtinLibPath(libName), + null /* packageName */, + null /* codePaths */, + libName, + VERSION_UNDEFINED, + SharedLibraryInfo.TYPE_BUILTIN, + VersionedPackage(PLATFORM_PACKAGE_NAME, 0L /* versionCode */), + null /* dependentPackages */, + null /* dependencies */, + false /* isNative */) + + private fun libOfStatic( + packageName: String, + libName: String, + version: Long + ): SharedLibraryInfo = + SharedLibraryInfo(null /* path */, + packageName, + listOf(apkPath(packageName)), + libName, + version, + SharedLibraryInfo.TYPE_STATIC, + VersionedPackage(packageName, version /* versionCode */), + null /* dependentPackages */, + null /* dependencies */, + false /* isNative */) + + private fun libOfDynamic(packageName: String, libName: String): SharedLibraryInfo = + SharedLibraryInfo(null /* path */, + packageName, + listOf(apkPath(packageName)), + libName, + VERSION_UNDEFINED, + SharedLibraryInfo.TYPE_DYNAMIC, + VersionedPackage(packageName, 1L /* versionCode */), + null /* dependentPackages */, + null /* dependencies */, + false /* isNative */) + + private fun builtinLibPath(libName: String): String = "/system/app/$libName/$libName.jar" + + private fun apkPath(packageName: String): String = + File(mRule.system().dataAppDirectory, packageName).path +}