Add ephemeral whitelist for SettingsProvider

Currently the list is small, only whats required to launch a basic
ephemeral app. It will expand in followup CLs.

Note that the goal of this is not to completely shut down all ways that
an ephemeral app could learn the value (or part of) of a setting not in
the set. The goal is to limit the raw access to settings to a small set that
includes settings that ephemeral apps should have access to directly
System APIs that are exposed to ephemeral apps may allow for
ephemeral apps to learn the value of settings not in the directly
exposed set and that is OK and _not_ a security issue.

This contains a hack to support code in system system server that in
the process of a binder transaction reads a setting using a
ContentReceiver with a system package name. This was previously not an
issue but causes an exception to be thrown from getCallingPackage which
reading a setting now calls.

Bug: 33349998
Test: Boots, functions as normal for regular apps.
Test: cts-tradefed run cts -m CtsProviderTestCases -t
android.provider.cts.SettingsTest

Change-Id: Icc839b0d98c725d23cdd395e8cb76a7b293f8767
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1865652..ce2774d 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -50,6 +50,7 @@
 import android.net.Uri;
 import android.net.wifi.WifiManager;
 import android.os.BatteryManager;
+import android.os.Binder;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
 import android.os.DropBoxManager;
@@ -1581,6 +1582,24 @@
     // with a partial enable/disable state in multi-threaded situations.
     private static final Object mLocationSettingsLock = new Object();
 
+    // Used in system server calling uid workaround in call()
+    private static boolean sInSystemServer = false;
+    private static final Object sInSystemServerLock = new Object();
+
+    /** @hide */
+    public static void setInSystemServer() {
+        synchronized (sInSystemServerLock) {
+            sInSystemServer = true;
+        }
+    }
+
+    /** @hide */
+    public static boolean isInSystemServer() {
+        synchronized (sInSystemServerLock) {
+            return sInSystemServer;
+        }
+    }
+
     public static class SettingNotFoundException extends AndroidException {
         public SettingNotFoundException(String msg) {
             super(msg);
@@ -1789,7 +1808,23 @@
                             }
                         }
                     }
