diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java index 8760efeafd8cc..dc1c700f5d080 100644 --- a/core/java/android/content/ClipboardManager.java +++ b/core/java/android/content/ClipboardManager.java @@ -103,7 +103,7 @@ public class ClipboardManager extends android.text.ClipboardManager { try { Preconditions.checkNotNull(clip); clip.prepareToLeaveProcess(true); - mService.setPrimaryClip(clip, mContext.getOpPackageName()); + mService.setPrimaryClip(clip, mContext.getOpPackageName(), mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -116,7 +116,7 @@ public class ClipboardManager extends android.text.ClipboardManager { */ public void clearPrimaryClip() { try { - mService.clearPrimaryClip(mContext.getOpPackageName()); + mService.clearPrimaryClip(mContext.getOpPackageName(), mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -132,7 +132,7 @@ public class ClipboardManager extends android.text.ClipboardManager { */ public @Nullable ClipData getPrimaryClip() { try { - return mService.getPrimaryClip(mContext.getOpPackageName()); + return mService.getPrimaryClip(mContext.getOpPackageName(), mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -149,7 +149,8 @@ public class ClipboardManager extends android.text.ClipboardManager { */ public @Nullable ClipDescription getPrimaryClipDescription() { try { - return mService.getPrimaryClipDescription(mContext.getOpPackageName()); + return mService.getPrimaryClipDescription(mContext.getOpPackageName(), + mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -163,7 +164,7 @@ public class ClipboardManager extends android.text.ClipboardManager { */ public boolean hasPrimaryClip() { try { - return mService.hasPrimaryClip(mContext.getOpPackageName()); + return mService.hasPrimaryClip(mContext.getOpPackageName(), mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -174,7 +175,8 @@ public class ClipboardManager extends android.text.ClipboardManager { if (mPrimaryClipChangedListeners.isEmpty()) { try { mService.addPrimaryClipChangedListener( - mPrimaryClipChangedServiceListener, mContext.getOpPackageName()); + mPrimaryClipChangedServiceListener, mContext.getOpPackageName(), + mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -189,7 +191,8 @@ public class ClipboardManager extends android.text.ClipboardManager { if (mPrimaryClipChangedListeners.isEmpty()) { try { mService.removePrimaryClipChangedListener( - mPrimaryClipChangedServiceListener); + mPrimaryClipChangedServiceListener, mContext.getOpPackageName(), + mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -226,7 +229,7 @@ public class ClipboardManager extends android.text.ClipboardManager { @Deprecated public boolean hasText() { try { - return mService.hasClipboardText(mContext.getOpPackageName()); + return mService.hasClipboardText(mContext.getOpPackageName(), mContext.getUserId()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/content/IClipboard.aidl b/core/java/android/content/IClipboard.aidl index 135a4363ef21e..0d5a46016f195 100644 --- a/core/java/android/content/IClipboard.aidl +++ b/core/java/android/content/IClipboard.aidl @@ -26,17 +26,18 @@ import android.content.IOnPrimaryClipChangedListener; * {@hide} */ interface IClipboard { - void setPrimaryClip(in ClipData clip, String callingPackage); - void clearPrimaryClip(String callingPackage); - ClipData getPrimaryClip(String pkg); - ClipDescription getPrimaryClipDescription(String callingPackage); - boolean hasPrimaryClip(String callingPackage); + void setPrimaryClip(in ClipData clip, String callingPackage, int userId); + void clearPrimaryClip(String callingPackage, int userId); + ClipData getPrimaryClip(String pkg, int userId); + ClipDescription getPrimaryClipDescription(String callingPackage, int userId); + boolean hasPrimaryClip(String callingPackage, int userId); void addPrimaryClipChangedListener(in IOnPrimaryClipChangedListener listener, - String callingPackage); - void removePrimaryClipChangedListener(in IOnPrimaryClipChangedListener listener); + String callingPackage, int userId); + void removePrimaryClipChangedListener(in IOnPrimaryClipChangedListener listener, + String callingPackage, int userId); /** * Returns true if the clipboard contains text; false otherwise. */ - boolean hasClipboardText(String callingPackage); + boolean hasClipboardText(String callingPackage, int userId); } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 17c56c3f216a5..a9e183ad5bf2d 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -11244,13 +11244,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Nullable final TextServicesManager getTextServicesManagerForUser() { + return getServiceManagerForUser("android", TextServicesManager.class); + } + + @Nullable + final ClipboardManager getClipboardManagerForUser() { + return getServiceManagerForUser(getContext().getPackageName(), ClipboardManager.class); + } + + @Nullable + final T getServiceManagerForUser(String packageName, Class managerClazz) { if (mTextOperationUser == null) { - return getContext().getSystemService(TextServicesManager.class); + return getContext().getSystemService(managerClazz); } try { - return getContext().createPackageContextAsUser( - "android", 0 /* flags */, mTextOperationUser) - .getSystemService(TextServicesManager.class); + Context context = getContext().createPackageContextAsUser( + packageName, 0 /* flags */, mTextOperationUser); + return context.getSystemService(managerClazz); } catch (PackageManager.NameNotFoundException e) { return null; } @@ -12540,8 +12550,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener && mEditor != null && mEditor.mKeyListener != null && getSelectionStart() >= 0 && getSelectionEnd() >= 0 - && ((ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE)) - .hasPrimaryClip()); + && getClipboardManagerForUser().hasPrimaryClip()); } boolean canPasteAsPlainText() { @@ -12549,9 +12558,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return false; } - final ClipData clipData = - ((ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE)) - .getPrimaryClip(); + final ClipData clipData = getClipboardManagerForUser().getPrimaryClip(); final ClipDescription description = clipData.getDescription(); final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN); final CharSequence text = clipData.getItemAt(0).getText(); @@ -12594,8 +12601,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * Paste clipboard content between min and max positions. */ private void paste(int min, int max, boolean withFormatting) { - ClipboardManager clipboard = - (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); + ClipboardManager clipboard = getClipboardManagerForUser(); ClipData clip = clipboard.getPrimaryClip(); if (clip != null) { boolean didFirst = false; @@ -12638,8 +12644,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @CheckResult private boolean setPrimaryClip(ClipData clip) { - ClipboardManager clipboard = - (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); + ClipboardManager clipboard = getClipboardManagerForUser(); try { clipboard.setPrimaryClip(clip); } catch (Throwable t) { diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index d28482ef82abb..af25ad56a2a79 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -16,11 +16,14 @@ package com.android.server.clipboard; +import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; + +import android.Manifest; import android.annotation.Nullable; -import android.app.ActivityManager; +import android.annotation.UserIdInt; +import android.app.ActivityManagerInternal; import android.app.AppGlobals; import android.app.AppOpsManager; -import android.app.IActivityManager; import android.app.IUriGrantsManager; import android.app.KeyguardManager; import android.app.UriGrantsManager; @@ -145,6 +148,11 @@ class HostClipboardMonitor implements Runnable { /** * Implementation of the clipboard for copy and paste. + *

+ * Caution: exception for clipboard data and isInternalSysWindowAppWithWindowFocus, any of data + * is accessed by userId or uid should be in * the try segment between + * Binder.clearCallingIdentity and Binder.restoreCallingIdentity. + *

*/ public class ClipboardService extends SystemService { @@ -152,7 +160,7 @@ public class ClipboardService extends SystemService { private static final boolean IS_EMULATOR = SystemProperties.getBoolean("ro.kernel.qemu", false); - private final IActivityManager mAm; + private final ActivityManagerInternal mAmInternal; private final IUriGrantsManager mUgm; private final UriGrantsManagerInternal mUgmInternal; private final WindowManagerInternal mWm; @@ -173,7 +181,7 @@ public class ClipboardService extends SystemService { public ClipboardService(Context context) { super(context); - mAm = ActivityManager.getService(); + mAmInternal = LocalServices.getService(ActivityManagerInternal.class); mUgm = UriGrantsManager.getService(); mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class); mWm = LocalServices.getService(WindowManagerInternal.class); @@ -244,6 +252,87 @@ public class ClipboardService extends SystemService { } } + /** + * To check if the application has granted the INTERNAL_SYSTEM_WINDOW permission and window + * focus. + *

+ * All of applications granted INTERNAL_SYSTEM_WINDOW has the risk to leak clip information to + * the other user because INTERNAL_SYSTEM_WINDOW is signature level. i.e. platform key. Because + * some of applications have both of INTERNAL_SYSTEM_WINDOW and INTERACT_ACROSS_USERS_FULL at + * the same time, that means they show the same window to all of users. + *

+ * Unfortunately, all of applications with INTERNAL_SYSTEM_WINDOW starts very early and then + * the real window show is belong to user 0 rather user X. The result of + * WindowManager.isUidFocused checking user X window is false. + *

+ * @return true if the app granted INTERNAL_SYSTEM_WINDOW permission. + */ + private boolean isInternalSysWindowAppWithWindowFocus(String callingPackage) { + // Shell can access the clipboard for testing purposes. + if (mPm.checkPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW, + callingPackage) == PackageManager.PERMISSION_GRANTED) { + if (mWm.isUidFocused(Binder.getCallingUid())) { + return true; + } + } + + return false; + } + + /** + * To get the validate current userId. + *

+ * The intending userId needs to be validated by ActivityManagerInternal.handleIncomingUser. + * To check if the uid of the process have the permission to run as the userId. + * e.x. INTERACT_ACROSS_USERS_FULL or INTERACT_ACROSS_USERS permission granted. + *

+ *

+ * The application with the granted INTERNAL_SYSTEM_WINDOW permission should run as the output + * of ActivityManagerInternal.handleIncomingUser rather the userId of Binder.getCAllingUid(). + * To use the userId of Binder.getCallingUid() is the root cause that leaks the information + * comes from user 0 to user X. + *

+ * + * @param packageName the package name of the calling side + * @param userId the userId passed by the calling side + * @return return the intending userId that has been validated by ActivityManagerInternal. + */ + @UserIdInt + private int getIntendingUserId(String packageName, @UserIdInt int userId) { + final int callingUid = Binder.getCallingUid(); + final int callingUserId = UserHandle.getUserId(callingUid); + if (!UserManager.supportsMultipleUsers() || callingUserId == userId) { + return callingUserId; + } + + int intendingUserId = callingUserId; + intendingUserId = mAmInternal.handleIncomingUser(Binder.getCallingPid(), + Binder.getCallingUid(), userId, false /* allow all */, ALLOW_FULL_ONLY, + "checkClipboardServiceCallingUser", packageName); + + return intendingUserId; + } + + /** + * To get the current running uid who is intend to run as. + * In ording to distinguish the nameing and reducing the confusing names, the client client + * side pass userId that is intend to run as, + * @return return IntentingUid = validated intenting userId + + * UserHandle.getAppId(Binder.getCallingUid()) + */ + private int getIntendingUid(String packageName, @UserIdInt int userId) { + return UserHandle.getUid(getIntendingUserId(packageName, userId), + UserHandle.getAppId(Binder.getCallingUid())); + } + + /** + * To handle the difference between userId and intendingUserId, uid and intendingUid. + * + * userId means that comes from the calling side and should be validated by + * ActivityManagerInternal.handleIncomingUser. + * After validation of ActivityManagerInternal.handleIncomingUser, the userId is called + * 'intendingUserId' and the uid is called 'intendingUid'. + */ private class ClipboardImpl extends IClipboard.Stub { @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) @@ -260,92 +349,112 @@ public class ClipboardService extends SystemService { } @Override - public void setPrimaryClip(ClipData clip, String callingPackage) { + public void setPrimaryClip(ClipData clip, String callingPackage, @UserIdInt int userId) { synchronized (this) { if (clip == null || clip.getItemCount() <= 0) { throw new IllegalArgumentException("No items"); } - final int callingUid = Binder.getCallingUid(); + final int intendingUid = getIntendingUid(callingPackage, userId); + final int intendingUserId = UserHandle.getUserId(intendingUid); if (!clipboardAccessAllowed(AppOpsManager.OP_WRITE_CLIPBOARD, callingPackage, - callingUid)) { + intendingUid, intendingUserId)) { return; } - checkDataOwnerLocked(clip, callingUid); - setPrimaryClipInternal(clip, callingUid); + checkDataOwnerLocked(clip, intendingUid); + setPrimaryClipInternal(clip, intendingUid); } } @Override - public void clearPrimaryClip(String callingPackage) { + public void clearPrimaryClip(String callingPackage, @UserIdInt int userId) { synchronized (this) { - final int callingUid = Binder.getCallingUid(); + final int intendingUid = getIntendingUid(callingPackage, userId); + final int intendingUserId = UserHandle.getUserId(intendingUid); if (!clipboardAccessAllowed(AppOpsManager.OP_WRITE_CLIPBOARD, callingPackage, - callingUid)) { + intendingUid, intendingUserId)) { return; } - setPrimaryClipInternal(null, callingUid); + setPrimaryClipInternal(null, intendingUid); } } @Override - public ClipData getPrimaryClip(String pkg) { + public ClipData getPrimaryClip(String pkg, @UserIdInt int userId) { synchronized (this) { + final int intendingUid = getIntendingUid(pkg, userId); + final int intendingUserId = UserHandle.getUserId(intendingUid); if (!clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, pkg, - Binder.getCallingUid()) || isDeviceLocked()) { + intendingUid, intendingUserId) + || isDeviceLocked(intendingUserId)) { return null; } - addActiveOwnerLocked(Binder.getCallingUid(), pkg); - return getClipboard().primaryClip; + addActiveOwnerLocked(intendingUid, pkg); + return getClipboard(intendingUserId).primaryClip; } } @Override - public ClipDescription getPrimaryClipDescription(String callingPackage) { + public ClipDescription getPrimaryClipDescription(String callingPackage, + @UserIdInt int userId) { synchronized (this) { + final int intendingUid = getIntendingUid(callingPackage, userId); + final int intendingUserId = UserHandle.getUserId(intendingUid); if (!clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, callingPackage, - Binder.getCallingUid()) || isDeviceLocked()) { + intendingUid, intendingUserId) + || isDeviceLocked(intendingUserId)) { return null; } - PerUserClipboard clipboard = getClipboard(); - return clipboard.primaryClip != null ? clipboard.primaryClip.getDescription() : null; + PerUserClipboard clipboard = getClipboard(intendingUserId); + return clipboard.primaryClip != null + ? clipboard.primaryClip.getDescription() : null; } } @Override - public boolean hasPrimaryClip(String callingPackage) { + public boolean hasPrimaryClip(String callingPackage, @UserIdInt int userId) { synchronized (this) { + final int intendingUid = getIntendingUid(callingPackage, userId); + final int intendingUserId = UserHandle.getUserId(intendingUid); if (!clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, callingPackage, - Binder.getCallingUid()) || isDeviceLocked()) { + intendingUid, intendingUserId) + || isDeviceLocked(intendingUserId)) { return false; } - return getClipboard().primaryClip != null; + return getClipboard(intendingUserId).primaryClip != null; } } @Override public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener, - String callingPackage) { + String callingPackage, @UserIdInt int userId) { synchronized (this) { - getClipboard().primaryClipListeners.register(listener, - new ListenerInfo(Binder.getCallingUid(), callingPackage)); + final int intendingUid = getIntendingUid(callingPackage, userId); + final int intendingUserId = UserHandle.getUserId(intendingUid); + getClipboard(intendingUserId).primaryClipListeners.register(listener, + new ListenerInfo(intendingUid, callingPackage)); } } @Override - public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) { + public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener, + String callingPackage, @UserIdInt int userId) { synchronized (this) { - getClipboard().primaryClipListeners.unregister(listener); + final int intendingUserId = getIntendingUserId(callingPackage, userId); + getClipboard(intendingUserId).primaryClipListeners.unregister(listener); } } @Override - public boolean hasClipboardText(String callingPackage) { + public boolean hasClipboardText(String callingPackage, int userId) { synchronized (this) { + final int intendingUid = getIntendingUid(callingPackage, userId); + final int intendingUserId = UserHandle.getUserId(intendingUid); if (!clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, callingPackage, - Binder.getCallingUid()) || isDeviceLocked()) { + intendingUid, intendingUserId) + || isDeviceLocked(intendingUserId)) { return false; } - PerUserClipboard clipboard = getClipboard(); + PerUserClipboard clipboard = getClipboard(intendingUserId); if (clipboard.primaryClip != null) { CharSequence text = clipboard.primaryClip.getItemAt(0).getText(); return text != null && text.length() > 0; @@ -355,11 +464,7 @@ public class ClipboardService extends SystemService { } }; - private PerUserClipboard getClipboard() { - return getClipboard(UserHandle.getCallingUserId()); - } - - private PerUserClipboard getClipboard(int userId) { + private PerUserClipboard getClipboard(@UserIdInt int userId) { synchronized (mClipboards) { PerUserClipboard puc = mClipboards.get(userId); if (puc == null) { @@ -370,7 +475,7 @@ public class ClipboardService extends SystemService { } } - List getRelatedProfiles(int userId) { + List getRelatedProfiles(@UserIdInt int userId) { final List related; final long origId = Binder.clearCallingIdentity(); try { @@ -397,7 +502,7 @@ public class ClipboardService extends SystemService { } } - void setPrimaryClipInternal(@Nullable ClipData clip, int callingUid) { + void setPrimaryClipInternal(@Nullable ClipData clip, int uid) { // Push clipboard to host, if any if (mHostClipboardMonitor != null) { if (clip == null) { @@ -412,8 +517,8 @@ public class ClipboardService extends SystemService { } // Update this user - final int userId = UserHandle.getUserId(callingUid); - setPrimaryClipInternal(getClipboard(userId), clip, callingUid); + final int userId = UserHandle.getUserId(uid); + setPrimaryClipInternal(getClipboard(userId), clip, uid); // Update related users List related = getRelatedProfiles(userId); @@ -426,11 +531,15 @@ public class ClipboardService extends SystemService { // primary clip in related users to prevent pasting stale content. if (!canCopy) { clip = null; + } else if (clip == null) { + // do nothing for canCopy == true and clip == null case + // To prevent from NPE happen in 'new ClipData(clip)' when run + // android.content.cts.ClipboardManagerTest#testClearPrimaryClip } else { // We want to fix the uris of the related user's clip without changing the // uris of the current user's clip. // So, copy the ClipData, and then copy all the items, so that nothing - // is shared in memmory. + // is shared in memory. clip = new ClipData(clip); for (int i = clip.getItemCount() - 1; i >= 0; i--) { clip.setItemAt(i, new ClipData.Item(clip.getItemAt(i))); @@ -443,7 +552,7 @@ public class ClipboardService extends SystemService { final boolean canCopyIntoProfile = !hasRestriction( UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, id); if (canCopyIntoProfile) { - setPrimaryClipInternal(getClipboard(id), clip, callingUid); + setPrimaryClipInternal(getClipboard(id), clip, uid); } } } @@ -452,7 +561,7 @@ public class ClipboardService extends SystemService { } void setPrimaryClipInternal(PerUserClipboard clipboard, @Nullable ClipData clip, - int callingUid) { + int uid) { revokeUris(clipboard); clipboard.activePermissionOwners.clear(); if (clip == null && clipboard.primaryClip == null) { @@ -460,7 +569,7 @@ public class ClipboardService extends SystemService { } clipboard.primaryClip = clip; if (clip != null) { - clipboard.primaryClipUid = callingUid; + clipboard.primaryClipUid = uid; } else { clipboard.primaryClipUid = android.os.Process.NOBODY_UID; } @@ -479,7 +588,7 @@ public class ClipboardService extends SystemService { clipboard.primaryClipListeners.getBroadcastCookie(i); if (clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, li.mPackageName, - li.mUid)) { + li.mUid, UserHandle.getUserId(li.mUid))) { clipboard.primaryClipListeners.getBroadcastItem(i) .dispatchPrimaryClipChanged(); } @@ -494,13 +603,12 @@ public class ClipboardService extends SystemService { } } - private boolean isDeviceLocked() { - int callingUserId = UserHandle.getCallingUserId(); + private boolean isDeviceLocked(@UserIdInt int userId) { final long token = Binder.clearCallingIdentity(); try { final KeyguardManager keyguardManager = getContext().getSystemService( KeyguardManager.class); - return keyguardManager != null && keyguardManager.isDeviceLocked(callingUserId); + return keyguardManager != null && keyguardManager.isDeviceLocked(userId); } finally { Binder.restoreCallingIdentity(token); } @@ -585,7 +693,7 @@ public class ClipboardService extends SystemService { } finally { Binder.restoreCallingIdentity(oldIdentity); } - PerUserClipboard clipboard = getClipboard(); + PerUserClipboard clipboard = getClipboard(UserHandle.getUserId(uid)); if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) { final int N = clipboard.primaryClip.getItemCount(); for (int i=0; i