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

Commit 400d707d authored by Sergey Nikolaienkov's avatar Sergey Nikolaienkov
Browse files

Check REQUEST_COMPANION_SELF_MANAGED permission in CDM

Check if the caller holds REQUEST_COMPANION_SELF_MANAGED permission when
processing an AssociationRequest for a "self-managed" association in
CDM.

Introduce RolesUtils and PermissionsUtils utility classes.

Bug: 194301022
Test: atest CompanionDeviceManagerTest
Change-Id: Ia359f912fd28950a1251492f7e7b8452852008b6
parent a0f4611d
Loading
Loading
Loading
Loading
+23 −74
Original line number Diff line number Diff line
@@ -16,24 +16,21 @@

package com.android.server.companion;

import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;

import static com.android.internal.util.CollectionUtils.filter;
import static com.android.internal.util.FunctionalUtils.uncheckExceptions;
import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;
import static com.android.server.companion.PermissionsUtils.enforceCallerCanInteractWithUserId;
import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOr;
import static com.android.server.companion.PermissionsUtils.enforceRequestDeviceProfilePermissions;
import static com.android.server.companion.PermissionsUtils.enforceRequestSelfManagedPermission;
import static com.android.server.companion.RolesUtils.isRoleHolder;

import static java.util.Collections.unmodifiableMap;
import static java.util.Objects.requireNonNull;

import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.role.RoleManager;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
@@ -46,8 +43,6 @@ import android.content.pm.Signature;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.PackageUtils;
import android.util.Slog;

@@ -60,26 +55,12 @@ import com.android.server.FgThread;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

class AssociationRequestsProcessor {
    private static final String TAG = LOG_TAG + ".AssociationRequestsProcessor";

    private static final Map<String, String> DEVICE_PROFILE_TO_PERMISSION;
    static {
        final Map<String, String> map = new ArrayMap<>();
        map.put(DEVICE_PROFILE_WATCH, Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH);
        map.put(DEVICE_PROFILE_APP_STREAMING,
                Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING);
        map.put(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION,
                Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION);

        DEVICE_PROFILE_TO_PERMISSION = unmodifiableMap(map);
    }