-                    Bundle b = cp.call(cr.getPackageName(), mCallGetCommand, name, args);
+                    Bundle b;
+                    // If we're in system server and in a binder transaction we need to clear the
+                    // calling uid. This works around code in system server that did not call
+                    // clearCallingIdentity, previously this wasn't needed because reading settings
+                    // did not do permission checking but thats no longer the case.
+                    // Long term this should be removed and callers should properly call
+                    // clearCallingIdentity or use a ContentResolver from the caller as needed.
+                    if (Settings.isInSystemServer() && Binder.getCallingUid() != Process.myUid()) {
+                        final long token = Binder.clearCallingIdentity();
+                        try {
+                            b = cp.call(cr.getPackageName(), mCallGetCommand, name, args);
+                        } finally {
+                            Binder.restoreCallingIdentity(token);
+                        }
+                    } else {
+                        b = cp.call(cr.getPackageName(), mCallGetCommand, name, args);
+                    }
                     if (b != null) {
                         String value = b.getString(Settings.NameValueTable.VALUE);
                         // Don't update our cache for reads of other users' data
@@ -1849,7 +1884,19 @@
             try {
                 Bundle queryArgs = ContentResolver.createSqlQueryBundle(
                         NAME_EQ_PLACEHOLDER, new String[]{name}, null);
-                c = cp.query(cr.getPackageName(), mUri, SELECT_VALUE_PROJECTION, queryArgs, null);
+                // Same workaround as above.
+                if (Settings.isInSystemServer() && Binder.getCallingUid() != Process.myUid()) {
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        c = cp.query(cr.getPackageName(), mUri, SELECT_VALUE_PROJECTION, queryArgs,
+                                null);
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                } else {
+                    c = cp.query(cr.getPackageName(), mUri, SELECT_VALUE_PROJECTION, queryArgs,
+                            null);
+                }
                 if (c == null) {
                     Log.w(TAG, "Can't get key " + name + " from " + mUri);
                     return null;
@@ -4006,6 +4053,22 @@
         }
 
         /**
+         * System settings which can be accessed by ephemeral apps.
+         * @hide
+         */
+        public static final Set<String> EPHEMERAL_SETTINGS = new ArraySet<>();
+        static {
+            EPHEMERAL_SETTINGS.add(TEXT_AUTO_REPLACE);
+            EPHEMERAL_SETTINGS.add(TEXT_AUTO_CAPS);
+            EPHEMERAL_SETTINGS.add(TEXT_AUTO_PUNCTUATE);
+            EPHEMERAL_SETTINGS.add(TEXT_SHOW_PASSWORD);
+            EPHEMERAL_SETTINGS.add(DATE_FORMAT);
+            EPHEMERAL_SETTINGS.add(FONT_SCALE);
+            EPHEMERAL_SETTINGS.add(HAPTIC_FEEDBACK_ENABLED);
+            EPHEMERAL_SETTINGS.add(TIME_12_24);
+        }
+
+        /**
          * When to use Wi-Fi calling
          *
          * @see android.telephony.TelephonyManager.WifiCallingChoices
@@ -6867,6 +6930,20 @@
         }
 
         /**
+         * Secure settings which can be accessed by ephemeral apps.
+         * @hide
+         */
+        public static final Set<String> EPHEMERAL_SETTINGS = new ArraySet<>();
+        static {
+            EPHEMERAL_SETTINGS.add(ENABLED_ACCESSIBILITY_SERVICES);
+            EPHEMERAL_SETTINGS.add(ACCESSIBILITY_SPEAK_PASSWORD);
+            EPHEMERAL_SETTINGS.add(ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
+
+            EPHEMERAL_SETTINGS.add(DEFAULT_INPUT_METHOD);
+            EPHEMERAL_SETTINGS.add(ENABLED_INPUT_METHODS);
+        }
+
+        /**
          * Helper method for determining if a location provider is enabled.
          *
          * @param cr the content resolver to use
@@ -9863,6 +9940,20 @@
          * @hide
          */
         public static final String CELL_ON = "cell_on";
+
+        /**
+         * Global settings which can be accessed by ephemeral apps.
+         * @hide
+         */
+        public static final Set<String> EPHEMERAL_SETTINGS = new ArraySet<>();
+        static {
+            EPHEMERAL_SETTINGS.add(WAIT_FOR_DEBUGGER);
+            EPHEMERAL_SETTINGS.add(DEVICE_PROVISIONED);
+            EPHEMERAL_SETTINGS.add(DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES);
+            EPHEMERAL_SETTINGS.add(DEVELOPMENT_FORCE_RTL);
+            EPHEMERAL_SETTINGS.add(EPHEMERAL_COOKIE_MAX_SIZE_BYTES);
+        }
+
     }
 
     /**
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 3e62158..a29a46d 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -262,6 +262,7 @@
 
     @Override
     public boolean onCreate() {
+        Settings.setInSystemServer();
         synchronized (mLock) {
             mUserManager = UserManager.get(getContext());
             mPackageManager = AppGlobals.getPackageManager();
@@ -813,7 +814,8 @@
             SettingsState settingsState = mSettingsRegistry.getSettingsLocked(
                     SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM);
 
-            List<String> names = settingsState.getSettingNamesLocked();
+            List<String> names = getSettingsNamesLocked(SETTINGS_TYPE_GLOBAL,
+                    UserHandle.USER_SYSTEM);
 
             final int nameCount = names.size();
 
@@ -836,6 +838,9 @@
             Slog.v(LOG_TAG, "getGlobalSetting(" + name + ")");
         }
 
+        // Ensure the caller can access the setting.
+        enforceSettingReadable(name, SETTINGS_TYPE_GLOBAL, UserHandle.getCallingUserId());
+
         // Get the value.
         synchronized (mLock) {
             return mSettingsRegistry.getSettingLocked(SETTINGS_TYPE_GLOBAL,
@@ -938,8 +943,7 @@
         final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(userId);
 
         synchronized (mLock) {
-            List<String> names = mSettingsRegistry.getSettingsNamesLocked(
-                    SETTINGS_TYPE_SECURE, callingUserId);
+            List<String> names = getSettingsNamesLocked(SETTINGS_TYPE_SECURE, callingUserId);
 
             final int nameCount = names.size();
 
@@ -974,6 +978,9 @@
         // Resolve the userId on whose behalf the call is made.
         final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);
 
+        // Ensure the caller can access the setting.
+        enforceSettingReadable(name, SETTINGS_TYPE_SECURE, callingUserId);
+
         // Determine the owning user as some profile settings are cloned from the parent.
         final int owningUserId = resolveOwningUserIdForSecureSettingLocked(callingUserId, name);
 
@@ -1104,8 +1111,7 @@
         final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(userId);
 
         synchronized (mLock) {
-            List<String> names = mSettingsRegistry.getSettingsNamesLocked(
-                    SETTINGS_TYPE_SYSTEM, callingUserId);
+            List<String> names = getSettingsNamesLocked(SETTINGS_TYPE_SYSTEM, callingUserId);
 
             final int nameCount = names.size();
 
@@ -1136,6 +1142,9 @@
         // Resolve the userId on whose behalf the call is made.
         final int callingUserId = resolveCallingUserIdEnforcingPermissionsLocked(requestingUserId);
 
+        // Ensure the caller can access the setting.
+        enforceSettingReadable(name, SETTINGS_TYPE_SYSTEM, callingUserId);
+
         // Determine the owning user as some profile settings are cloned from the parent.
         final int owningUserId = resolveOwningUserIdForSystemSettingLocked(callingUserId, name);
 
@@ -1354,9 +1363,15 @@
                 && (parentId = getGroupParentLocked(userId)) != userId) {
             // The setting has a dependency and the profile has a parent
             String dependency = sSystemCloneFromParentOnDependency.get(setting);
-            Setting settingObj = getSecureSetting(dependency, userId);
-            if (settingObj != null && settingObj.getValue().equals("1")) {
-                return parentId;
+            // Lookup the dependency setting as ourselves, some callers may not have access to it.
+            final long token = Binder.clearCallingIdentity();
+            try {
+                Setting settingObj = getSecureSetting(dependency, userId);
+                if (settingObj != null && settingObj.getValue().equals("1")) {
+                    return parentId;
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
             }
         }
         return resolveOwningUserIdLocked(userId, sSystemCloneToManagedSettings, setting);
@@ -1424,6 +1439,55 @@
         }
     }
 
+    private Set<String> getEphemeralAccessibleSettings(int settingsType) {
+        switch (settingsType) {
+            case SETTINGS_TYPE_GLOBAL:
+                return Settings.Global.EPHEMERAL_SETTINGS;
+            case SETTINGS_TYPE_SECURE:
+                return Settings.Secure.EPHEMERAL_SETTINGS;
+            case SETTINGS_TYPE_SYSTEM:
+                return Settings.System.EPHEMERAL_SETTINGS;
+            default:
+                throw new IllegalArgumentException("Invalid settings type: " + settingsType);
+        }
+    }
+
+    private List<String> getSettingsNamesLocked(int settingsType, int userId) {
+        ApplicationInfo ai = getCallingApplicationInfoOrThrow(userId);
+        if (ai.isEphemeralApp()) {
+            return new ArrayList<String>(getEphemeralAccessibleSettings(settingsType));
+        } else {
+            return mSettingsRegistry.getSettingsNamesLocked(settingsType, userId);
+        }
+    }
+
+    private void enforceSettingReadable(String settingName, int settingsType, int userId) {
+        if (UserHandle.getAppId(Binder.getCallingUid()) < Process.FIRST_APPLICATION_UID) {
+            return;
+        }
+        ApplicationInfo ai = getCallingApplicationInfoOrThrow(userId);
+        if (!ai.isEphemeralApp()) {
+            return;
+        }
+        if (!getEphemeralAccessibleSettings(settingsType).contains(settingName)) {
+            throw new SecurityException("Setting " + settingName + " is not accessible from"
+                    + " ephemeral package " + getCallingPackage());
+        }
+    }
+
+    private ApplicationInfo getCallingApplicationInfoOrThrow(int userId) {
+        ApplicationInfo ai = null;
+        try {
+            ai = mPackageManager.getApplicationInfo(getCallingPackage(), 0 , userId);
+        } catch (RemoteException ignored) {
+        }
+        if (ai == null) {
+            throw new IllegalStateException("Failed to lookup info for package "
+                    + getCallingPackage());
+        }
+        return ai;
+    }
+
     private PackageInfo getCallingPackageInfoOrThrow(int userId) {
         try {
             PackageInfo packageInfo = mPackageManager.getPackageInfo(
@@ -1493,7 +1557,7 @@
         value = value.substring(1);
 
         Setting settingValue = getSecureSetting(
-                Settings.Secure.LOCATION_PROVIDERS_ALLOWED, owningUserId);
+                    Settings.Secure.LOCATION_PROVIDERS_ALLOWED, owningUserId);
         if (settingValue == null) {
             return false;
         }