Merge "Adding Access Control in CrossProfileIntentFilter"

This commit is contained in:
Sarup Dalwani
2022-08-09 12:51:41 +00:00
committed by Android (Google) Code Review
5 changed files with 209 additions and 5 deletions

View File

@@ -16,6 +16,7 @@
package com.android.server.pm;
import android.annotation.IntDef;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.UserHandle;
@@ -30,6 +31,8 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* The {@link PackageManagerService} maintains some {@link CrossProfileIntentFilter}s for each user.
@@ -41,13 +44,52 @@ class CrossProfileIntentFilter extends WatchedIntentFilter {
private static final String ATTR_FLAGS = "flags";
private static final String ATTR_OWNER_PACKAGE = "ownerPackage";
private static final String ATTR_FILTER = "filter";
private static final String ATTR_ACCESS_CONTROL = "accessControl";
private static final String TAG = "CrossProfileIntentFilter";
/**
* AccessControlLevel provides level of access for user to create/modify
* {@link CrossProfileIntentFilter} in {@link com.android.server.pm.Settings}.
* Each AccessControlLevel have value assigned, the higher the value
* implies higher restriction for creation/modification.
* AccessControlLevel allows us to protect against malicious changes in user's
* {@link CrossProfileIntentFilter}s, which might add/remove {@link CrossProfileIntentFilter}
* leading to unprecedented results.
*/
@IntDef(prefix = {"ACCESS_LEVEL_"}, value = {
ACCESS_LEVEL_ALL,
ACCESS_LEVEL_SYSTEM,
ACCESS_LEVEL_SYSTEM_ADD_ONLY,
})
@Retention(RetentionPolicy.SOURCE)
public @interface AccessControlLevel {
}
/**
* ACCESS_LEVEL_ALL signifies that irrespective of user we would allow
* access(addition/modification/removal) for CrossProfileIntentFilter.
* This is the default access control level.
*/
public static final int ACCESS_LEVEL_ALL = 0;
/**
* ACCESS_LEVEL_SYSTEM signifies that only system/root user would be able to
* access(addition/modification/removal) CrossProfileIntentFilter.
*/
public static final int ACCESS_LEVEL_SYSTEM = 10;
/**
* ACCESS_LEVEL_SYSTEM_ADD_ONLY signifies that only system/root user would be able to add
* CrossProfileIntentFilter but not modify/remove. Once added, it cannot be modified or removed.
*/
public static final int ACCESS_LEVEL_SYSTEM_ADD_ONLY = 20;
// If the intent matches the IntentFilter, then it can be forwarded to this userId.
final int mTargetUserId;
final String mOwnerPackage; // packageName of the app.
final int mFlags;
final int mAccessControlLevel;
// The cache for snapshots, so they are not rebuilt if the base object has not
// changed.
@@ -65,10 +107,16 @@ class CrossProfileIntentFilter extends WatchedIntentFilter {
CrossProfileIntentFilter(IntentFilter filter, String ownerPackage, int targetUserId,
int flags) {
this(filter, ownerPackage, targetUserId, flags, ACCESS_LEVEL_ALL);
}
CrossProfileIntentFilter(IntentFilter filter, String ownerPackage, int targetUserId,
int flags, @AccessControlLevel int accessControlLevel) {
super(filter);
mTargetUserId = targetUserId;
mOwnerPackage = ownerPackage;
mFlags = flags;
mAccessControlLevel = accessControlLevel;
mSnapshot = makeCache();
}
@@ -77,12 +125,18 @@ class CrossProfileIntentFilter extends WatchedIntentFilter {
this(filter.mFilter, ownerPackage, targetUserId, flags);
}
CrossProfileIntentFilter(WatchedIntentFilter filter, String ownerPackage, int targetUserId,
int flags, @AccessControlLevel int accessControlLevel) {
this(filter.mFilter, ownerPackage, targetUserId, flags, accessControlLevel);
}
// Copy constructor used only to create a snapshot.
private CrossProfileIntentFilter(CrossProfileIntentFilter f) {
super(f);
mTargetUserId = f.mTargetUserId;
mOwnerPackage = f.mOwnerPackage;
mFlags = f.mFlags;
mAccessControlLevel = f.mAccessControlLevel;
mSnapshot = new SnapshotCache.Sealed();
}
@@ -98,9 +152,14 @@ class CrossProfileIntentFilter extends WatchedIntentFilter {
return mOwnerPackage;
}
public @AccessControlLevel int getAccessControlLevel() {
return mAccessControlLevel;
}
CrossProfileIntentFilter(TypedXmlPullParser parser) throws XmlPullParserException, IOException {
mTargetUserId = parser.getAttributeInt(null, ATTR_TARGET_USER_ID, UserHandle.USER_NULL);
mOwnerPackage = getStringFromXml(parser, ATTR_OWNER_PACKAGE, "");
mAccessControlLevel = parser.getAttributeInt(null, ATTR_ACCESS_CONTROL, ACCESS_LEVEL_ALL);
mFlags = parser.getAttributeInt(null, ATTR_FLAGS, 0);
mSnapshot = makeCache();
@@ -151,6 +210,7 @@ class CrossProfileIntentFilter extends WatchedIntentFilter {
serializer.attributeInt(null, ATTR_TARGET_USER_ID, mTargetUserId);
serializer.attributeInt(null, ATTR_FLAGS, mFlags);
serializer.attribute(null, ATTR_OWNER_PACKAGE, mOwnerPackage);
serializer.attributeInt(null, ATTR_ACCESS_CONTROL, mAccessControlLevel);
serializer.startTag(null, ATTR_FILTER);
mFilter.writeToXml(serializer);
serializer.endTag(null, ATTR_FILTER);
@@ -165,7 +225,8 @@ class CrossProfileIntentFilter extends WatchedIntentFilter {
boolean equalsIgnoreFilter(CrossProfileIntentFilter other) {
return mTargetUserId == other.mTargetUserId
&& mOwnerPackage.equals(other.mOwnerPackage)
&& mFlags == other.mFlags;
&& mFlags == other.mFlags
&& mAccessControlLevel == other.mAccessControlLevel;
}
public CrossProfileIntentFilter snapshot() {

View File

@@ -3350,6 +3350,12 @@ public class PackageManagerService implements PackageSender, TestUtilityService
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
int callingUid = Binder.getCallingUid();
enforceOwnerRights(snapshot, ownerPackage, callingUid);
// Verifying that current calling uid should be able to add {@link CrossProfileIntentFilter}
// for source and target user
mUserManager.enforceCrossProfileIntentFilterAccess(sourceUserId, targetUserId, callingUid,
/* addCrossProfileIntentFilter */ true);
PackageManagerServiceUtils.enforceShellRestriction(mInjector.getUserManagerInternal(),
UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, sourceUserId);
if (intentFilter.countActions() == 0) {
@@ -3358,7 +3364,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService
}
synchronized (mLock) {
CrossProfileIntentFilter newFilter = new CrossProfileIntentFilter(intentFilter,
ownerPackage, targetUserId, flags);
ownerPackage, targetUserId, flags, mUserManager
.getCrossProfileIntentFilterAccessControl(sourceUserId, targetUserId));
CrossProfileIntentResolver resolver =
mSettings.editCrossProfileIntentResolverLPw(sourceUserId);
ArrayList<CrossProfileIntentFilter> existing = resolver.findFilters(intentFilter);
@@ -4572,7 +4579,11 @@ public class PackageManagerService implements PackageSender, TestUtilityService
ArraySet<CrossProfileIntentFilter> set =
new ArraySet<>(resolver.filterSet());
for (CrossProfileIntentFilter filter : set) {
if (filter.getOwnerPackage().equals(ownerPackage)) {
//Only remove if calling user is allowed based on access control of
// {@link CrossProfileIntentFilter}
if (filter.getOwnerPackage().equals(ownerPackage)
&& mUserManager.isCrossProfileIntentFilterAccessible(sourceUserId,
filter.mTargetUserId, /* addCrossProfileIntentFilter */ false)) {
resolver.removeFilter(filter);
}
}

View File

@@ -1866,6 +1866,103 @@ public class UserManagerService extends IUserManager.Stub {
return mLocalService.exists(userId);
}
/**
* Returns user's {@link CrossProfileIntentFilter.AccessControlLevel}, which is derived from
* {@link UserTypeDetails}. If user does not have defined their access control level,
* returns default {@link CrossProfileIntentFilter#ACCESS_LEVEL_ALL}
*/
private @CrossProfileIntentFilter.AccessControlLevel int
getCrossProfileIntentFilterAccessControl(@UserIdInt int userId) {
final UserTypeDetails userTypeDetails = getUserTypeDetailsNoChecks(userId);
return userTypeDetails != null ? userTypeDetails.getCrossProfileIntentFilterAccessControl()
: CrossProfileIntentFilter.ACCESS_LEVEL_ALL;
}
/**
* Verifies if calling user is allowed to access {@link CrossProfileIntentFilter} between given
* source and target user.
* @param sourceUserId userId for which CrossProfileIntentFilter would be configured
* @param targetUserId target user where we can resolve given intent filter
* @param callingUid user accessing api
* @param addCrossProfileIntentFilter if the operation is addition or not.
* @throws SecurityException is calling user is not allowed to access.
*/
public void enforceCrossProfileIntentFilterAccess(
int sourceUserId, int targetUserId,
int callingUid, boolean addCrossProfileIntentFilter) {
if (!isCrossProfileIntentFilterAccessible(sourceUserId, targetUserId,
addCrossProfileIntentFilter)) {
throw new SecurityException("CrossProfileIntentFilter cannot be accessed by user "
+ callingUid);
}
}
/**
* Checks if {@link CrossProfileIntentFilter} can be accessed by calling user for given source
* and target user. There are following rules of access
* 1. For {@link CrossProfileIntentFilter#ACCESS_LEVEL_ALL},
* irrespective of user we would allow access(addition/modification/removal)
* 2. For {@link CrossProfileIntentFilter#ACCESS_LEVEL_SYSTEM},
* only system/root user would be able to access(addition/modification/removal)
* 3. For {@link CrossProfileIntentFilter#ACCESS_LEVEL_SYSTEM_ADD_ONLY},
* only system/root user would be able to add but not modify/remove. Once added, it cannot be
* modified or removed
* @param sourceUserId userId for which CrossProfileIntentFilter would be configured
* @param targetUserId target user where we can resolve given intent filter
* @param addCrossProfileIntentFilter if the operation is addition or not.
* @return true if {@link CrossProfileIntentFilter} can be accessed by calling user
*/
public boolean isCrossProfileIntentFilterAccessible(int sourceUserId, int targetUserId,
boolean addCrossProfileIntentFilter) {
int effectiveAccessControl =
getCrossProfileIntentFilterAccessControl(sourceUserId, targetUserId);
/*
For {@link CrossProfileIntentFilter#ACCESS_LEVEL_SYSTEM}, if accessing user is not
system or root disallowing access to {@link CrossProfileIntentFilter}
*/
if (CrossProfileIntentFilter.ACCESS_LEVEL_SYSTEM == effectiveAccessControl
&& !PackageManagerServiceUtils.isSystemOrRoot()) {
return false;
}
/*
For {@link CrossProfileIntentFilter#ACCESS_LEVEL_SYSTEM_ADD_ONLY}, allowing only
system user to add {@link CrossProfileIntentFilter}. All users(including system) are
disallowed to modify/remove.
*/
if (CrossProfileIntentFilter.ACCESS_LEVEL_SYSTEM_ADD_ONLY == effectiveAccessControl
&& (!addCrossProfileIntentFilter || !PackageManagerServiceUtils.isSystemOrRoot())) {
return false;
}
return true;
}
/**
* Returns {@link CrossProfileIntentFilter.AccessControlLevel}
* that should be assigned to {@link CrossProfileIntentFilter}
* computed from source user's and target user's
* {@link CrossProfileIntentFilter.AccessControlLevel}.
* The Access Level is configured per {@link CrossProfileIntentFilter} and its property of edge
* between source and target user e.g. for all {@link CrossProfileIntentFilter}s configured
* between Primary user and Clone profile should have access level of
* {@link CrossProfileIntentFilter#ACCESS_LEVEL_SYSTEM} which is driven by highest
* access value from source or target. The higher value means higher restrictions.
* @param sourceUserId userId of source user for whom CrossProfileIntentFilter will be stored
* @param targetUserId userId of target user for whom Cross Profile access would be allowed
* @return least privileged {@link CrossProfileIntentFilter.AccessControlLevel} from source or
* target user.
*/
public @CrossProfileIntentFilter.AccessControlLevel int
getCrossProfileIntentFilterAccessControl(int sourceUserId, int targetUserId) {
int sourceAccessControlLevel,
targetAccessControlLevel, effectiveAccessControl;
sourceAccessControlLevel = getCrossProfileIntentFilterAccessControl(sourceUserId);
targetAccessControlLevel = getCrossProfileIntentFilterAccessControl(targetUserId);
effectiveAccessControl = Math.max(sourceAccessControlLevel, targetAccessControlLevel);
return effectiveAccessControl;
}
@Override
public void setUserName(@UserIdInt int userId, String name) {
checkManageUsersPermission("rename users");

View File

@@ -163,6 +163,14 @@ public final class UserTypeDetails {
*/
private final boolean mIsCredentialSharableWithParent;
/**
* Denotes the default access control for {@link CrossProfileIntentFilter} of user profile.
*
* <p> Default value is {@link CrossProfileIntentFilter#ACCESS_LEVEL_ALL}
*/
private final @CrossProfileIntentFilter.AccessControlLevel int
mCrossProfileIntentFilterAccessControl;
private UserTypeDetails(@NonNull String name, boolean enabled, int maxAllowed,
@UserInfoFlag int baseType, @UserInfoFlag int defaultUserInfoPropertyFlags, int label,
int maxAllowedPerParent,
@@ -174,7 +182,8 @@ public final class UserTypeDetails {
@Nullable Bundle defaultSecureSettings,
@Nullable List<DefaultCrossProfileIntentFilter> defaultCrossProfileIntentFilters,
boolean isMediaSharedWithParent,
boolean isCredentialSharableWithParent) {
boolean isCredentialSharableWithParent,
@CrossProfileIntentFilter.AccessControlLevel int accessControlLevel) {
this.mName = name;
this.mEnabled = enabled;
this.mMaxAllowed = maxAllowed;
@@ -195,6 +204,7 @@ public final class UserTypeDetails {
this.mDarkThemeBadgeColors = darkThemeBadgeColors;
this.mIsMediaSharedWithParent = isMediaSharedWithParent;
this.mIsCredentialSharableWithParent = isCredentialSharableWithParent;
this.mCrossProfileIntentFilterAccessControl = accessControlLevel;
}
/**
@@ -327,6 +337,16 @@ public final class UserTypeDetails {
return mIsCredentialSharableWithParent;
}
/**
* Returning user's {@link CrossProfileIntentFilter.AccessControlLevel}. If not explicitly
* configured, default value is {@link CrossProfileIntentFilter#ACCESS_LEVEL_ALL}
* @return user's {@link CrossProfileIntentFilter.AccessControlLevel}
*/
public @CrossProfileIntentFilter.AccessControlLevel int
getCrossProfileIntentFilterAccessControl() {
return mCrossProfileIntentFilterAccessControl;
}
/** Returns a {@link Bundle} representing the default user restrictions. */
@NonNull Bundle getDefaultRestrictions() {
return BundleUtils.clone(mDefaultRestrictions);
@@ -420,6 +440,8 @@ public final class UserTypeDetails {
private @DrawableRes int mBadgeNoBackground = Resources.ID_NULL;
private boolean mIsMediaSharedWithParent = false;
private boolean mIsCredentialSharableWithParent = false;
private @CrossProfileIntentFilter.AccessControlLevel int
mCrossProfileIntentFilterAccessControl = CrossProfileIntentFilter.ACCESS_LEVEL_ALL;
public Builder setName(String name) {
mName = name;
@@ -519,6 +541,16 @@ public final class UserTypeDetails {
return this;
}
/**
* Sets {@link CrossProfileIntentFilter.AccessControlLevel} for the user.
* @param accessControlLevel default access control for user
*/
public Builder setCrossProfileIntentFilterAccessControl(
@CrossProfileIntentFilter.AccessControlLevel int accessControlLevel) {
mCrossProfileIntentFilterAccessControl = accessControlLevel;
return this;
}
/**
* Sets shared media property for the user.
* @param isCredentialSharableWithParent the value to be set, true or false
@@ -571,7 +603,8 @@ public final class UserTypeDetails {
mDefaultSecureSettings,
mDefaultCrossProfileIntentFilters,
mIsMediaSharedWithParent,
mIsCredentialSharableWithParent);
mIsCredentialSharableWithParent,
mCrossProfileIntentFilterAccessControl);
}
private boolean hasBadge() {

View File

@@ -122,6 +122,8 @@ public final class UserTypeFactory {
.setLabel(0)
.setDefaultRestrictions(null)
.setIsMediaSharedWithParent(true)
.setCrossProfileIntentFilterAccessControl(
CrossProfileIntentFilter.ACCESS_LEVEL_SYSTEM)
.setIsCredentialSharableWithParent(true);
}