Grant access to ephemeral metadata
When an ephemeral application explicitly accesses an
installed application, it grants access to its package
metadata. The ephemeral application effectively stays
hidden if it doesn't explicitly connect to any activity,
service or provider [i.e. implicit connections using
an ACTION_VIEW/CATEGORY_BROWSABLE intent will not expose
its metadata].
Bug: 34123112
Test: cts-tradefed run commandAndExit cts-dev -m CtsAppSecurityHostTestCases -t android.appsecurity.cts.EphemeralTest
Change-Id: I7e1680902599b3ada0d4fba5998af30566017051
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index 2590a6b..62f3848 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -223,6 +223,27 @@
int userId);
/**
+ * Grants access to the package metadata for an ephemeral application.
+ * <p>
+ * When an ephemeral application explicitly tries to interact with a full
+ * install application [via an activity, service or provider that has been
+ * exposed using the {@code visibleToInstantApp} attribute], the normal
+ * application must be able to see metadata about the connecting ephemeral
+ * app. If the ephemeral application uses an implicit intent [ie action VIEW,
+ * category BROWSABLE], it remains hidden from the launched activity.
+ * <p>
+ * If the {@code sourceUid} is not for an ephemeral app or {@code targetUid}
+ * is not for a fully installed app, this method will be a no-op.
+ *
+ * @param userId the user
+ * @param intent the intent that triggered the grant
+ * @param targetAppId The app ID of the fully installed application
+ * @param ephemeralAppId The app ID of the ephemeral application
+ */
+ public abstract void grantEphemeralAccess(int userId, Intent intent,
+ int targetAppId, int ephemeralAppId);
+
+ /**
* @return The SetupWizard package name.
*/
public abstract String getSetupWizardPackageName();
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 7661127..bfed37d 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1907,6 +1907,7 @@
mAm.grantUriPermissionUncheckedFromIntentLocked(si.neededGrants,
si.getUriPermissionsLocked());
}
+ // TODO b/34123112; Insert ephemeral grant here
bumpServiceExecutingLocked(r, execInFg, "start");
if (!oomAdjusted) {
oomAdjusted = true;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2a324eb..0243391 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8079,6 +8079,12 @@
return pi;
}
+ void grantEphemeralAccessLocked(int userId, Intent intent,
+ int targetAppId, int ephemeralAppId) {
+ getPackageManagerInternalLocked().
+ grantEphemeralAccess(userId, intent, targetAppId, ephemeralAppId);
+ }
+
private UriPermission findUriPermissionLocked(int targetUid, GrantUri grantUri) {
final ArrayMap<GrantUri, UriPermission> targetUris = mGrantedUriPermissions.get(targetUid);
if (targetUris != null) {
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 3f71d12..46e00479 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -1131,7 +1131,8 @@
mService.grantUriPermissionFromIntentLocked(mCallingUid, mStartActivity.packageName,
mIntent, mStartActivity.getUriPermissionsLocked(), mStartActivity.userId);
-
+ mService.grantEphemeralAccessLocked(mStartActivity.userId, mIntent,
+ mStartActivity.appInfo.uid, UserHandle.getAppId(mCallingUid));
if (mSourceRecord != null && mSourceRecord.isRecentsActivity()) {
mStartActivity.task.setTaskToReturnTo(RECENTS_ACTIVITY_TYPE);
}
diff --git a/services/core/java/com/android/server/pm/EphemeralApplicationRegistry.java b/services/core/java/com/android/server/pm/EphemeralApplicationRegistry.java
index 1e3e0ca..e8be629 100644
--- a/services/core/java/com/android/server/pm/EphemeralApplicationRegistry.java
+++ b/services/core/java/com/android/server/pm/EphemeralApplicationRegistry.java
@@ -17,6 +17,7 @@
package com.android.server.pm;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.EphemeralApplicationInfo;
import android.content.pm.PackageParser;
import android.content.pm.PackageUserState;
@@ -27,10 +28,13 @@
import android.graphics.drawable.Drawable;
import android.os.Binder;
import android.os.Environment;
+import android.os.UserHandle;
import android.provider.Settings;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
@@ -51,6 +55,7 @@
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -62,7 +67,7 @@
class EphemeralApplicationRegistry {
private static final boolean DEBUG = false;
- private static final boolean ENABLED = false;
+ private static final boolean ENABLED = true;
private static final String LOG_TAG = "EphemeralAppRegistry";
@@ -90,6 +95,16 @@
@GuardedBy("mService.mPackages")
private SparseArray<List<UninstalledEphemeralAppState>> mUninstalledEphemeralApps;
+ /**
+ * Automatic grants for access to instant app metadata.
+ * The key is the target application UID.
+ * The value is a set of instant app UIDs.
+ * UserID -> TargetAppId -> InstantAppId
+ */
+ private SparseArray<SparseArray<SparseBooleanArray>> mEphemeralGrants;
+ /** The set of all installed instant apps. UserID -> AppID */
+ private SparseArray<SparseBooleanArray> mInstalledEphemeralAppUids;
+
public EphemeralApplicationRegistry(PackageManagerService service) {
mService = service;
}
@@ -189,6 +204,9 @@
// Propagate permissions before removing any state
propagateEphemeralAppPermissionsIfNeeded(pkg, userId);
+ if (pkg.applicationInfo.isEphemeralApp()) {
+ addEphemeralAppLPw(userId, ps.appId);
+ }
// Remove the in-memory state
if (mUninstalledEphemeralApps != null) {
@@ -248,9 +266,11 @@
if (pkg.applicationInfo.isEphemeralApp()) {
// Add a record for an uninstalled ephemeral app
addUninstalledEphemeralAppLPw(pkg, userId);
+ removeEphemeralAppLPw(userId, ps.appId);
} else {
// Deleting an app prunes all ephemeral state such as cookie
deleteDir(getEphemeralApplicationDir(pkg.packageName, userId));
+ removeAppLPw(userId, ps.appId);
}
}
}
@@ -262,9 +282,114 @@
if (mUninstalledEphemeralApps != null) {
mUninstalledEphemeralApps.remove(userId);
}
+ if (mInstalledEphemeralAppUids != null) {
+ mInstalledEphemeralAppUids.remove(userId);
+ }
+ if (mEphemeralGrants != null) {
+ mEphemeralGrants.remove(userId);
+ }
deleteDir(getEphemeralApplicationsDir(userId));
}
+ public boolean isEphemeralAccessGranted(int userId, int targetAppId, int ephemeralAppId) {
+ if (mEphemeralGrants == null) {
+ return false;
+ }
+ final SparseArray<SparseBooleanArray> targetAppList = mEphemeralGrants.get(userId);
+ if (targetAppList == null) {
+ return false;
+ }
+ final SparseBooleanArray ephemeralGrantList = targetAppList.get(targetAppId);
+ if (ephemeralGrantList == null) {
+ return false;
+ }
+ return ephemeralGrantList.get(ephemeralAppId);
+ }
+
+ public void grantEphemeralAccessLPw(int userId, Intent intent,
+ int targetAppId, int ephemeralAppId) {
+ if (mInstalledEphemeralAppUids == null) {
+ return; // no ephemeral apps installed; no need to grant
+ }
+ SparseBooleanArray ephemeralAppList = mInstalledEphemeralAppUids.get(userId);
+ if (ephemeralAppList == null || !ephemeralAppList.get(ephemeralAppId)) {
+ return; // ephemeral app id isn't installed; no need to grant
+ }
+ if (ephemeralAppList.get(targetAppId)) {
+ return; // target app id is an ephemeral app; no need to grant
+ }
+ if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
+ final Set<String> categories = intent.getCategories();
+ if (categories != null && categories.contains(Intent.CATEGORY_BROWSABLE)) {
+ return; // launched via VIEW/BROWSABLE intent; no need to grant
+ }
+ }
+ if (mEphemeralGrants == null) {
+ mEphemeralGrants = new SparseArray<>();
+ }
+ SparseArray<SparseBooleanArray> targetAppList = mEphemeralGrants.get(userId);
+ if (targetAppList == null) {
+ targetAppList = new SparseArray<>();
+ mEphemeralGrants.put(userId, targetAppList);
+ }
+ SparseBooleanArray ephemeralGrantList = targetAppList.get(targetAppId);
+ if (ephemeralGrantList == null) {
+ ephemeralGrantList = new SparseBooleanArray();
+ targetAppList.put(targetAppId, ephemeralGrantList);
+ }
+ ephemeralGrantList.put(ephemeralAppId, true /*granted*/);
+ }
+
+ public void addEphemeralAppLPw(int userId, int ephemeralAppId) {
+ if (mInstalledEphemeralAppUids == null) {
+ mInstalledEphemeralAppUids = new SparseArray<>();
+ }
+ SparseBooleanArray ephemeralAppList = mInstalledEphemeralAppUids.get(userId);
+ if (ephemeralAppList == null) {
+ ephemeralAppList = new SparseBooleanArray();
+ mInstalledEphemeralAppUids.put(userId, ephemeralAppList);
+ }
+ ephemeralAppList.put(ephemeralAppId, true /*installed*/);
+ }
+
+ private void removeEphemeralAppLPw(int userId, int ephemeralAppId) {
+ // remove from the installed list
+ if (mInstalledEphemeralAppUids == null) {
+ return; // no ephemeral apps on the system
+ }
+ final SparseBooleanArray ephemeralAppList = mInstalledEphemeralAppUids.get(userId);
+ if (ephemeralAppList == null) {
+ Slog.w(LOG_TAG, "Remove ephemeral not in install list");
+ return;
+ } else {
+ ephemeralAppList.delete(ephemeralAppId);
+ }
+ // remove any grants
+ if (mEphemeralGrants == null) {
+ return; // no grants on the system
+ }
+ final SparseArray<SparseBooleanArray> targetAppList = mEphemeralGrants.get(userId);
+ if (targetAppList == null) {
+ return; // no grants for this user
+ }
+ final int numApps = targetAppList.size();
+ for (int i = targetAppList.size() - 1; i >= 0; --i) {
+ targetAppList.valueAt(i).delete(ephemeralAppId);
+ }
+ }
+
+ private void removeAppLPw(int userId, int targetAppId) {
+ // remove from the installed list
+ if (mEphemeralGrants == null) {
+ return; // no grants on the system
+ }
+ final SparseArray<SparseBooleanArray> targetAppList = mEphemeralGrants.get(userId);
+ if (targetAppList == null) {
+ return; // no grants for this user
+ }
+ targetAppList.delete(targetAppId);
+ }
+
private void addUninstalledEphemeralAppLPw(PackageParser.Package pkg, int userId) {
EphemeralApplicationInfo uninstalledApp = createEphemeralAppInfoForPackage(pkg, userId);
if (uninstalledApp == null) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6669889..ccc6a95 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2224,6 +2224,7 @@
Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
mDefaultPermissionPolicy = new DefaultPermissionGrantPolicy(this);
+ mEphemeralApplicationRegistry = new EphemeralApplicationRegistry(this);
File dataDir = Environment.getDataDirectory();
mAppInstallDir = new File(dataDir, "app");
@@ -2806,8 +2807,6 @@
setUpEphemeralInstallerActivityLP(mEphemeralInstallerComponent);
}
- mEphemeralApplicationRegistry = new EphemeralApplicationRegistry(this);
-
// Read and update the usage of dex files.
// Do this at the end of PM init so that all the packages have their
// data directory reconciled.
@@ -3257,6 +3256,31 @@
if (p == null) {
return null;
}
+ // Filter out ephemeral app metadata:
+ // * The system/shell/root can see metadata for any app
+ // * An installed app can see metadata for 1) other installed apps
+ // and 2) ephemeral apps that have explicitly interacted with it
+ // * Ephemeral apps can only see their own metadata
+ final int callingAppId = UserHandle.getAppId(Binder.getCallingUid());
+ if (callingAppId != Process.SYSTEM_UID
+ && callingAppId != Process.SHELL_UID
+ && callingAppId != Process.ROOT_UID) {
+ final String ephemeralPackageName = getEphemeralPackageName(Binder.getCallingUid());
+ if (ephemeralPackageName != null) {
+ // ephemeral apps can only get information on themselves
+ if (!ephemeralPackageName.equals(p.packageName)) {
+ return null;
+ }
+ } else {
+ if (p.applicationInfo.isEphemeralApp()) {
+ // only get access to the ephemeral app if we've been granted access
+ if (!mEphemeralApplicationRegistry.isEphemeralAccessGranted(
+ userId, callingAppId, ps.appId)) {
+ return null;
+ }
+ }
+ }
+ }
final PermissionsState permissionsState = ps.getPermissionsState();
@@ -3336,22 +3360,19 @@
// reader
synchronized (mPackages) {
- // Normalize package name to hanlde renamed packages
+ // Normalize package name to handle renamed packages
packageName = normalizePackageNameLPr(packageName);
final boolean matchFactoryOnly = (flags & MATCH_FACTORY_ONLY) != 0;
- PackageParser.Package p = null;
if (matchFactoryOnly) {
final PackageSetting ps = mSettings.getDisabledSystemPkgLPr(packageName);
if (ps != null) {
return generatePackageInfo(ps, flags, userId);
}
}
- if (p == null) {
- p = mPackages.get(packageName);
- if (matchFactoryOnly && p != null && !isSystemApp(p)) {
- return null;
- }
+ PackageParser.Package p = mPackages.get(packageName);
+ if (matchFactoryOnly && p != null && !isSystemApp(p)) {
+ return null;
}
if (DEBUG_PACKAGE_INFO)
Log.v(TAG, "getPackageInfo " + packageName + ": " + p);
@@ -8839,6 +8860,10 @@
// Modify state for the given package setting
commitPackageSettings(pkg, pkgSetting, user, scanFlags,
(policyFlags & PackageParser.PARSE_CHATTY) != 0 /*chatty*/);
+ if (isEphemeral(pkg)) {
+ final int userId = user == null ? 0 : user.getIdentifier();
+ mEphemeralApplicationRegistry.addEphemeralAppLPw(userId, pkgSetting.appId);
+ }
}
return pkg;
}
@@ -21924,6 +21949,15 @@
responseObj, origIntent, resolvedType, launchIntent, callingPackage, userId);
}
+ @Override
+ public void grantEphemeralAccess(int userId, Intent intent,
+ int targetAppId, int ephemeralAppId) {
+ synchronized (mPackages) {
+ mEphemeralApplicationRegistry.grantEphemeralAccessLPw(userId, intent,
+ targetAppId, ephemeralAppId);
+ }
+ }
+
public String getSetupWizardPackageName() {
return mSetupWizardPackage;
}