    private static final ComponentName SERVICE_TO_BIND_TO = ComponentName.createRelative(
            CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME,
            ".CompanionDeviceDiscoveryService");
@@ -88,7 +69,6 @@ class AssociationRequestsProcessor {
    private static final long ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS = 60 * 60 * 1000; // 60 min;

    private final Context mContext;
    private final RoleManager mRoleManager;
    private final CompanionDeviceManagerService mService;
    private final PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>> mServiceConnectors;

@@ -96,10 +76,9 @@ class AssociationRequestsProcessor {
    private IAssociationRequestCallback mAppCallback;
    private AndroidFuture<?> mOngoingDeviceDiscovery;

    AssociationRequestsProcessor(CompanionDeviceManagerService service, RoleManager roleManager) {
    AssociationRequestsProcessor(CompanionDeviceManagerService service) {
        mContext = service.getContext();
        mService = service;
        mRoleManager = roleManager;

        final Intent serviceIntent = new Intent().setComponent(SERVICE_TO_BIND_TO);
        mServiceConnectors = new PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>>() {
@@ -129,13 +108,17 @@ class AssociationRequestsProcessor {
                    + "package=u" + userId + "/" + packageName);
        }

        mService.enforceCallerCanInteractWithUserId(userId);
        mService.enforceCallerIsSystemOr(userId, packageName);
        enforceCallerCanInteractWithUserId(mContext, userId);
        enforceCallerIsSystemOr(userId, packageName);

        mService.checkUsesFeature(packageName, userId);

        if (request.isSelfManaged()) {
            enforceRequestSelfManagedPermission(mContext);
        }

        final String deviceProfile = request.getDeviceProfile();
        validateDeviceProfileAndCheckPermission(deviceProfile);
        enforceRequestDeviceProfilePermissions(mContext, deviceProfile);

        synchronized (mService.mLock) {
            if (mRequest != null) {
@@ -190,9 +173,8 @@ class AssociationRequestsProcessor {
                    if (err == null) {
                        mService.createAssociationInternal(
                                userId, deviceAddress, packageName, deviceProfile);
                        mServiceConnectors.forUser(userId).post(service -> {
                            service.onAssociationCreated();
                        });
                        mServiceConnectors.forUser(userId).post(
                                ICompanionDeviceDiscoveryService::onAssociationCreated);
                    } else {
                        Slog.e(TAG, "Failed to discover device(s)", err);
                        callback.onFailure("No devices found: " + err.getMessage());
@@ -201,43 +183,6 @@ class AssociationRequestsProcessor {
                }));
    }

    private boolean isRoleHolder(int userId, String packageName, String role) {
        final long identity = Binder.clearCallingIdentity();
        try {
            List<String> holders = mRoleManager.getRoleHoldersAsUser(role, UserHandle.of(userId));
            return holders.contains(packageName);
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    private void validateDeviceProfileAndCheckPermission(@Nullable String deviceProfile) {
        // Device profile can be null.
        if (deviceProfile == null) return;

        if (DEVICE_PROFILE_APP_STREAMING.equals(deviceProfile)) {
            // TODO: remove, when properly supporting this profile.
            throw new UnsupportedOperationException(
                    "DEVICE_PROFILE_APP_STREAMING is not fully supported yet.");
        }

        if (DEVICE_PROFILE_AUTOMOTIVE_PROJECTION.equals(deviceProfile)) {
            // TODO: remove, when properly supporting this profile.
            throw new UnsupportedOperationException(
                    "DEVICE_PROFILE_AUTOMOTIVE_PROJECTION is not fully supported yet.");
        }

        if (!DEVICE_PROFILE_TO_PERMISSION.containsKey(deviceProfile)) {
            throw new IllegalArgumentException("Unsupported device profile: " + deviceProfile);
        }

        final String permission = DEVICE_PROFILE_TO_PERMISSION.get(deviceProfile);
        if (mContext.checkCallingOrSelfPermission(permission) != PERMISSION_GRANTED) {
            throw new SecurityException("Application must hold " + permission + " to associate "
                    + "with a device with " + deviceProfile + " profile.");
        }
    }

    private void cleanup() {
        if (DEBUG) {
            Slog.d(TAG, "cleanup(); discovery = "
@@ -257,11 +202,15 @@ class AssociationRequestsProcessor {
    }

    private boolean mayAssociateWithoutPrompt(String packageName, int userId) {
        if (mRequest.getDeviceProfile() != null
                && isRoleHolder(userId, packageName, mRequest.getDeviceProfile())) {
        final String deviceProfile = mRequest.getDeviceProfile();
        if (deviceProfile != null) {
            final boolean isRoleHolder = Binder.withCleanCallingIdentity(
                    () -> isRoleHolder(mContext, userId, packageName, deviceProfile));
            if (isRoleHolder) {
                // Don't need to collect user's consent since app already holds the role.
                return true;
            }
        }

        String[] sameOemPackages = mContext.getResources()
                .getStringArray(com.android.internal.R.array.config_companionDevicePackages);
+24 −127
Original line number Diff line number Diff line
@@ -17,15 +17,12 @@

package com.android.server.companion;

import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES;
import static android.bluetooth.le.ScanSettings.SCAN_MODE_BALANCED;
import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
import static android.content.pm.PackageManager.FEATURE_COMPANION_DEVICE_SETUP;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Binder.getCallingUid;
import static android.os.Process.SYSTEM_UID;
import static android.os.UserHandle.getCallingUserId;

@@ -38,6 +35,13 @@ import static com.android.internal.util.CollectionUtils.map;
import static com.android.internal.util.Preconditions.checkState;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import static com.android.internal.util.function.pooled.PooledLambda.obtainRunnable;
import static com.android.server.companion.PermissionsUtils.checkCallerCanManageAssociationsForPackage;
import static com.android.server.companion.PermissionsUtils.checkCallerCanManagerCompanionDevice;
import static com.android.server.companion.PermissionsUtils.enforceCallerCanInteractWithUserId;
import static com.android.server.companion.PermissionsUtils.enforceCallerCanManagerCompanionDevice;
import static com.android.server.companion.PermissionsUtils.enforceCallerIsSystemOr;
import static com.android.server.companion.RolesUtils.addRoleHolderForAssociation;
import static com.android.server.companion.RolesUtils.removeRoleHolderForAssociation;

import static java.util.Collections.emptySet;
import static java.util.Collections.unmodifiableSet;
@@ -52,7 +56,6 @@ import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.role.RoleManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.le.BluetoothLeScanner;
@@ -162,7 +165,6 @@ public class CompanionDeviceManagerService extends SystemService {
    private final AssociationRequestsProcessor mAssociationRequestsProcessor;
    private PowerWhitelistManager mPowerWhitelistManager;
    private IAppOpsService mAppOpsManager;
    private RoleManager mRoleManager;
    private BluetoothAdapter mBluetoothAdapter;
    private UserManager mUserManager;

@@ -205,7 +207,6 @@ public class CompanionDeviceManagerService extends SystemService {
        mPersistentDataStore = new PersistentDataStore();

        mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class);
        mRoleManager = context.getSystemService(RoleManager.class);
        mAppOpsManager = IAppOpsService.Stub.asInterface(
                ServiceManager.getService(Context.APP_OPS_SERVICE));
        mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
@@ -215,7 +216,7 @@ public class CompanionDeviceManagerService extends SystemService {
                context.getSystemService(PermissionControllerManager.class));
        mUserManager = context.getSystemService(UserManager.class);
        mCompanionDevicePresenceController = new CompanionDevicePresenceController();
        mAssociationRequestsProcessor = new AssociationRequestsProcessor(this, mRoleManager);
        mAssociationRequestsProcessor = new AssociationRequestsProcessor(this);

        registerPackageMonitor();
    }
@@ -328,8 +329,9 @@ public class CompanionDeviceManagerService extends SystemService {

        final int userId = association.getUserId();
        final String packageName = association.getPackageName();

        if (!checkCallerCanManageAssociationsForPackage(userId, packageName)) return null;
        if (!checkCallerCanManageAssociationsForPackage(getContext(), userId, packageName)) {
            return null;
        }

        return association;
    }
@@ -397,18 +399,17 @@ public class CompanionDeviceManagerService extends SystemService {
                    + "request=" + request + ", "
                    + "package=u" + userId + "/" + packageName);
            mAssociationRequestsProcessor.process(request, packageName, userId, callback);

        }

        @Override
        public List<AssociationInfo> getAssociations(String packageName, int userId) {
            final int callingUid = getCallingUserId();
            if (!checkCallerCanManageAssociationsForPackage(userId, packageName)) {
            if (!checkCallerCanManageAssociationsForPackage(getContext(), userId, packageName)) {
                throw new SecurityException("Caller (uid=" + callingUid + ") does not have "
                        + "permissions to get associations for u" + userId + "/" + packageName);
            }

            if (!checkCallerCanManagerCompanionDevice()) {
            if (!checkCallerCanManagerCompanionDevice(getContext())) {
                // If the caller neither is system nor holds MANAGE_COMPANION_DEVICES: it needs to
                // request the feature (also: the caller is the app itself).
                checkUsesFeature(packageName, getCallingUserId());
@@ -420,8 +421,8 @@ public class CompanionDeviceManagerService extends SystemService {

        @Override
        public List<AssociationInfo> getAllAssociationsForUser(int userId) throws RemoteException {
            enforceCallerCanInteractWithUserId(userId);
            enforceCallerCanManagerCompanionDevice();
            enforceCallerCanInteractWithUserId(getContext(), userId);
            enforceCallerCanManagerCompanionDevice(getContext(), "getAllAssociationsForUser");

            return new ArrayList<>(
                    CompanionDeviceManagerService.this.getAllAssociationsForUser(userId));
@@ -430,8 +431,9 @@ public class CompanionDeviceManagerService extends SystemService {
        @Override
        public void addOnAssociationsChangedListener(IOnAssociationsChangedListener listener,
                int userId) {
            enforceCallerCanInteractWithUserId(userId);
            enforceCallerCanManagerCompanionDevice();
            enforceCallerCanInteractWithUserId(getContext(), userId);
            enforceCallerCanManagerCompanionDevice(getContext(),
                    "addOnAssociationsChangedListener");

            //TODO: Implement.
        }
@@ -617,7 +619,7 @@ public class CompanionDeviceManagerService extends SystemService {
        public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
                String[] args, ShellCallback callback, ResultReceiver resultReceiver)
                throws RemoteException {
            enforceCallerCanManagerCompanionDevice();
            enforceCallerCanManagerCompanionDevice(getContext(), "onShellCommand");
            new CompanionDeviceShellCommand(CompanionDeviceManagerService.this)
                    .exec(this, in, out, err, args, callback, resultReceiver);
        }
@@ -767,25 +769,8 @@ public class CompanionDeviceManagerService extends SystemService {
                        + " for " + association
                        + " - profile still present in " + otherAssociationWithDeviceProfile);
            } else {
                final long identity = Binder.clearCallingIdentity();
                try {
                    mRoleManager.removeRoleHolderAsUser(
                            association.getDeviceProfile(),
                            association.getPackageName(),
                            RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP,
                            UserHandle.of(association.getUserId()),
                            getContext().getMainExecutor(),
                            success -> {
                                if (!success) {
                                    Slog.e(LOG_TAG, "Failed to revoke device profile role "
                                            + association.getDeviceProfile()
                                            + " to " + association.getPackageName()
                                            + " for user " + association.getUserId());
                                }
                            });
                } finally {
                    Binder.restoreCallingIdentity(identity);
                }
                Binder.withCleanCallingIdentity(
                        () -> removeRoleHolderForAssociation(getContext(), association));
            }
        }
    }
@@ -835,7 +820,7 @@ public class CompanionDeviceManagerService extends SystemService {

        if (!association.isSelfManaged()) {
            if (mCurrentlyConnectedDevices.contains(association.getDeviceMacAddressAsString())) {
                grantDeviceProfile(association);
                addRoleHolderForAssociation(getContext(), association);
            }

            if (association.isNotifyOnDeviceNearby()) {
@@ -957,7 +942,8 @@ public class CompanionDeviceManagerService extends SystemService {
                        Slog.i(LOG_TAG, "Granting role " + association.getDeviceProfile()
                                + " to " + association.getPackageName()
                                + " due to device connected: " + association.getDeviceMacAddress());
                        grantDeviceProfile(association);

                        addRoleHolderForAssociation(getContext(), association);
                    }
                }
            }
@@ -966,27 +952,6 @@ public class CompanionDeviceManagerService extends SystemService {
        onDeviceNearby(address);
    }

    private void grantDeviceProfile(AssociationInfo association) {
        Slog.i(LOG_TAG, "grantDeviceProfile(association = " + association + ")");

        if (association.getDeviceProfile() != null) {
            mRoleManager.addRoleHolderAsUser(
                    association.getDeviceProfile(),
                    association.getPackageName(),
                    RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP,
                    UserHandle.of(association.getUserId()),
                    getContext().getMainExecutor(),
                    success -> {
                        if (!success) {
                            Slog.e(LOG_TAG, "Failed to grant device profile role "
                                    + association.getDeviceProfile()
                                    + " to " + association.getPackageName()
                                    + " for user " + association.getUserId());
                        }
                    });
        }
    }

    void onDeviceDisconnected(String address) {
        Slog.d(LOG_TAG, "onDeviceDisconnected(address = " + address + ")");

@@ -1281,74 +1246,6 @@ public class CompanionDeviceManagerService extends SystemService {
        return copy;
    }

    boolean checkCallerCanInteractWithUserId(int userId) {
        if (getCallingUserId() == userId) return true;

        return getContext().checkCallingPermission(INTERACT_ACROSS_USERS) == PERMISSION_GRANTED;
    }

    void enforceCallerCanInteractWithUserId(int userId) {
        if (getCallingUserId() == userId) return;

        getContext().enforceCallingPermission(INTERACT_ACROSS_USERS, null);
    }

    private boolean checkCallerIsSystemOr(@UserIdInt int userId, @NonNull String pkg) {
        final int callingUid = getCallingUid();
        if (callingUid == SYSTEM_UID) return true;

        if (getCallingUserId() != userId) return false;

        try {
            if (mAppOpsManager.checkPackage(callingUid, pkg) != MODE_ALLOWED) return false;
        } catch (RemoteException e) {
            // Can't happen: AppOpsManager is running in the same process.
        }
        return true;
    }

    void enforceCallerIsSystemOr(@UserIdInt int userId, @NonNull String pkg) {
        final int callingUid = getCallingUid();
        if (callingUid == SYSTEM_UID) return;

        final int callingUserId = getCallingUserId();
        if (getCallingUserId() != userId) {
            throw new SecurityException("Calling UserId (" + callingUserId + ") does not match "
                    + "the expected UserId (" + userId + ")");
        }

        try {
            if (mAppOpsManager.checkPackage(callingUid, pkg) != MODE_ALLOWED) {
                throw new SecurityException(pkg + " doesn't belong to calling uid ("
                        + callingUid + ")");
            }
        } catch (RemoteException e) {
            // Can't happen: AppOpsManager is running in the same process.
        }
    }

    private boolean checkCallerCanManagerCompanionDevice() {
        if (getCallingUserId() == SYSTEM_UID) return true;

        return getContext().checkCallingPermission(MANAGE_COMPANION_DEVICES) == PERMISSION_GRANTED;
    }

    void enforceCallerCanManagerCompanionDevice() {
        if (getCallingUserId() == SYSTEM_UID) return;

        getContext().enforceCallingPermission(MANAGE_COMPANION_DEVICES,
                "Caller must hold " + MANAGE_COMPANION_DEVICES + " permission.");
    }

    private boolean checkCallerCanManageAssociationsForPackage(
            @UserIdInt int userId, @NonNull String packageName) {
        if (checkCallerIsSystemOr(userId, packageName)) return true;

        if (!checkCallerCanInteractWithUserId(userId)) return false;

        return checkCallerCanManagerCompanionDevice();
    }

    void checkUsesFeature(@NonNull String pkg, @UserIdInt int userId) {
        if (getCallingUserId() == SYSTEM_UID) return;

+189 −0

File added.

Preview size limit exceeded, changes collapsed.

+96 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.companion;

import static android.app.role.RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP;

import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
import static com.android.server.companion.CompanionDeviceManagerService.LOG_TAG;

import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.role.RoleManager;
import android.companion.AssociationInfo;
import android.content.Context;
import android.os.UserHandle;
import android.util.Slog;

import java.util.List;

/** Utility methods for accessing {@link RoleManager} APIs. */
final class RolesUtils {

    static boolean isRoleHolder(@NonNull Context context, @UserIdInt int userId,
            @NonNull String packageName, @NonNull String role) {
        final RoleManager roleManager = context.getSystemService(RoleManager.class);
        final List<String> roleHolders = roleManager.getRoleHoldersAsUser(
                role, UserHandle.of(userId));
        return roleHolders.contains(packageName);
    }

    static void addRoleHolderForAssociation(
            @NonNull Context context, @NonNull AssociationInfo associationInfo) {
        if (DEBUG) {
            Slog.d(LOG_TAG, "addRoleHolderForAssociation() associationInfo=" + associationInfo);
        }

        final String deviceProfile = associationInfo.getDeviceProfile();
        if (deviceProfile == null) return;

        final RoleManager roleManager = context.getSystemService(RoleManager.class);

        final String packageName = associationInfo.getPackageName();
        final int userId = associationInfo.getUserId();
        final UserHandle userHandle = UserHandle.of(userId);

        roleManager.addRoleHolderAsUser(deviceProfile, packageName,
                MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(),
                success -> {
                    if (!success) {
                        Slog.e(LOG_TAG, "Failed to add u" + userId + "\\" + packageName
                                + " to the list of " + deviceProfile + " holders.");
                    }
                });
    }

    static void removeRoleHolderForAssociation(
            @NonNull Context context, @NonNull AssociationInfo associationInfo) {
        if (DEBUG) {
            Slog.d(LOG_TAG, "removeRoleHolderForAssociation() associationInfo=" + associationInfo);
        }

        final String deviceProfile = associationInfo.getDeviceProfile();
        if (deviceProfile == null) return;

        final RoleManager roleManager = context.getSystemService(RoleManager.class);

        final String packageName = associationInfo.getPackageName();
        final int userId = associationInfo.getUserId();
        final UserHandle userHandle = UserHandle.of(userId);

        roleManager.removeRoleHolderAsUser(deviceProfile, packageName,
                MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(),
                success -> {
                    if (!success) {
                        Slog.e(LOG_TAG, "Failed to remove u" + userId + "\\" + packageName
                                + " from the list of " + deviceProfile + " holders.");
                    }
                });
    }

    private RolesUtils() {};
}