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

Commit 71d8e5ff authored by Svetoslav Ganov's avatar Svetoslav Ganov Committed by Android (Google) Code Review
Browse files

Merge "Handle restricted permissions for shared UID components - framework" into qt-dev

parents 85ad2979 38a0631d
Loading
Loading
Loading
Loading
+130 −50
Original line number Diff line number Diff line
@@ -22,25 +22,28 @@ import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageManagerInternal.PackageListObserver;
import android.content.pm.PackageParser;
import android.content.pm.PermissionInfo;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManagerInternal;
import android.permission.PermissionControllerManager;
import android.permission.PermissionManagerInternal;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;

import com.android.internal.util.function.QuadConsumer;
import com.android.internal.util.function.TriConsumer;
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

/**
@@ -51,11 +54,17 @@ import java.util.concurrent.CountDownLatch;
 * and app ops - and vise versa.
 */
public final class PermissionPolicyService extends SystemService {
    private static final String PLATFORM_PACKAGE = "android";

    private static final String LOG_TAG = PermissionPolicyService.class.getSimpleName();

    // No need to lock as this is populated on boot when the OS is
    // single threaded and is never mutated until a reboot.
    private static final ArraySet<String> sAllRestrictedPermissions = new ArraySet<>();

    public PermissionPolicyService(@NonNull Context context) {
        super(context);
        cacheAllRestrictedPermissions(context);
    }

    @Override
@@ -89,6 +98,20 @@ public final class PermissionPolicyService extends SystemService {
        startWatchingRuntimePermissionChanges(getContext(), userId);
    }

    private static void cacheAllRestrictedPermissions(@NonNull Context context) {
        try {
            final PackageInfo packageInfo = context.getPackageManager()
                    .getPackageInfo(PLATFORM_PACKAGE, PackageManager.GET_PERMISSIONS);
            for (PermissionInfo permissionInfo : packageInfo.permissions) {
                if (permissionInfo.isRestricted()) {
                    sAllRestrictedPermissions.add(permissionInfo.name);
                }
            }
        } catch (NameNotFoundException impossible) {
            /* cannot happen */
        }
    }

