diff options
6 files changed, 514 insertions, 26 deletions
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java index 591619698295..15cd63927648 100644 --- a/services/core/java/com/android/server/pm/Computer.java +++ b/services/core/java/com/android/server/pm/Computer.java @@ -187,6 +187,8 @@ public interface Computer extends PackageDataSnapshot { PackageStateInternal getPackageStateInternal(String packageName); PackageStateInternal getPackageStateInternal(String packageName, int callingUid); + PackageStateInternal getPackageStateFiltered(@NonNull String packageName, int callingUid, + @UserIdInt int userId); ParceledListSlice<PackageInfo> getInstalledPackages(long flags, int userId); ResolveInfo createForwardingResolveInfoUnchecked(WatchedIntentFilter filter, int sourceUserId, int targetUserId); diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 50bb05161ffd..da6f042ca462 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -1816,6 +1816,19 @@ public class ComputerEngine implements Computer { return mSettings.getPackage(packageName); } + @Override + public PackageStateInternal getPackageStateFiltered(@NonNull String packageName, + int callingUid, @UserIdInt int userId) { + packageName = resolveInternalPackageNameInternalLocked( + packageName, PackageManager.VERSION_CODE_HIGHEST, callingUid); + var packageState = mSettings.getPackage(packageName); + if (shouldFilterApplication(packageState, callingUid, userId)) { + return null; + } else { + return packageState; + } + } + public final ParceledListSlice<PackageInfo> getInstalledPackages(long flags, int userId) { final int callingUid = Binder.getCallingUid(); if (getInstantAppPackageName(callingUid) != null) { diff --git a/services/core/java/com/android/server/pm/PackageManagerLocal.java b/services/core/java/com/android/server/pm/PackageManagerLocal.java index 39cc37ea2e7c..c9b48bf3c2a1 100644 --- a/services/core/java/com/android/server/pm/PackageManagerLocal.java +++ b/services/core/java/com/android/server/pm/PackageManagerLocal.java @@ -20,11 +20,17 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.os.Binder; +import android.os.UserHandle; + +import com.android.server.pm.pkg.PackageState; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; +import java.util.Map; +import java.util.function.Consumer; /** * In-process API for server side PackageManager related infrastructure. @@ -57,10 +63,10 @@ public interface PackageManagerLocal { FLAG_STORAGE_CE, }) @Retention(RetentionPolicy.SOURCE) - public @interface StorageFlags {} + @interface StorageFlags {} /** - * Reconcile sdk data sub-directories for the given {@code packagName}. + * Reconcile sdk data sub-directories for the given {@code packageName}. * * Sub directories are created if they do not exist already. If there is an existing per- * sdk directory that is missing from {@code subDirNames}, then it is removed. @@ -80,4 +86,100 @@ public interface PackageManagerLocal { void reconcileSdkData(@Nullable String volumeUuid, @NonNull String packageName, @NonNull List<String> subDirNames, int userId, int appId, int previousAppId, @NonNull String seInfo, @StorageFlags int flags) throws IOException; + + /** + * Provides a snapshot scoped class to access snapshot-aware APIs. Should be short-term use and + * closed as soon as possible. + * <p/> + * All reachable types in the snapshot are read-only. + * <p/> + * The snapshot assumes the caller is acting on behalf of the system and will not filter any + * results. + * + * @hide + */ + @NonNull + UnfilteredSnapshot withUnfilteredSnapshot(); + + /** + * {@link #withFilteredSnapshot(int, UserHandle)} that infers the UID and user from the + * caller through {@link Binder#getCallingUid()} and {@link Binder#getCallingUserHandle()}. + * + * @see #withFilteredSnapshot(int, UserHandle) + * @hide + */ + @NonNull + FilteredSnapshot withFilteredSnapshot(); + + /** + * Provides a snapshot scoped class to access snapshot-aware APIs. Should be short-term use and + * closed as soon as possible. + * <p/> + * All reachable types in the snapshot are read-only. + * + * @param callingUid The caller UID to filter results based on. This includes package visibility + * and permissions, including cross-user enforcement. + * @param user The user to query as, should usually be the user that the caller was + * invoked from. + * @hide + */ + @SuppressWarnings("UserHandleName") // Ignore naming convention, not invoking action as user + @NonNull + FilteredSnapshot withFilteredSnapshot(int callingUid, @NonNull UserHandle user); + + /** + * @hide + */ + interface UnfilteredSnapshot extends AutoCloseable { + + /** + * Allows re-use of this snapshot, but in a filtered context. This allows a caller to invoke + * itself as multiple other actual callers without having to re-take a snapshot. + * <p/> + * Note that closing the parent snapshot closes any filtered children generated from it. + * + * @return An isolated instance of {@link FilteredSnapshot} which can be closed without + * affecting this parent snapshot or any sibling snapshots. + */ + @SuppressWarnings("UserHandleName") // Ignore naming convention, not invoking action as user + @NonNull + FilteredSnapshot filtered(int callingUid, @NonNull UserHandle user); + + /** + * Returns a map of all {@link PackageState PackageStates} on the device. + * + * @return Mapping of package name to {@link PackageState}. + */ + @NonNull + Map<String, PackageState> getPackageStates(); + + @Override + void close(); + } + + /** + * @hide + */ + interface FilteredSnapshot extends AutoCloseable { + + /** + * @return {@link PackageState} for the {@code packageName}, filtered if applicable. + */ + @Nullable + PackageState getPackageState(@NonNull String packageName); + + /** + * Iterates on all states. This should only be used when either the target package name + * is not known or the large majority of the states are expected to be used. + * + * This will cause app visibility filtering to be invoked on each state on the device, + * which can be expensive. + * + * @param consumer Block to accept each state as it becomes available post-filtering. + */ + void forAllPackageStates(@NonNull Consumer<PackageState> consumer); + + @Override + void close(); + } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 2c460f858d36..d939ca61fa08 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -200,6 +200,7 @@ import com.android.server.pm.dex.ArtManagerService; import com.android.server.pm.dex.ArtUtils; import com.android.server.pm.dex.DexManager; import com.android.server.pm.dex.ViewCompiler; +import com.android.server.pm.local.PackageManagerLocalImpl; import com.android.server.pm.parsing.PackageInfoUtils; import com.android.server.pm.parsing.PackageParser2; import com.android.server.pm.parsing.pkg.AndroidPackageInternal; @@ -1071,10 +1072,27 @@ public class PackageManagerService implements PackageSender, TestUtilityService @VisibleForTesting(visibility = Visibility.PACKAGE) @NonNull public Computer snapshotComputer() { - if (Thread.holdsLock(mLock)) { - // If the current thread holds mLock then it may have modified state but not - // yet invalidated the snapshot. Always give the thread the live computer. - return mLiveComputer; + return snapshotComputer(true /*allowLiveComputer*/); + } + + /** + * This method should only ever be called from {@link PackageManagerLocal#snapshot()}. + * + * @param allowLiveComputer Whether to allow a live computer instance based on caller {@link + * #mLock} hold state. In certain cases, like for {@link + * PackageManagerLocal} API, it must be enforced that the caller gets + * a snapshot at time, and never the live variant. + * @deprecated Use {@link #snapshotComputer()} + */ + @Deprecated + @NonNull + public Computer snapshotComputer(boolean allowLiveComputer) { + if (allowLiveComputer) { + if (Thread.holdsLock(mLock)) { + // If the current thread holds mLock then it may have modified state but not + // yet invalidated the snapshot. Always give the thread the live computer. + return mLiveComputer; + } } var oldSnapshot = sSnapshot.get(); @@ -1531,7 +1549,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService ServiceManager.addService("package", iPackageManager); final PackageManagerNative pmn = new PackageManagerNative(m); ServiceManager.addService("package_native", pmn); - LocalManagerRegistry.addManager(PackageManagerLocal.class, m.new PackageManagerLocalImpl()); + LocalManagerRegistry.addManager(PackageManagerLocal.class, + new PackageManagerLocalImpl(m)); return Pair.create(m, iPackageManager); } @@ -6017,25 +6036,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService } } - private class PackageManagerLocalImpl implements PackageManagerLocal { - @Override - public void reconcileSdkData(@Nullable String volumeUuid, @NonNull String packageName, - @NonNull List<String> subDirNames, int userId, int appId, int previousAppId, - @NonNull String seInfo, int flags) throws IOException { - synchronized (mInstallLock) { - ReconcileSdkDataArgs args = mInstaller.buildReconcileSdkDataArgs(volumeUuid, - packageName, subDirNames, userId, appId, seInfo, - flags); - args.previousAppId = previousAppId; - try { - mInstaller.reconcileSdkData(args); - } catch (InstallerException e) { - throw new IOException(e.getMessage()); - } - } - } - } - private class PackageManagerInternalImpl extends PackageManagerInternalBase { public PackageManagerInternalImpl() { @@ -7296,4 +7296,20 @@ public class PackageManagerService implements PackageSender, TestUtilityService mSettings.addInstallerPackageNames(installSource); } } + + public void reconcileSdkData(@Nullable String volumeUuid, @NonNull String packageName, + @NonNull List<String> subDirNames, int userId, int appId, int previousAppId, + @NonNull String seInfo, int flags) throws IOException { + synchronized (mInstallLock) { + ReconcileSdkDataArgs args = mInstaller.buildReconcileSdkDataArgs(volumeUuid, + packageName, subDirNames, userId, appId, seInfo, + flags); + args.previousAppId = previousAppId; + try { + mInstaller.reconcileSdkData(args); + } catch (InstallerException e) { + throw new IOException(e.getMessage()); + } + } + } } diff --git a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java new file mode 100644 index 000000000000..00c8f84b340b --- /dev/null +++ b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2022 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.local; + +import android.annotation.CallSuper; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.os.Binder; +import android.os.UserHandle; + +import com.android.server.pm.Computer; +import com.android.server.pm.PackageManagerLocal; +import com.android.server.pm.PackageManagerService; +import com.android.server.pm.pkg.PackageState; +import com.android.server.pm.snapshot.PackageDataSnapshot; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +/** @hide */ +public class PackageManagerLocalImpl implements PackageManagerLocal { + + private final PackageManagerService mService; + + public PackageManagerLocalImpl(PackageManagerService service) { + mService = service; + } + + @Override + public void reconcileSdkData(@Nullable String volumeUuid, @NonNull String packageName, + @NonNull List<String> subDirNames, int userId, int appId, int previousAppId, + @NonNull String seInfo, int flags) throws IOException { + mService.reconcileSdkData(volumeUuid, packageName, subDirNames, userId, appId, + previousAppId, seInfo, flags); + } + + @NonNull + @Override + public UnfilteredSnapshotImpl withUnfilteredSnapshot() { + return new UnfilteredSnapshotImpl(mService.snapshotComputer(false /*allowLiveComputer*/)); + } + + @NonNull + @Override + public FilteredSnapshotImpl withFilteredSnapshot() { + return withFilteredSnapshot(Binder.getCallingUid(), Binder.getCallingUserHandle()); + } + + @NonNull + @Override + public FilteredSnapshotImpl withFilteredSnapshot(int callingUid, @NonNull UserHandle user) { + return new FilteredSnapshotImpl(callingUid, user, + mService.snapshotComputer(false /*allowLiveComputer*/), null); + } + + private abstract static class BaseSnapshotImpl implements AutoCloseable { + + private boolean mClosed; + + @NonNull + protected Computer mSnapshot; + + private BaseSnapshotImpl(@NonNull PackageDataSnapshot snapshot) { + mSnapshot = (Computer) snapshot; + } + + @Override + public void close() { + mClosed = true; + mSnapshot = null; + // TODO: Recycle snapshots? + } + + @CallSuper + protected void checkClosed() { + if (mClosed) { + throw new IllegalStateException("Snapshot already closed"); + } + } + } + + private static class UnfilteredSnapshotImpl extends BaseSnapshotImpl implements + UnfilteredSnapshot { + + private UnfilteredSnapshotImpl(@NonNull PackageDataSnapshot snapshot) { + super(snapshot); + } + + @Override + public FilteredSnapshot filtered(int callingUid, @NonNull UserHandle user) { + return new FilteredSnapshotImpl(callingUid, user, mSnapshot, this); + } + + @SuppressWarnings("RedundantSuppression") + @NonNull + @Override + public Map<String, PackageState> getPackageStates() { + checkClosed(); + + //noinspection unchecked, RedundantCast + return (Map<String, PackageState>) (Map<?, ?>) mSnapshot.getPackageStates(); + } + } + + private static class FilteredSnapshotImpl extends BaseSnapshotImpl implements + FilteredSnapshot { + + private final int mCallingUid; + + @UserIdInt + private final int mUserId; + + @Nullable + private ArrayList<PackageState> mFilteredPackageStates; + + @Nullable + private final UnfilteredSnapshotImpl mParentSnapshot; + + private FilteredSnapshotImpl(int callingUid, @NonNull UserHandle user, + @NonNull PackageDataSnapshot snapshot, + @Nullable UnfilteredSnapshotImpl parentSnapshot) { + super(snapshot); + mCallingUid = callingUid; + mUserId = user.getIdentifier(); + mParentSnapshot = parentSnapshot; + } + + @Override + protected void checkClosed() { + if (mParentSnapshot != null) { + mParentSnapshot.checkClosed(); + } + + super.checkClosed(); + } + + @Nullable + @Override + public PackageState getPackageState(@NonNull String packageName) { + checkClosed(); + return mSnapshot.getPackageStateFiltered(packageName, mCallingUid, mUserId); + } + + @Override + public void forAllPackageStates(@NonNull Consumer<PackageState> consumer) { + checkClosed(); + + if (mFilteredPackageStates == null) { + var packageStates = mSnapshot.getPackageStates(); + var filteredPackageStates = new ArrayList<PackageState>(); + for (int index = 0, size = packageStates.size(); index < size; index++) { + var packageState = packageStates.valueAt(index); + if (!mSnapshot.shouldFilterApplication(packageState, mCallingUid, mUserId)) { + filteredPackageStates.add(packageState); + } + } + mFilteredPackageStates = filteredPackageStates; + } + + for (int index = 0, size = mFilteredPackageStates.size(); index < size; index++) { + var packageState = mFilteredPackageStates.get(index); + consumer.accept(packageState); + } + } + } +} diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt new file mode 100644 index 000000000000..faa235254665 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2022 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.test + +import android.os.UserHandle +import android.util.ArrayMap +import com.android.server.pm.Computer +import com.android.server.pm.PackageManagerLocal +import com.android.server.pm.PackageManagerService +import com.android.server.pm.local.PackageManagerLocalImpl +import com.android.server.pm.pkg.PackageState +import com.android.server.pm.pkg.PackageStateInternal +import com.android.server.testutils.mockThrowOnUnmocked +import com.android.server.testutils.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyString +import kotlin.test.assertFailsWith + +class PackageManagerLocalSnapshotTest { + + private val service = mockThrowOnUnmocked<PackageManagerService> { + @Suppress("DEPRECATION") + whenever(snapshotComputer(false)) { mockSnapshot() } + } + + private val packageStateAll = mockPackageState("com.package.all") + private val packageStateUser0 = mockPackageState("com.package.zero") + private val packageStateUser10 = mockPackageState("com.package.ten") + + @Test + fun unfiltered() { + val pmLocal = pmLocal() + val snapshot = pmLocal.withUnfilteredSnapshot() + val filteredOne: PackageManagerLocal.FilteredSnapshot + val filteredTwo: PackageManagerLocal.FilteredSnapshot + snapshot.use { + val packageStates = it.packageStates + + // Check contents + assertThat(packageStates).containsExactly( + packageStateAll.packageName, packageStateAll, + packageStateUser0.packageName, packageStateUser0, + packageStateUser10.packageName, packageStateUser10, + ) + + // Check further calls get the same object + assertThat(it.packageStates).isSameInstanceAs(packageStates) + + // Generate 3 filtered children (2 for the same caller, 1 for different) + filteredOne = it.filtered(1000, UserHandle.getUserHandleForUid(1000)) + filteredTwo = it.filtered(1000, UserHandle.getUserHandleForUid(1000)) + val filteredThree = it.filtered(20000, UserHandle.getUserHandleForUid(1001000)) + + // Check that siblings, even for the same input, are isolated + assertThat(filteredOne).isNotSameInstanceAs(filteredTwo) + + assertThat(filteredOne.getPackageState(packageStateAll.packageName)) + .isEqualTo(packageStateAll) + assertThat(filteredOne.getPackageState(packageStateUser0.packageName)) + .isEqualTo(packageStateUser0) + assertThat(filteredOne.getPackageState(packageStateUser10.packageName)).isNull() + + filteredThree.use { + val statesList = mutableListOf<PackageState>() + assertThat(it.forAllPackageStates { statesList += it }) + assertThat(statesList).containsExactly(packageStateAll, packageStateUser10) + } + + // Call after child close, parent open fails + assertClosedFailure { + filteredThree.getPackageState(packageStateAll.packageName) + } + + // Manually close first sibling and check that second still works + filteredOne.close() + assertThat(filteredTwo.getPackageState(packageStateAll.packageName)) + .isEqualTo(packageStateAll) + } + + // Call after close fails + assertClosedFailure { snapshot.packageStates } + assertClosedFailure { filteredOne.forAllPackageStates {} } + assertClosedFailure { + filteredTwo.getPackageState(packageStateAll.packageName) + } + + // Check newly taken snapshot is different + assertThat(pmLocal.withUnfilteredSnapshot()).isNotSameInstanceAs(snapshot) + } + + @Test + fun filtered() { + val pmLocal = pmLocal() + val snapshot = pmLocal.withFilteredSnapshot(1000, UserHandle.getUserHandleForUid(1000)) + snapshot.use { + assertThat(it.getPackageState(packageStateAll.packageName)) + .isEqualTo(packageStateAll) + assertThat(it.getPackageState(packageStateUser0.packageName)) + .isEqualTo(packageStateUser0) + assertThat(it.getPackageState(packageStateUser10.packageName)).isNull() + + val statesList = mutableListOf<PackageState>() + assertThat(it.forAllPackageStates { statesList += it }) + assertThat(statesList).containsExactly(packageStateAll, packageStateUser0) + } + + // Call after close fails + assertClosedFailure { + snapshot.getPackageState(packageStateAll.packageName) + } + + // Check newly taken snapshot is different + assertThat(pmLocal.withFilteredSnapshot()).isNotSameInstanceAs(snapshot) + } + + private fun pmLocal(): PackageManagerLocal = PackageManagerLocalImpl(service) + + private fun mockSnapshot() = mockThrowOnUnmocked<Computer> { + val packageStates = ArrayMap<String, PackageStateInternal>().apply { + put(packageStateAll.packageName, packageStateAll) + put(packageStateUser0.packageName, packageStateUser0) + put(packageStateUser10.packageName, packageStateUser10) + } + whenever(this.packageStates) { packageStates } + whenever(getPackageStateFiltered(anyString(), anyInt(), anyInt())) { + packageStates[arguments[0]]?.takeUnless { + shouldFilterApplication(it, arguments[1] as Int, arguments[2] as Int) + } + } + + whenever( + shouldFilterApplication(any(PackageStateInternal::class.java), anyInt(), anyInt()) + ) { + val packageState = arguments[0] as PackageState + val user = arguments[2] as Int + + when (packageState) { + packageStateAll -> false + packageStateUser0 -> user != 0 + packageStateUser10 -> user != 10 + else -> true + } + } + } + + private fun mockPackageState(packageName: String) = mockThrowOnUnmocked<PackageStateInternal> { + whenever(this.packageName) { packageName } + whenever(toString()) { packageName } + } + + private fun assertClosedFailure(block: () -> Unit) = + assertFailsWith(IllegalStateException::class, block) + .run { assertThat(message).isEqualTo("Snapshot already closed") } +} |