blob: 0b48b5c6dd7058c2656f0a638741998a389d7d28 [file] [log] [blame]
/*
* 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.pm.verify.domain;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Process;
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
public class DomainVerificationEnforcer {
@NonNull
private final Context mContext;
@NonNull
private Callback mCallback;
public DomainVerificationEnforcer(@NonNull Context context) {
mContext = context;
}
public void setCallback(@NonNull Callback callback) {
mCallback = callback;
}
/**
* Enforced when mutating any state from shell or internally in the system process.
*/
public void assertInternal(int callingUid) {
switch (callingUid) {
case Process.ROOT_UID:
case Process.SHELL_UID:
case Process.SYSTEM_UID:
break;
default:
throw new SecurityException(
"Caller " + callingUid + " is not allowed to change internal state");
}
}
/**
* Enforced when retrieving state for a package. The system, the verifier, and anyone approved
* to mutate user selections are allowed through.
*/
public void assertApprovedQuerent(int callingUid, @NonNull DomainVerificationProxy proxy) {
switch (callingUid) {
case Process.ROOT_UID:
case Process.SHELL_UID:
case Process.SYSTEM_UID:
break;
default:
if (!proxy.isCallerVerifier(callingUid)) {
throw new SecurityException(
"Caller is not allowed to query domain verification state");
}
mContext.enforcePermission(android.Manifest.permission.QUERY_ALL_PACKAGES,
Binder.getCallingPid(), callingUid,
"Caller " + callingUid + " does not hold "
+ android.Manifest.permission.QUERY_ALL_PACKAGES);
break;
}
}
/**
* Enforced when mutating domain verification state inside an exposed API method.
*/
public void assertApprovedVerifier(int callingUid, @NonNull DomainVerificationProxy proxy)
throws SecurityException {
boolean isAllowed;
switch (callingUid) {
case Process.ROOT_UID:
case Process.SHELL_UID:
case Process.SYSTEM_UID:
isAllowed = true;
break;
default:
final int callingPid = Binder.getCallingPid();
boolean isLegacyVerificationAgent = false;
if (mContext.checkPermission(
android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, callingPid,
callingUid) != PackageManager.PERMISSION_GRANTED) {
isLegacyVerificationAgent = mContext.checkPermission(
android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT,
callingPid, callingUid) == PackageManager.PERMISSION_GRANTED;
if (!isLegacyVerificationAgent) {
throw new SecurityException("Caller " + callingUid + " does not hold "
+ android.Manifest.permission.DOMAIN_VERIFICATION_AGENT);
}
}
// If the caller isn't a legacy verifier, it needs the QUERY_ALL permission
if (!isLegacyVerificationAgent) {
mContext.enforcePermission(android.Manifest.permission.QUERY_ALL_PACKAGES,
callingPid, callingUid, "Caller " + callingUid + " does not hold "
+ android.Manifest.permission.QUERY_ALL_PACKAGES);
}
isAllowed = proxy.isCallerVerifier(callingUid);
break;
}
if (!isAllowed) {
throw new SecurityException("Caller " + callingUid
+ " is not the approved domain verification agent");
}
}
/**
* Enforced when mutating user selection state inside an exposed API method.
*/
public boolean assertApprovedUserStateQuerent(int callingUid, @UserIdInt int callingUserId,
@NonNull String packageName, @UserIdInt int targetUserId) throws SecurityException {
if (callingUserId != targetUserId) {
mContext.enforcePermission(
Manifest.permission.INTERACT_ACROSS_USERS,
Binder.getCallingPid(), callingUid,
"Caller is not allowed to edit other users");
}
if (!mCallback.doesUserExist(callingUserId)) {
throw new SecurityException("User " + callingUserId + " does not exist");
} else if (!mCallback.doesUserExist(targetUserId)) {
throw new SecurityException("User " + targetUserId + " does not exist");
}
return !mCallback.filterAppAccess(packageName, callingUid, targetUserId);
}
/**
* Enforced when mutating user selection state inside an exposed API method.
*/
public boolean assertApprovedUserSelector(int callingUid, @UserIdInt int callingUserId,
@Nullable String packageName, @UserIdInt int targetUserId) throws SecurityException {
if (callingUserId != targetUserId) {
mContext.enforcePermission(
Manifest.permission.INTERACT_ACROSS_USERS,
Binder.getCallingPid(), callingUid,
"Caller is not allowed to edit other users");
}
mContext.enforcePermission(
android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION,
Binder.getCallingPid(), callingUid,
"Caller is not allowed to edit user selections");
if (!mCallback.doesUserExist(callingUserId)) {
throw new SecurityException("User " + callingUserId + " does not exist");
} else if (!mCallback.doesUserExist(targetUserId)) {
throw new SecurityException("User " + targetUserId + " does not exist");
}
if (packageName == null) {
return true;
}
return !mCallback.filterAppAccess(packageName, callingUid, targetUserId);
}
public boolean callerIsLegacyUserSelector(int callingUid, @UserIdInt int callingUserId,
@NonNull String packageName, @UserIdInt int targetUserId) {
mContext.enforcePermission(
android.Manifest.permission.SET_PREFERRED_APPLICATIONS,
Binder.getCallingPid(), callingUid,
"Caller is not allowed to edit user state");
if (callingUserId != targetUserId) {
if (mContext.checkPermission(
Manifest.permission.INTERACT_ACROSS_USERS,
Binder.getCallingPid(), callingUid) != PackageManager.PERMISSION_GRANTED) {
// Legacy API did not enforce this, so for backwards compatibility, fail silently
return false;
}
}
if (!mCallback.doesUserExist(callingUserId)) {
throw new SecurityException("User " + callingUserId + " does not exist");
} else if (!mCallback.doesUserExist(targetUserId)) {
throw new SecurityException("User " + targetUserId + " does not exist");
}
return !mCallback.filterAppAccess(packageName, callingUid, targetUserId);
}
public boolean callerIsLegacyUserQuerent(int callingUid, @UserIdInt int callingUserId,
@NonNull String packageName, @UserIdInt int targetUserId) {
if (callingUserId != targetUserId) {
// The legacy API enforces the _FULL variant, so maintain that here
mContext.enforcePermission(
Manifest.permission.INTERACT_ACROSS_USERS_FULL,
Binder.getCallingPid(), callingUid,
"Caller is not allowed to edit other users");
}
if (!mCallback.doesUserExist(callingUserId)) {
throw new SecurityException("User " + callingUserId + " does not exist");
} else if (!mCallback.doesUserExist(targetUserId)) {
throw new SecurityException("User " + targetUserId + " does not exist");
}
return !mCallback.filterAppAccess(packageName, callingUid, targetUserId);
}
/**
* Querying for the owners of a domain. Because this API cannot filter the returned list of
* packages, enforces {@link android.Manifest.permission.QUERY_ALL_PACKAGES}, but also enforces
* {@link android.Manifest.permission.INTERACT_ACROSS_USERS} because each user has a different
* state.
*/
public void assertOwnerQuerent(int callingUid, @UserIdInt int callingUserId,
@UserIdInt int targetUserId) {
final int callingPid = Binder.getCallingPid();
if (callingUserId != targetUserId) {
mContext.enforcePermission(android.Manifest.permission.INTERACT_ACROSS_USERS,
callingPid, callingUid, "Caller is not allowed to query other users");
}
mContext.enforcePermission(android.Manifest.permission.QUERY_ALL_PACKAGES,
callingPid, callingUid, "Caller " + callingUid + " does not hold "
+ android.Manifest.permission.QUERY_ALL_PACKAGES);
mContext.enforcePermission(
android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION,
callingPid, callingUid, "Caller is not allowed to query user selections");
if (!mCallback.doesUserExist(callingUserId)) {
throw new SecurityException("User " + callingUserId + " does not exist");
} else if (!mCallback.doesUserExist(targetUserId)) {
throw new SecurityException("User " + targetUserId + " does not exist");
}
}
public interface Callback {
/**
* @return true if access to the given package should be filtered and the method failed as
* if the package was not installed
*/
boolean filterAppAccess(@NonNull String packageName, int callingUid, @UserIdInt int userId);
boolean doesUserExist(@UserIdInt int userId);
}
}