Overlayable actor enforcement

Validates that the caller of an OverlayManager API that mutates state
is actually allowed to act on the target as defined in the target's
overlayable tag.

<overlayable name="MyResources" actor="namespace/name">

An actor is valid if any of the following is true:
 - is root/system
 - is the target overlay package
 - has the CHANGE_OVERLAY_PACKAGES permission and an actor is not defined
 - is the same package name as the sole resolved Activity for the actor specified
     in the overlayable definition, with only pre-installed, namespaced actors
     currently supported

Bug: 119442583
Bug: 135052950

Test: atest SystemConfigNamedActorTest
Test: atest com.android.server.om

Change-Id: If56b9e8366852eaef84f6bb25c3e6871eaa3f219
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 5f3e503..63de61c 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -39,10 +39,12 @@
 import android.content.IntentFilter;
 import android.content.om.IOverlayManager;
 import android.content.om.OverlayInfo;
+import android.content.om.OverlayableInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserInfo;
+import android.content.res.ApkAssets;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Environment;
@@ -63,6 +65,7 @@
 import com.android.server.FgThread;
 import com.android.server.IoThread;
 import com.android.server.LocalServices;
+import com.android.server.SystemConfig;
 import com.android.server.SystemService;
 import com.android.server.pm.UserManagerService;
 
@@ -229,6 +232,8 @@
 
     private final OverlayManagerServiceImpl mImpl;
 
