Merge "Adding Access Control in CrossProfileIntentFilter"
This commit is contained in:
committed by
Android (Google) Code Review
commit
2772baedf5
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -122,6 +122,8 @@ public final class UserTypeFactory {
|
||||
.setLabel(0)
|
||||
.setDefaultRestrictions(null)
|
||||
.setIsMediaSharedWithParent(true)
|
||||
.setCrossProfileIntentFilterAccessControl(
|
||||
CrossProfileIntentFilter.ACCESS_LEVEL_SYSTEM)
|
||||
.setIsCredentialSharableWithParent(true);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user