Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 19e5acd4 authored by Michael Groover's avatar Michael Groover
Browse files

Check original DeviceConfig flag in allowlist for overrides

Android 16 limits the DeviceConfig flags that can be written by
the shell user to those that have been allowlisted. When a flag
is modified through the DeviceConfig#setLocalOverride method,
the requested flag is placed in the device_config_overrides
namespace, and the flag in this new namespace is used during the
allowlist check. Because of this new namespace, flags that were
previously allowlisted would fail the check without a new entry
in the allowlist using the device_config_overrides namespace.
This commit checks for this override namespace, obtains the
original flag, and uses that flag to verify whether the shell
user can modify it..

Bug: 388608113
Flag: android.security.protect_device_config_flags
Test: atest DeviceConfigApiTests
Test: atest android.provider.SettingsProviderTest
Change-Id: Ia5853fc1399bdb3b4767ae65badf0298acda31ea
parent 13280939
Loading
Loading
Loading
Loading
+48 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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();
        }
    }
}
+35 −6
Original line number Diff line number Diff line
@@ -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;

@@ -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;
@@ -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