diff options
| -rw-r--r-- | api/current.txt | 1 | ||||
| -rw-r--r-- | api/system-current.txt | 1 | ||||
| -rw-r--r-- | api/test-current.txt | 1 | ||||
| -rw-r--r-- | core/java/android/app/ContextImpl.java | 18 | ||||
| -rw-r--r-- | core/java/android/app/admin/DevicePolicyManager.java | 42 | ||||
| -rw-r--r-- | core/java/android/app/admin/IDevicePolicyManager.aidl | 6 | ||||
| -rw-r--r-- | core/java/android/content/Context.java | 27 | ||||
| -rw-r--r-- | core/java/android/content/ContextWrapper.java | 28 | ||||
| -rw-r--r-- | services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java | 124 | ||||
| -rw-r--r-- | test-runner/src/android/test/mock/MockContext.java | 21 |
10 files changed, 265 insertions, 4 deletions
diff --git a/api/current.txt b/api/current.txt index 873957b411d4..6096f90aedd7 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5983,6 +5983,7 @@ package android.app.admin { method public boolean addCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String); method public void addPersistentPreferredActivity(android.content.ComponentName, android.content.IntentFilter, android.content.ComponentName); method public void addUserRestriction(android.content.ComponentName, java.lang.String); + method public boolean bindDeviceAdminServiceAsUser(android.content.ComponentName, android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle); method public void clearCrossProfileIntentFilters(android.content.ComponentName); method public void clearDeviceOwnerApp(java.lang.String); method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String); diff --git a/api/system-current.txt b/api/system-current.txt index 88f25a8da94e..370afc53497b 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -6154,6 +6154,7 @@ package android.app.admin { method public boolean addCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String); method public void addPersistentPreferredActivity(android.content.ComponentName, android.content.IntentFilter, android.content.ComponentName); method public void addUserRestriction(android.content.ComponentName, java.lang.String); + method public boolean bindDeviceAdminServiceAsUser(android.content.ComponentName, android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle); method public void clearCrossProfileIntentFilters(android.content.ComponentName); method public void clearDeviceOwnerApp(java.lang.String); method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String); diff --git a/api/test-current.txt b/api/test-current.txt index 5d088322d3f1..ab16ce956381 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -5999,6 +5999,7 @@ package android.app.admin { method public boolean addCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String); method public void addPersistentPreferredActivity(android.content.ComponentName, android.content.IntentFilter, android.content.ComponentName); method public void addUserRestriction(android.content.ComponentName, java.lang.String); + method public boolean bindDeviceAdminServiceAsUser(android.content.ComponentName, android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle); method public void clearCrossProfileIntentFilters(android.content.ComponentName); method public void clearDeviceOwnerApp(java.lang.String); method public void clearPackagePersistentPreferredActivities(android.content.ComponentName, java.lang.String); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index f1d0e10dd6e3..c5180fd39c7d 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -18,6 +18,7 @@ package android.app; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentProvider; @@ -1457,8 +1458,22 @@ class ContextImpl extends Context { return bindServiceCommon(service, conn, flags, handler, user); } + /** @hide */ + @Override + public IServiceConnection getServiceDispatcher(ServiceConnection conn, Handler handler, + int flags) { + return mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, flags); + } + + /** @hide */ + @Override + public IApplicationThread getIApplicationThread() { + return mMainThread.getApplicationThread(); + } + private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags, Handler handler, UserHandle user) { + // Keep this in sync with DevicePolicyManager.bindDeviceAdminServiceAsUser. IServiceConnection sd; if (conn == null) { throw new IllegalArgumentException("connection is null"); @@ -2141,7 +2156,8 @@ class ContextImpl extends Context { return mOuterContext; } - final IBinder getActivityToken() { + @Override + public IBinder getActivityToken() { return mActivityToken; } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 1ab809d09b32..5ca39b0eb80e 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -20,19 +20,21 @@ import android.annotation.ColorInt; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.annotation.UserIdInt; import android.annotation.WorkerThread; import android.app.Activity; -import android.app.admin.NetworkEvent; import android.app.admin.PasswordMetrics; +import android.app.IServiceConnection; import android.app.admin.SecurityLog.SecurityEvent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ParceledListSlice; @@ -45,10 +47,8 @@ import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteCallback; import android.os.RemoteException; -import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; -import android.os.ServiceManager.ServiceNotFoundException; import android.provider.ContactsContract.Directory; import android.provider.Settings; import android.security.Credentials; @@ -6713,4 +6713,40 @@ public class DevicePolicyManager { throw re.rethrowFromSystemServer(); } } + + /** + * Called by device owner/ profile owner in managed profile to bind the service with each other. + * The service must be unexported. Note that the {@link Context} used to obtain this + * {@link DevicePolicyManager} instance via {@link Context#getSystemService(Class)} will be used + * to bind to the {@link android.app.Service}. + * STOPSHIP (b/31952368): Update the javadoc after we policy to control which packages can talk. + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param serviceIntent Identifies the service to connect to. The Intent must specify either an + * explicit component name or a package name to match an + * {@link IntentFilter} published by a service. + * @param conn Receives information as the service is started and stopped. This must be a + * valid {@link ServiceConnection} object; it must not be {@code null}. + * @param flags Operation options for the binding operation. See + * {@link Context#bindService(Intent, ServiceConnection, int)}. + * @param targetUser Which user to bind to. + * @return If you have successfully bound to the service, {@code true} is returned; + * {@code false} is returned if the connection is not made and you will not + * receive the service object. + * @see Context#bindService(Intent, ServiceConnection, int) + */ + public boolean bindDeviceAdminServiceAsUser( + @NonNull ComponentName admin, Intent serviceIntent, @NonNull ServiceConnection conn, + @Context.BindServiceFlags int flags, @NonNull UserHandle targetUser) { + throwIfParentInstance("bindDeviceAdminServiceAsUser"); + // Keep this in sync with ContextImpl.bindServiceCommon. + try { + final IServiceConnection sd = mContext.getServiceDispatcher(conn, null, flags); + serviceIntent.prepareToLeaveProcess(mContext); + return mService.bindDeviceAdminServiceAsUser(admin, + mContext.getIApplicationThread(), mContext.getActivityToken(), serviceIntent, + sd, flags, targetUser.getIdentifier()); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 729d12b3751a..a2546c024962 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -18,6 +18,8 @@ package android.app.admin; import android.app.admin.NetworkEvent; +import android.app.IApplicationThread; +import android.app.IServiceConnection; import android.app.admin.SystemUpdatePolicy; import android.app.admin.PasswordMetrics; import android.content.ComponentName; @@ -319,4 +321,8 @@ interface IDevicePolicyManager { void setNetworkLoggingEnabled(in ComponentName admin, boolean enabled); boolean isNetworkLoggingEnabled(in ComponentName admin); List<NetworkEvent> retrieveNetworkLogs(in ComponentName admin, long batchToken); + + boolean bindDeviceAdminServiceAsUser(in ComponentName admin, + IApplicationThread caller, IBinder token, in Intent service, + IServiceConnection connection, int flags, int targetUserId); } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 3964e0a8bbd9..821b0f85c555 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -32,6 +32,10 @@ import android.annotation.StyleableRes; import android.annotation.SystemApi; import android.annotation.TestApi; import android.annotation.UserIdInt; +import android.app.IApplicationThread; +import android.app.IServiceConnection; +import android.app.LoadedApk; +import android.app.admin.DevicePolicyManager; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; @@ -4363,4 +4367,27 @@ public abstract class Context { public boolean isCredentialEncryptedStorage() { return isCredentialProtectedStorage(); } + + /** + * @hide + */ + public IBinder getActivityToken() { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } + + /** + * @hide + */ + @Nullable + public IServiceConnection getServiceDispatcher(ServiceConnection conn, Handler handler, + int flags) { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } + + /** + * @hide + */ + public IApplicationThread getIApplicationThread() { + throw new RuntimeException("Not implemented. Must override in a subclass."); + } } diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index edc8d824e5c4..75336559088b 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -16,7 +16,10 @@ package android.content; +import android.annotation.Nullable; import android.annotation.SystemApi; +import android.app.IApplicationThread; +import android.app.IServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; @@ -857,4 +860,29 @@ public class ContextWrapper extends Context { public boolean isCredentialProtectedStorage() { return mBase.isCredentialProtectedStorage(); } + + /** + * @hide + */ + @Override + public IBinder getActivityToken() { + return mBase.getActivityToken(); + } + + /** + * @hide + */ + @Override + public IServiceConnection getServiceDispatcher(ServiceConnection conn, Handler handler, + int flags) { + return mBase.getServiceDispatcher(conn, handler, flags); + } + + /** + * @hide + */ + @Override + public IApplicationThread getIApplicationThread() { + return mBase.getIApplicationThread(); + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 538ba3b56e95..7e684476cf0a 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -42,6 +42,8 @@ import android.app.ActivityManagerNative; import android.app.AlarmManager; import android.app.AppGlobals; import android.app.IActivityManager; +import android.app.IApplicationThread; +import android.app.IServiceConnection; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -137,6 +139,7 @@ import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.JournaledFile; import com.android.internal.util.ParcelableString; @@ -1439,6 +1442,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { ServiceManager.getService(IpConnectivityLog.SERVICE_NAME)); } + PackageManager getPackageManager() { + return mContext.getPackageManager(); + } + PowerManagerInternal getPowerManagerInternal() { return LocalServices.getService(PowerManagerInternal.class); } @@ -5907,6 +5914,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + boolean isDeviceOwner(ActiveAdmin admin) { + return isDeviceOwner(admin.info.getComponent(), admin.getUserHandle().getIdentifier()); + } + public boolean isDeviceOwner(ComponentName who, int userId) { synchronized (this) { return mOwners.hasDeviceOwner() @@ -9428,6 +9439,77 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + @Override + public boolean bindDeviceAdminServiceAsUser( + @NonNull ComponentName admin, @NonNull IApplicationThread caller, + @Nullable IBinder activtiyToken, @NonNull Intent serviceIntent, + @NonNull IServiceConnection connection, int flags, @UserIdInt int targetUserId) { + if (!mHasFeature) { + return false; + } + Preconditions.checkNotNull(admin); + Preconditions.checkNotNull(caller); + Preconditions.checkNotNull(serviceIntent); + Preconditions.checkNotNull(connection); + final int callingUserId = mInjector.userHandleGetCallingUserId(); + Preconditions.checkArgument(callingUserId != targetUserId, + "target user id must be different from the calling user id"); + + synchronized (this) { + final ActiveAdmin callingAdmin = getActiveAdminForCallerLocked(admin, + DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + // Ensure the target user is valid. + if (isDeviceOwner(callingAdmin)) { + enforceManagedProfile(targetUserId, "Target user must be a managed profile"); + } else { + // Further lock down to profile owner in managed profile. + enforceManagedProfile(callingUserId, + "Only support profile owner in managed profile."); + if (mOwners.getDeviceOwnerUserId() != targetUserId) { + throw new SecurityException("Target user must be a device owner."); + } + } + } + final long callingIdentity = mInjector.binderClearCallingIdentity(); + try { + if (!mUserManager.isSameProfileGroup(callingUserId, targetUserId)) { + throw new SecurityException( + "Can only bind service across users under the same profile group"); + } + final String targetPackage; + synchronized (this) { + targetPackage = getOwnerPackageNameForUserLocked(targetUserId); + } + // STOPSHIP(b/31952368): Add policy to control which packages can talk. + if (TextUtils.isEmpty(targetPackage) || !targetPackage.equals(admin.getPackageName())) { + throw new SecurityException("Device owner and profile owner must be the same " + + "package in order to communicate."); + } + // Validate and sanitize the incoming service intent. + final Intent sanitizedIntent = + createCrossUserServiceIntent(serviceIntent, targetPackage); + if (sanitizedIntent == null) { + // Fail, cannot lookup the target service. + throw new SecurityException("Invalid intent or failed to look up the service"); + } + // Ask ActivityManager to bind it. Notice that we are binding the service with the + // caller app instead of DevicePolicyManagerService. + try { + return mInjector.getIActivityManager().bindService( + caller, activtiyToken, serviceIntent, + serviceIntent.resolveTypeIfNeeded(mContext.getContentResolver()), + connection, flags, mContext.getOpPackageName(), + targetUserId) != 0; + } catch (RemoteException ex) { + // Same process, should not happen. + } + } finally { + mInjector.binderRestoreCallingIdentity(callingIdentity); + } + // Fail to bind. + return false; + } + /** * Return true if a given user has any accounts that'll prevent installing a device or profile * owner {@code owner}. @@ -9591,4 +9673,46 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { ? mNetworkLogger.retrieveLogs(batchToken) : null; } + + /** + * Return the package name of owner in a given user. + */ + private String getOwnerPackageNameForUserLocked(int userId) { + return getDeviceOwnerUserId() == userId + ? mOwners.getDeviceOwnerPackageName() + : mOwners.getProfileOwnerPackage(userId); + } + + /** + * @param rawIntent Original service intent specified by caller. + * @param expectedPackageName The expected package name in the incoming intent. + * @return Intent that have component explicitly set. {@code null} if the incoming intent + * or target service is invalid. + */ + private Intent createCrossUserServiceIntent ( + @NonNull Intent rawIntent, @NonNull String expectedPackageName) { + if (rawIntent.getComponent() == null && rawIntent.getPackage() == null) { + Log.e(LOG_TAG, "Service intent must be explicit (with a package name or component): " + + rawIntent); + return null; + } + ResolveInfo info = mInjector.getPackageManager().resolveService(rawIntent, 0); + if (info == null || info.serviceInfo == null) { + Log.e(LOG_TAG, "Fail to look up the service: " + rawIntent); + return null; + } + if (!expectedPackageName.equals(info.serviceInfo.packageName)) { + Log.e(LOG_TAG, "Only allow to bind service in " + expectedPackageName); + return null; + } + if (info.serviceInfo.exported) { + Log.e(LOG_TAG, "The service must be unexported."); + return null; + } + // It is the system server to bind the service, it would be extremely dangerous if it + // can be exploited to bind any service. Set the component explicitly to make sure we + // do not bind anything accidentally. + rawIntent.setComponent(info.serviceInfo.getComponentName()); + return rawIntent; + } } diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java index fdd971b1fc00..190fc35e4873 100644 --- a/test-runner/src/android/test/mock/MockContext.java +++ b/test-runner/src/android/test/mock/MockContext.java @@ -17,6 +17,8 @@ package android.test.mock; import android.annotation.SystemApi; +import android.app.IApplicationThread; +import android.app.IServiceConnection; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; @@ -759,4 +761,23 @@ public class MockContext extends Context { public boolean isCredentialProtectedStorage() { throw new UnsupportedOperationException(); } + + /** {@hide} */ + @Override + public IBinder getActivityToken() { + throw new UnsupportedOperationException(); + } + + /** {@hide} */ + @Override + public IServiceConnection getServiceDispatcher(ServiceConnection conn, Handler handler, + int flags) { + throw new UnsupportedOperationException(); + } + + /** {@hide} */ + @Override + public IApplicationThread getIApplicationThread() { + throw new UnsupportedOperationException(); + } } |