    private static void grantOrUpgradeDefaultRuntimePermissionsInNeeded(@NonNull Context context,
            @UserIdInt int userId) {
        final PackageManagerInternal packageManagerInternal = LocalServices.getService(
@@ -123,16 +146,6 @@ public final class PermissionPolicyService extends SystemService {
        }
    }

    private static void onRestrictedPermissionEnabledChange(@NonNull Context context) {
        final PermissionManagerInternal permissionManagerInternal = LocalServices
                .getService(PermissionManagerInternal.class);
        final UserManagerInternal userManagerInternal = LocalServices.getService(
                UserManagerInternal.class);
        for (int userId : userManagerInternal.getUserIds()) {
            synchronizePermissionsAndAppOpsForUser(context, userId);
        }
    }

    private static void startWatchingRuntimePermissionChanges(@NonNull Context context,
            int userId) {
        final PermissionManagerInternal permissionManagerInternal = LocalServices.getService(
@@ -149,40 +162,66 @@ public final class PermissionPolicyService extends SystemService {
            @NonNull String packageName, @UserIdInt int userId) {
        final PackageManagerInternal packageManagerInternal = LocalServices.getService(
                PackageManagerInternal.class);
        final PackageParser.Package pkg = packageManagerInternal
                .getPackage(packageName);
        if (pkg != null) {
            PermissionToOpSynchronizer.syncPackage(context, pkg, userId);
        final PackageParser.Package pkg = packageManagerInternal.getPackage(packageName);
        if (pkg == null) {
            return;
        }
        final PermissionToOpSynchroniser synchroniser = new PermissionToOpSynchroniser(context);
        synchroniser.addPackage(context, pkg, userId);
        final String[] sharedPkgNames = packageManagerInternal.getPackagesForSharedUserId(
                pkg.mSharedUserId, userId);
        if (sharedPkgNames != null) {
            for (String sharedPkgName : sharedPkgNames) {
                final PackageParser.Package sharedPkg = packageManagerInternal
                        .getPackage(sharedPkgName);
                if (sharedPkg != null) {
                    synchroniser.addPackage(context, sharedPkg, userId);
                }
            }
        }
        synchroniser.syncPackages();
    }

    private static void synchronizePermissionsAndAppOpsForUser(@NonNull Context context,
            @UserIdInt int userId) {
        final PackageManagerInternal packageManagerInternal = LocalServices.getService(
                PackageManagerInternal.class);
        final PermissionToOpSynchronizer synchronizer = new PermissionToOpSynchronizer(context);
        final PermissionToOpSynchroniser synchronizer = new PermissionToOpSynchroniser(context);
        packageManagerInternal.forEachPackage((pkg) ->
                synchronizer.addPackage(context, pkg, userId));
        synchronizer.syncPackages();
    }

    private static class PermissionToOpSynchronizer {
    /**
     * Synchronizes permission to app ops. You *must* always sync all packages
     * in a shared UID at the same time to ensure proper synchronization.
     */
    private static class PermissionToOpSynchroniser {
        private final @NonNull Context mContext;

        private final @NonNull SparseIntArray mUids = new SparseIntArray();
        private final @NonNull SparseArray<String> mPackageNames = new SparseArray<>();
        private final @NonNull SparseIntArray mAllowedUidOps = new SparseIntArray();
        private final @NonNull SparseIntArray mDefaultUidOps = new SparseIntArray();

        PermissionToOpSynchronizer(@NonNull Context context) {
        PermissionToOpSynchroniser(@NonNull Context context) {
            mContext = context;
        }

        private void addPackage(@NonNull Context context,
                @NonNull PackageParser.Package pkg, @UserIdInt int userId) {
            addPackage(context, pkg, userId, this::addAllowedEntry, this::addIgnoredEntry);
        }

        void syncPackages() {
            // TRICKY: we set the app op for a restricted permission to allow if the app
            // requesting the permission is whitelisted and to deny if the app requesting
            // the permission is not whitelisted. However, there is another case where an
            // app in a shared user can access a component in another app in the same shared
            // user due to being in the same shared user and not by having the permission
            // that guards the component form the rest of the world. We need to handle this.
            // The way we do this is by setting app ops corresponding to non requested
            // restricted permissions to allow as this would allow the shared uid access
            // case and be okay for other apps as they would not have the permission and
            // would fail on the permission checks before reaching the app op check.
            final SparseArray<List<String>> unrequestedRestrictedPermissionsForUid =
                    new SparseArray<>();

            final AppOpsManager appOpsManager = mContext.getSystemService(AppOpsManager.class);
            final int allowedCount = mAllowedUidOps.size();
            for (int i = 0; i < allowedCount; i++) {
@@ -190,31 +229,84 @@ public final class PermissionPolicyService extends SystemService {
                final int uid = mAllowedUidOps.valueAt(i);
                final String packageName = mPackageNames.valueAt(i);
                setUidModeAllowed(appOpsManager, opCode, uid, packageName);

                // Keep track this permission was requested by the UID.
                List<String> unrequestedRestrictedPermissions =
                        unrequestedRestrictedPermissionsForUid.get(uid);
                if (unrequestedRestrictedPermissions == null) {
                    unrequestedRestrictedPermissions = new ArrayList<>(sAllRestrictedPermissions);
                    unrequestedRestrictedPermissionsForUid.put(uid,
                            unrequestedRestrictedPermissions);
                }
                unrequestedRestrictedPermissions.remove(AppOpsManager.opToPermission(opCode));

                mUids.delete(uid);
            }
            final int defaultCount = mDefaultUidOps.size();
            for (int i = 0; i < defaultCount; i++) {
                final int opCode = mDefaultUidOps.keyAt(i);
                final int uid = mDefaultUidOps.valueAt(i);
                setUidModeDefault(appOpsManager, opCode, uid);

                // Keep track this permission was requested by the UID.
                List<String> unrequestedRestrictedPermissions =
                        unrequestedRestrictedPermissionsForUid.get(uid);
                if (unrequestedRestrictedPermissions == null) {
                    unrequestedRestrictedPermissions = new ArrayList<>(sAllRestrictedPermissions);
                    unrequestedRestrictedPermissionsForUid.put(uid,
                            unrequestedRestrictedPermissions);
                }
                unrequestedRestrictedPermissions.remove(AppOpsManager.opToPermission(opCode));

                mUids.delete(uid);
            }

            // Give root access
            mUids.put(Process.ROOT_UID, Process.ROOT_UID);

            // Add records for UIDs that don't use any restricted permissions.
            final int uidCount = mUids.size();
            for (int i = 0; i < uidCount; i++) {
                final int uid = mUids.keyAt(i);
                unrequestedRestrictedPermissionsForUid.put(uid,
                        new ArrayList<>(sAllRestrictedPermissions));
            }

            // Flip ops for all unrequested restricted permission for the UIDs.
            final int unrequestedUidCount = unrequestedRestrictedPermissionsForUid.size();
            for (int i = 0; i < unrequestedUidCount; i++) {
                final List<String> unrequestedRestrictedPermissions =
                        unrequestedRestrictedPermissionsForUid.valueAt(i);
                if (unrequestedRestrictedPermissions != null) {
                    final int uid = unrequestedRestrictedPermissionsForUid.keyAt(i);
                    final String[] packageNames = (uid != Process.ROOT_UID)
                            ? mContext.getPackageManager().getPackagesForUid(uid)
                            : new String[] {"root"};
                    if (packageNames == null) {
                        continue;
                    }
                    final int permissionCount = unrequestedRestrictedPermissions.size();
                    for (int j = 0; j < permissionCount; j++) {
                        final String permission = unrequestedRestrictedPermissions.get(j);
                        for (String packageName : packageNames) {
                            setUidModeAllowed(appOpsManager,
                                    AppOpsManager.permissionToOpCode(permission), uid,
                                    packageName);
                        }
                    }
                }
            }

        static void syncPackage(@NonNull Context context, @NonNull PackageParser.Package pkg,
                @UserIdInt int userId) {
            addPackage(context, pkg, userId, PermissionToOpSynchronizer::setUidModeAllowed,
                    PermissionToOpSynchronizer::setUidModeDefault);
        }

        private static void addPackage(@NonNull Context context,
                @NonNull PackageParser.Package pkg, @UserIdInt int userId,
                @NonNull QuadConsumer<AppOpsManager, Integer, Integer, String> allowedConsumer,
                @NonNull TriConsumer<AppOpsManager, Integer, Integer> defaultConsumer) {
        private void addPackage(@NonNull Context context,
                @NonNull PackageParser.Package pkg, @UserIdInt int userId) {
            final PackageManager packageManager = context.getPackageManager();
            final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);

            final int uid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.applicationInfo.uid));
            final UserHandle userHandle = UserHandle.of(userId);

            mUids.put(uid, uid);

            final int permissionCount = pkg.requestedPermissions.size();
            for (int i = 0; i < permissionCount; i++) {
                final String permission = pkg.requestedPermissions.get(i);
@@ -241,9 +333,10 @@ public final class PermissionPolicyService extends SystemService {

                if (permissionInfo.isHardRestricted()) {
                    if (applyRestriction) {
                        defaultConsumer.accept(appOpsManager, opCode, uid);
                        mDefaultUidOps.put(opCode, uid);
                    } else {
                        allowedConsumer.accept(appOpsManager, opCode, uid, pkg.packageName);
                        mPackageNames.put(opCode, pkg.packageName);
                        mAllowedUidOps.put(opCode, uid);
                    }
                } else if (permissionInfo.isSoftRestricted()) {
                    //TODO: Implement soft restrictions like storage here.
@@ -251,19 +344,6 @@ public final class PermissionPolicyService extends SystemService {
            }
        }

        @SuppressWarnings("unused")
        private void addAllowedEntry(@NonNull AppOpsManager appOpsManager, int opCode,
                int uid, @NonNull String packageName) {
            mPackageNames.put(opCode, packageName);
            mAllowedUidOps.put(opCode, uid);
        }

        @SuppressWarnings("unused")
        private void addIgnoredEntry(@NonNull AppOpsManager appOpsManager,
                int opCode, int uid) {
            mDefaultUidOps.put(opCode, uid);
        }

        private static void setUidModeAllowed(@NonNull AppOpsManager appOpsManager,
                int opCode, int uid, @NonNull String packageName) {
            final int currentMode = appOpsManager.unsafeCheckOpRaw(AppOpsManager