Loading core/tests/coretests/src/android/provider/SettingsProviderTest.java +48 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import static org.hamcrest.Matchers.aMapWithSize; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.junit.Assert.assertThat; import android.app.UiAutomation; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; Loading @@ -38,6 +39,7 @@ import android.test.AndroidTestCase; import androidx.test.filters.MediumTest; import androidx.test.filters.SmallTest; import androidx.test.filters.Suppress; import androidx.test.platform.app.InstrumentationRegistry; import java.util.HashMap; import java.util.List; Loading Loading @@ -447,4 +449,50 @@ public class SettingsProviderTest extends AndroidTestCase { r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, newName, null); } } @SmallTest public void testCall_putOverrideConfig() throws Exception { // The shell user is restricted to a set of allowlisted flags / namespaces that can be // written. When an flag override is requested, the flag is rewritten to be in the form: // device_config_overrides/namespace:flagname // To avoid requiring allowlisting both the base flag and the override version, // SettingsProvider will parse out the overridden flag and check if it has been allowlisted. // This test verifies that this is properly handled for both the good case as well as when // the overridden flag is not in the proper format by ensuring a SecurityException is not // thrown since these flags have been allowlisted. UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); uiAutomation.adoptShellPermissionIdentity(); ContentResolver r = getContext().getContentResolver(); String overridesNamespace = "device_config_overrides"; String namespace = "namespace1"; String flagName = "key1"; String validFlag = overridesNamespace + "/" + namespace + ":" + flagName; String invalidFlag1 = overridesNamespace + "/"; String invalidFlag2 = overridesNamespace + "/" + namespace + ":"; String invalidFlag3 = overridesNamespace + "/" + ":"; String value = "value1"; Bundle args = new Bundle(); args.putString(Settings.NameValueTable.VALUE, value); try { r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, validFlag, args); r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, invalidFlag1, args); r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, invalidFlag2, args); r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, invalidFlag3, args); } finally { r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, validFlag, null); r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, invalidFlag1, null); r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, invalidFlag2, null); r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, invalidFlag3, null); uiAutomation.dropShellPermissionIdentity(); } } } packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +35 −6 Original line number Diff line number Diff line Loading @@ -385,6 +385,9 @@ public class SettingsProvider extends ContentProvider { private static final Set<String> sDeviceConfigAllowlistedNamespaces = new ArraySet<>(); // TODO(b/388901162): Remove this when the same constant is exposed as an API in DeviceConfig. private static final String DEVICE_CONFIG_OVERRIDES_NAMESPACE = "device_config_overrides"; // We have to call in the user manager with no lock held, private volatile UserManager mUserManager; Loading Loading @@ -2480,12 +2483,21 @@ public class SettingsProvider extends ContentProvider { for (String flag : flags) { boolean namespaceAllowed = false; if (isRestrictedShell) { int delimiterIndex = flag.indexOf("/"); String flagNamespace; if (delimiterIndex != -1) { flagNamespace = flag.substring(0, delimiterIndex); } else { flagNamespace = flag; String flagNamespace = getFlagNamespace(flag); // If the namespace indicates this is a flag override, then the actual // namespace and flag name should be used for the allowlist verification. if (DEVICE_CONFIG_OVERRIDES_NAMESPACE.equals(flagNamespace)) { // Override flags are in the following form: // device_config_overrides/namespace:flagName int slashIndex = flag.indexOf("/"); int colonIndex = flag.indexOf(":", slashIndex); if (slashIndex != -1 && colonIndex != -1 && (slashIndex + 1) < flag.length() && (colonIndex + 1) < flag.length()) { flagNamespace = flag.substring(slashIndex + 1, colonIndex); StringBuilder flagBuilder = new StringBuilder(flagNamespace); flagBuilder.append("/").append(flag.substring(colonIndex + 1)); flag = flagBuilder.toString(); } } if (allowlistedDeviceConfigNamespaces.contains(flagNamespace)) { namespaceAllowed = true; Loading @@ -2512,6 +2524,23 @@ public class SettingsProvider extends ContentProvider { } } /** * Returns the namespace for the provided {@code flag}. * <p> * Flags are expected to be in the form namespace/flagName; if the '/' delimiter does * not exist, then the provided flag is returned as the namespace. */ private static String getFlagNamespace(String flag) { int delimiterIndex = flag.indexOf("/"); String flagNamespace; if (delimiterIndex != -1) { flagNamespace = flag.substring(0, delimiterIndex); } else { flagNamespace = flag; } return flagNamespace; } // The check is added mainly for auto devices. On auto devices, it is possible that // multiple users are visible simultaneously using visible background users. // In such cases, it is desired that Non-current user (ex. visible background users) can Loading Loading
core/tests/coretests/src/android/provider/SettingsProviderTest.java +48 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import static org.hamcrest.Matchers.aMapWithSize; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.junit.Assert.assertThat; import android.app.UiAutomation; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; Loading @@ -38,6 +39,7 @@ import android.test.AndroidTestCase; import androidx.test.filters.MediumTest; import androidx.test.filters.SmallTest; import androidx.test.filters.Suppress; import androidx.test.platform.app.InstrumentationRegistry; import java.util.HashMap; import java.util.List; Loading Loading @@ -447,4 +449,50 @@ public class SettingsProviderTest extends AndroidTestCase { r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, newName, null); } } @SmallTest public void testCall_putOverrideConfig() throws Exception { // The shell user is restricted to a set of allowlisted flags / namespaces that can be // written. When an flag override is requested, the flag is rewritten to be in the form: // device_config_overrides/namespace:flagname // To avoid requiring allowlisting both the base flag and the override version, // SettingsProvider will parse out the overridden flag and check if it has been allowlisted. // This test verifies that this is properly handled for both the good case as well as when // the overridden flag is not in the proper format by ensuring a SecurityException is not // thrown since these flags have been allowlisted. UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); uiAutomation.adoptShellPermissionIdentity(); ContentResolver r = getContext().getContentResolver(); String overridesNamespace = "device_config_overrides"; String namespace = "namespace1"; String flagName = "key1"; String validFlag = overridesNamespace + "/" + namespace + ":" + flagName; String invalidFlag1 = overridesNamespace + "/"; String invalidFlag2 = overridesNamespace + "/" + namespace + ":"; String invalidFlag3 = overridesNamespace + "/" + ":"; String value = "value1"; Bundle args = new Bundle(); args.putString(Settings.NameValueTable.VALUE, value); try { r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, validFlag, args); r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, invalidFlag1, args); r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, invalidFlag2, args); r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, invalidFlag3, args); } finally { r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, validFlag, null); r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, invalidFlag1, null); r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, invalidFlag2, null); r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, invalidFlag3, null); uiAutomation.dropShellPermissionIdentity(); } } }
packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +35 −6 Original line number Diff line number Diff line Loading @@ -385,6 +385,9 @@ public class SettingsProvider extends ContentProvider { private static final Set<String> sDeviceConfigAllowlistedNamespaces = new ArraySet<>(); // TODO(b/388901162): Remove this when the same constant is exposed as an API in DeviceConfig. private static final String DEVICE_CONFIG_OVERRIDES_NAMESPACE = "device_config_overrides"; // We have to call in the user manager with no lock held, private volatile UserManager mUserManager; Loading Loading @@ -2480,12 +2483,21 @@ public class SettingsProvider extends ContentProvider { for (String flag : flags) { boolean namespaceAllowed = false; if (isRestrictedShell) { int delimiterIndex = flag.indexOf("/"); String flagNamespace; if (delimiterIndex != -1) { flagNamespace = flag.substring(0, delimiterIndex); } else { flagNamespace = flag; String flagNamespace = getFlagNamespace(flag); // If the namespace indicates this is a flag override, then the actual // namespace and flag name should be used for the allowlist verification. if (DEVICE_CONFIG_OVERRIDES_NAMESPACE.equals(flagNamespace)) { // Override flags are in the following form: // device_config_overrides/namespace:flagName int slashIndex = flag.indexOf("/"); int colonIndex = flag.indexOf(":", slashIndex); if (slashIndex != -1 && colonIndex != -1 && (slashIndex + 1) < flag.length() && (colonIndex + 1) < flag.length()) { flagNamespace = flag.substring(slashIndex + 1, colonIndex); StringBuilder flagBuilder = new StringBuilder(flagNamespace); flagBuilder.append("/").append(flag.substring(colonIndex + 1)); flag = flagBuilder.toString(); } } if (allowlistedDeviceConfigNamespaces.contains(flagNamespace)) { namespaceAllowed = true; Loading @@ -2512,6 +2524,23 @@ public class SettingsProvider extends ContentProvider { } } /** * Returns the namespace for the provided {@code flag}. * <p> * Flags are expected to be in the form namespace/flagName; if the '/' delimiter does * not exist, then the provided flag is returned as the namespace. */ private static String getFlagNamespace(String flag) { int delimiterIndex = flag.indexOf("/"); String flagNamespace; if (delimiterIndex != -1) { flagNamespace = flag.substring(0, delimiterIndex); } else { flagNamespace = flag; } return flagNamespace; } // The check is added mainly for auto devices. On auto devices, it is possible that // multiple users are visible simultaneously using visible background users. // In such cases, it is desired that Non-current user (ex. visible background users) can Loading