diff options
3 files changed, 135 insertions, 7 deletions
diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java index 330c15cc4614..c82a6564c1d7 100644 --- a/core/java/com/android/internal/util/Preconditions.java +++ b/core/java/com/android/internal/util/Preconditions.java @@ -187,6 +187,32 @@ public class Preconditions { } /** + * Ensures the truth of an expression involving whether the calling identity is authorized to + * call the calling method. + * + * @param expression a boolean expression + * @throws SecurityException if {@code expression} is false + */ + public static void checkCallAuthorization(final boolean expression) { + if (!expression) { + throw new SecurityException("Calling identity is not authorized"); + } + } + + /** + * Ensures the truth of an expression involving whether the calling user is authorized to + * call the calling method. + * + * @param expression a boolean expression + * @throws SecurityException if {@code expression} is false + */ + public static void checkCallingUser(final boolean expression) { + if (!expression) { + throw new SecurityException("Calling user is not authorized"); + } + } + + /** * Check the requested flags, throwing if any requested flags are outside * the allowed set. * diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/CallerIdentity.java b/services/devicepolicy/java/com/android/server/devicepolicy/CallerIdentity.java new file mode 100644 index 000000000000..5193fa85d238 --- /dev/null +++ b/services/devicepolicy/java/com/android/server/devicepolicy/CallerIdentity.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2020 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.devicepolicy; + +import android.annotation.Nullable; +import android.content.ComponentName; +import android.os.UserHandle; + +/** + * Caller identity containing the caller's UID, package name and component name. + * All parameters are verified on object creation unless the component name is null and the + * caller is a delegate. + */ +class CallerIdentity { + + private final int mUid; + @Nullable + private final String mPackageName; + @Nullable + private final ComponentName mComponentName; + + CallerIdentity(int uid, @Nullable String packageName, @Nullable ComponentName componentName) { + mUid = uid; + mPackageName = packageName; + mComponentName = componentName; + } + + public int getUid() { + return mUid; + } + + public int getUserId() { + return UserHandle.getUserId(mUid); + } + + public UserHandle getUserHandle() { + return UserHandle.getUserHandleForUid(mUid); + } + + @Nullable public String getPackageName() { + return mPackageName; + } + + @Nullable public ComponentName getComponentName() { + return mComponentName; + } +} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index c6b93d6ca4f4..c82bc3a425c8 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -2702,6 +2702,39 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } /** + * Creates a new {@link CallerIdentity} object to represent the caller's identity. + */ + private CallerIdentity getCallerIdentity(String callerPackage) { + final int callerUid = mInjector.binderGetCallingUid(); + + if (!isCallingFromPackage(callerPackage, callerUid)) { + throw new SecurityException( + String.format("Caller with uid %d is not %s", callerUid, callerPackage)); + } + + return new CallerIdentity(callerUid, callerPackage, null); + } + + /** + * Creates a new {@link CallerIdentity} object to represent the caller's identity. + */ + private CallerIdentity getCallerIdentity(@NonNull ComponentName componentName) { + final int callerUid = mInjector.binderGetCallingUid(); + final DevicePolicyData policy = getUserData(UserHandle.getUserId(callerUid)); + ActiveAdmin admin = policy.mAdminMap.get(componentName); + + if (admin == null) { + throw new SecurityException(String.format("No active admin for %s", componentName)); + } + if (admin.getUid() != callerUid) { + throw new SecurityException( + String.format("Admin %s is not owned by uid %d", componentName, callerUid)); + } + + return new CallerIdentity(callerUid, componentName.getPackageName(), componentName); + } + + /** * Checks if the device is in COMP mode, and if so migrates it to managed profile on a * corporate owned device. */ @@ -8728,6 +8761,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + private boolean isDeviceOwner(CallerIdentity identity) { + synchronized (getLockObject()) { + return mOwners.hasDeviceOwner() + && mOwners.getDeviceOwnerUserId() == identity.getUserId() + && mOwners.getDeviceOwnerComponent().equals(identity.getComponentName()); + } + } + private boolean isDeviceOwnerPackage(String packageName, int userId) { synchronized (getLockObject()) { return mOwners.hasDeviceOwner() @@ -11984,20 +12025,20 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void setLocationEnabled(ComponentName who, boolean locationEnabled) { - enforceDeviceOwner(Objects.requireNonNull(who)); - - UserHandle user = mInjector.binderGetCallingUserHandle(); + CallerIdentity identity = getCallerIdentity(who); + Preconditions.checkCallAuthorization(isDeviceOwner(identity)); mInjector.binderWithCleanCallingIdentity(() -> { boolean wasLocationEnabled = mInjector.getLocationManager().isLocationEnabledForUser( - user); - mInjector.getLocationManager().setLocationEnabledForUser(locationEnabled, user); + identity.getUserHandle()); + mInjector.getLocationManager().setLocationEnabledForUser(locationEnabled, + identity.getUserHandle()); // make a best effort to only show the notification if the admin is actually enabling // location. this is subject to race conditions with settings changes, but those are // unlikely to realistically interfere - if (locationEnabled && (wasLocationEnabled != locationEnabled)) { - showLocationSettingsEnabledNotification(user); + if (locationEnabled && !wasLocationEnabled) { + showLocationSettingsEnabledNotification(identity.getUserHandle()); } }); |