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

Commit a4da588e authored by Michael Groover's avatar Michael Groover
Browse files

Add support for module specified DeviceConfig namespace allowlists

Android 16 restricts the DeviceConfig namespaces / flags that can
be written by the shell user to those that have been allowlisted.
However, mainline modules can introduce new namespaces that are
intended to be modified by end users through adb shell; since
the platform may not be aware of these new namespaces, and the
allowlisted namespaces in the ConfigInfrastructure may not update
at the same time as the declaring APEX, the user will not be able
to modify the flags under the new namespaces. This commit adds
support to query for the etc/writable_namespaces file under the
APEX directory; any entries in this file will be added to the
DeviceConfig namespace allowlist.

Bug: 364083026
Flag: android.security.protect_device_config_flags
Test: atest DeviceConfigApiTests
Test: Manually installed APEX with writable_namespaces file and
      verified namespaces in this file could be modified by adb
Change-Id: I62a0c2a080c7860ee3dcf0885d20ffca05f5959e
parent 0df592ef
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -854,4 +854,6 @@ interface IPackageManager {
    boolean isPageSizeCompatEnabled(in String packageName);

    String getPageSizeCompatWarningMessage(in String packageName);

    List<String> getAllApexDirectories();
}
+63 −1
Original line number Diff line number Diff line
@@ -130,10 +130,12 @@ import com.google.android.collect.Sets;

import libcore.util.HexEncoding;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -380,6 +382,8 @@ public class SettingsProvider extends ContentProvider {
    @GuardedBy("mLock")
    private Handler mHandler;

    private static final Set<String> sDeviceConfigAllowlistedNamespaces = new ArraySet<>();

    // We have to call in the user manager with no lock held,
    private volatile UserManager mUserManager;

@@ -2442,6 +2446,10 @@ public class SettingsProvider extends ContentProvider {
        if (!isRestrictedShell && hasWritePermission) {
            assertCallingUserDenyList(flags);
        } else if (hasAllowlistPermission) {
            Set<String> allowlistedDeviceConfigNamespaces = null;
            if (isRestrictedShell) {
                allowlistedDeviceConfigNamespaces = getAllowlistedDeviceConfigNamespaces();
            }
            for (String flag : flags) {
                boolean namespaceAllowed = false;
                if (isRestrictedShell) {
@@ -2452,7 +2460,7 @@ public class SettingsProvider extends ContentProvider {
                    } else {
                        flagNamespace = flag;
                    }
                    if (WritableNamespaces.ALLOWLIST.contains(flagNamespace)) {
                    if (allowlistedDeviceConfigNamespaces.contains(flagNamespace)) {
                        namespaceAllowed = true;
                    }
                } else {
@@ -2513,6 +2521,60 @@ public class SettingsProvider extends ContentProvider {
        }
    }

    /**
     * Returns a Set of DeviceConfig allowlisted namespaces in which all flags can be modified
     * by a caller with the {@code WRITE_ALLOWLISTED_DEVICE_CONFIG} permission.
     * <p>
     * This method also supports mainline modules that introduce their own allowlisted
     * namespaces within the {@code etc/writable_namespaces} file under their directory.
     */
    private Set<String> getAllowlistedDeviceConfigNamespaces() {
        synchronized (sDeviceConfigAllowlistedNamespaces) {
            if (!sDeviceConfigAllowlistedNamespaces.isEmpty()) {
                return sDeviceConfigAllowlistedNamespaces;
            }
            if (android.provider.flags.Flags.deviceConfigWritableNamespacesApi()) {
                sDeviceConfigAllowlistedNamespaces.addAll(DeviceConfig.getAdbWritableNamespaces());
            } else {
                sDeviceConfigAllowlistedNamespaces.addAll(WritableNamespaces.ALLOWLIST);
            }
            final long identity = Binder.clearCallingIdentity();
            try {
                List<String> apexDirectories;
                try {
                    apexDirectories = mPackageManager.getAllApexDirectories();
                } catch (RemoteException e) {
                    Slog.e(LOG_TAG, "Caught a RemoteException obtaining APEX directories: ", e);
                    return sDeviceConfigAllowlistedNamespaces;
                }
                for (int i = 0; i < apexDirectories.size(); i++) {
                    String apexDirectory = apexDirectories.get(i);
                    File namespaceFile = Environment.buildPath(new File(apexDirectory), "etc",
                            "writable_namespaces");
                    if (namespaceFile.exists() && namespaceFile.isFile()) {
                        try (BufferedReader reader = new BufferedReader(
                                new FileReader(namespaceFile))) {
                            String namespace;
                            while ((namespace = reader.readLine()) != null) {
                                namespace = namespace.trim();
                                // Support comments by ignoring any lines that start with '#'.
                                if (!namespace.isEmpty() && !namespace.startsWith("#")) {
                                    sDeviceConfigAllowlistedNamespaces.add(namespace);
                                }
                            }
                        } catch (IOException e) {
                            Slog.e(LOG_TAG, "Caught an exception parsing file: " + namespaceFile,
                                    e);
                        }
                    }
                }
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
            return sDeviceConfigAllowlistedNamespaces;
        }
    }

    private static void warnOrThrowForUndesiredSecureSettingsMutationForTargetSdk(
            int targetSdkVersion, String name) {
        // If the app targets Lollipop MR1 or older SDK we warn, otherwise crash.
+14 −0
Original line number Diff line number Diff line
@@ -6639,6 +6639,20 @@ public class PackageManagerService implements PackageSender, TestUtilityService
            return agent;
        }

        @Override
        @NonNull
        public List<String> getAllApexDirectories() {
            PackageManagerServiceUtils.enforceSystemOrRoot(
                    "getAllApexDirectories can only be called by system or root");
            List<String> apexDirectories = new ArrayList<>();
            List<ApexManager.ActiveApexInfo> apexes = mApexManager.getActiveApexInfos();
            for (int i = 0; i < apexes.size(); i++) {
                ApexManager.ActiveApexInfo apex = apexes.get(i);
                apexDirectories.add(apex.apexDirectory.getAbsolutePath());
            }
            return apexDirectories;
        }

        @Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
                throws RemoteException {