+    private final OverlayActorEnforcer mActorEnforcer;
+
     private final AtomicBoolean mPersistSettingsScheduled = new AtomicBoolean(false);
 
     public OverlayManagerService(@NonNull final Context context) {
@@ -237,12 +242,13 @@
             traceBegin(TRACE_TAG_RRO, "OMS#OverlayManagerService");
             mSettingsFile = new AtomicFile(
                     new File(Environment.getDataSystemDirectory(), "overlays.xml"), "overlays");
-            mPackageManager = new PackageManagerHelper();
+            mPackageManager = new PackageManagerHelper(context);
             mUserManager = UserManagerService.getInstance();
             IdmapManager im = new IdmapManager(mPackageManager);
             mSettings = new OverlayManagerSettings();
             mImpl = new OverlayManagerServiceImpl(mPackageManager, im, mSettings,
                     getDefaultOverlayPackages(), new OverlayChangeListener());
+            mActorEnforcer = new OverlayActorEnforcer(mPackageManager);
 
             final IntentFilter packageFilter = new IntentFilter();
             packageFilter.addAction(ACTION_PACKAGE_ADDED);
@@ -581,7 +587,7 @@
                 int userId) throws RemoteException {
             try {
                 traceBegin(TRACE_TAG_RRO, "OMS#setEnabled " + packageName + " " + enable);
-                enforceChangeOverlayPackagesPermission("setEnabled");
+                enforceActor(packageName, "setEnabled", userId);
                 userId = handleIncomingUser(userId, "setEnabled");
                 if (packageName == null) {
                     return false;
@@ -605,7 +611,7 @@
                 int userId) throws RemoteException {
             try {
                 traceBegin(TRACE_TAG_RRO, "OMS#setEnabledExclusive " + packageName + " " + enable);
-                enforceChangeOverlayPackagesPermission("setEnabledExclusive");
+                enforceActor(packageName, "setEnabledExclusive", userId);
                 userId = handleIncomingUser(userId, "setEnabledExclusive");
                 if (packageName == null || !enable) {
                     return false;
@@ -630,7 +636,7 @@
                 throws RemoteException {
             try {
                 traceBegin(TRACE_TAG_RRO, "OMS#setEnabledExclusiveInCategory " + packageName);
-                enforceChangeOverlayPackagesPermission("setEnabledExclusiveInCategory");
+                enforceActor(packageName, "setEnabledExclusiveInCategory", userId);
                 userId = handleIncomingUser(userId, "setEnabledExclusiveInCategory");
                 if (packageName == null) {
                     return false;
@@ -656,7 +662,7 @@
             try {
                 traceBegin(TRACE_TAG_RRO, "OMS#setPriority " + packageName + " "
                         + parentPackageName);
-                enforceChangeOverlayPackagesPermission("setPriority");
+                enforceActor(packageName, "setPriority", userId);
                 userId = handleIncomingUser(userId, "setPriority");
                 if (packageName == null || parentPackageName == null) {
                     return false;
@@ -680,7 +686,7 @@
                 throws RemoteException {
             try {
                 traceBegin(TRACE_TAG_RRO, "OMS#setHighestPriority " + packageName);
-                enforceChangeOverlayPackagesPermission("setHighestPriority");
+                enforceActor(packageName, "setHighestPriority", userId);
                 userId = handleIncomingUser(userId, "setHighestPriority");
                 if (packageName == null) {
                     return false;
@@ -704,7 +710,7 @@
                 throws RemoteException {
             try {
                 traceBegin(TRACE_TAG_RRO, "OMS#setLowestPriority " + packageName);
-                enforceChangeOverlayPackagesPermission("setLowestPriority");
+                enforceActor(packageName, "setLowestPriority", userId);
                 userId = handleIncomingUser(userId, "setLowestPriority");
                 if (packageName == null) {
                     return false;
@@ -750,7 +756,7 @@
                 return;
             }
 
-            enforceChangeOverlayPackagesPermission("invalidateCachesForOverlay");
+            enforceActor(packageName, "invalidateCachesForOverlay", userId);
             userId = handleIncomingUser(userId, "invalidateCachesForOverlay");
             final long ident = Binder.clearCallingIdentity();
             try {
@@ -861,18 +867,6 @@
         }
 
         /**
-         * Enforce that the caller holds the CHANGE_OVERLAY_PACKAGES permission (or is
-         * system or root).
-         *
-         * @param message used as message if SecurityException is thrown
-         * @throws SecurityException if the permission check fails
-         */
-        private void enforceChangeOverlayPackagesPermission(@NonNull final String message) {
-            getContext().enforceCallingOrSelfPermission(
-                    android.Manifest.permission.CHANGE_OVERLAY_PACKAGES, message);
-        }
-
-        /**
          * Enforce that the caller holds the DUMP permission (or is system or root).
          *
          * @param message used as message if SecurityException is thrown
@@ -881,6 +875,13 @@
         private void enforceDumpPermission(@NonNull final String message) {
             getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, message);
         }
+
+        private void enforceActor(String packageName, String methodName, int userId)
+                throws SecurityException {
+            OverlayInfo overlayInfo = mImpl.getOverlayInfo(packageName, userId);
+            int callingUid = Binder.getCallingUid();
+            mActorEnforcer.enforceActor(overlayInfo, methodName, callingUid, userId);
+        }
     };
 
     private final class OverlayChangeListener
@@ -1035,9 +1036,16 @@
         }
     }
 
-    private static final class PackageManagerHelper implements
-            OverlayManagerServiceImpl.PackageManagerHelper {
+    /**
+     * Delegate for {@link android.content.pm.PackageManager} and {@link PackageManagerInternal}
+     * functionality, separated for easy testing.
+     *
+     * @hide
+     */
+    public static final class PackageManagerHelper implements
+            OverlayManagerServiceImpl.PackageManagerHelper, OverlayActorEnforcer.VerifyCallback {
 
+        private final Context mContext;
         private final IPackageManager mPackageManager;
         private final PackageManagerInternal mPackageManagerInternal;
 
@@ -1048,11 +1056,14 @@
         // behind until all pending intents have been processed.
         private final SparseArray<HashMap<String, PackageInfo>> mCache = new SparseArray<>();
 
-        PackageManagerHelper() {
+        PackageManagerHelper(Context context) {
+            mContext = context;
             mPackageManager = getPackageManager();
             mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         }
 
+        // TODO(b/143096091): Remove PackageInfo cache so that PackageManager is always queried
+        //  to enforce visibility/other permission checks
         public PackageInfo getPackageInfo(@NonNull final String packageName, final int userId,
                 final boolean useCache) {
             if (useCache) {
@@ -1075,7 +1086,19 @@
 
         @Override
         public PackageInfo getPackageInfo(@NonNull final String packageName, final int userId) {
-            return getPackageInfo(packageName, userId, true);
+            // TODO(b/143096091): Remove clearing calling ID
+            long callingIdentity = Binder.clearCallingIdentity();
+            try {
+                return getPackageInfo(packageName, userId, true);
+            } finally {
+                Binder.restoreCallingIdentity(callingIdentity);
+            }
+        }
+
+        @NonNull
+        @Override
+        public Map<String, ? extends Map<String, String>> getNamedActors() {
+            return SystemConfig.getInstance().getNamedActors();
         }
 
         @Override
@@ -1097,6 +1120,70 @@
             return mPackageManagerInternal.getOverlayPackages(userId);
         }
 
+        @Nullable
+        @Override
+        public OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
+                @Nullable String targetOverlayableName, int userId)
+                throws IOException {
+            // TODO(b/143096091): Remove clearing calling ID
+            long callingIdentity = Binder.clearCallingIdentity();
+            try {
+                PackageInfo packageInfo = getPackageInfo(packageName, userId);
+                if (packageInfo == null) {
+                    throw new IOException("Unable to get target package");
+                }
+
+                String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
+
+                ApkAssets apkAssets = null;
+                try {
+                    apkAssets = ApkAssets.loadFromPath(baseCodePath);
+                    return apkAssets.getOverlayableInfo(targetOverlayableName);
+                } finally {
+                    if (apkAssets != null) {
+                        try {
+                            apkAssets.close();
+                        } catch (Throwable ignored) {
+                        }
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(callingIdentity);
+            }
+        }
+
+        @Override
+        public boolean doesTargetDefineOverlayable(String targetPackageName, int userId)
+                throws RemoteException, IOException {
+            // TODO(b/143096091): Remove clearing calling ID
+            long callingIdentity = Binder.clearCallingIdentity();
+            try {
+                PackageInfo packageInfo = mPackageManager.getPackageInfo(targetPackageName, 0,
+                        userId);
+                String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
+
+                ApkAssets apkAssets = null;
+                try {
+                    apkAssets = ApkAssets.loadFromPath(baseCodePath);
+                    return apkAssets.definesOverlayable();
+                } finally {
+                    if (apkAssets != null) {
+                        try {
+                            apkAssets.close();
+                        } catch (Throwable ignored) {
+                        }
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(callingIdentity);
+            }
+        }
+
+        @Override
+        public void enforcePermission(String permission, String message) throws SecurityException {
+            mContext.enforceCallingOrSelfPermission(permission, message);
+        }
+
         public PackageInfo getCachedPackageInfo(@NonNull final String packageName,
                 final int userId) {
             final HashMap<String, PackageInfo> map = mCache.get(userId);
@@ -1128,6 +1215,22 @@
             mCache.delete(userId);
         }
 
+        @Nullable
+        @Override
+        public String[] getPackagesForUid(int uid) {
+            // TODO(b/143096091): Remove clearing calling ID
+            long callingIdentity = Binder.clearCallingIdentity();
+            try {
+                try {
+                    return mPackageManager.getPackagesForUid(uid);
+                } catch (RemoteException ignored) {
+                    return null;
+                }
+            } finally {
+                Binder.restoreCallingIdentity(callingIdentity);
+            }
+        }
+
         private static final String TAB1 = "    ";
         private static final String TAB2 = TAB1 + TAB1;