Skip to content
This repository has been archived by the owner on Oct 29, 2024. It is now read-only.

Commit

Permalink
add per-app clipboard access setting
Browse files Browse the repository at this point in the history
  • Loading branch information
octocorvus committed Mar 23, 2024
1 parent 6db320e commit 6ce6622
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 6 deletions.
1 change: 1 addition & 0 deletions core/java/android/ext/SettingsIntents.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public class SettingsIntents {
public static final String APP_NATIVE_DEBUGGING = "android.settings.OPEN_APP_NATIVE_DEBUGGING_SETTINGS";
public static final String APP_MEMTAG = "android.settings.OPEN_APP_MEMTAG_SETTINGS";
public static final String APP_HARDENED_MALLOC = "android.settings.OPEN_APP_HARDENED_MALLOC_SETTINGS";
public static final String APP_CLIPBOARD = "android.settings.OPEN_APP_CLIPBOARD_SETTINGS";

public static Intent getAppIntent(String action, String pkgName) {
var i = new Intent(action);
Expand Down
4 changes: 4 additions & 0 deletions core/java/android/ext/settings/ExtSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ public boolean validateValue(String val) {
public static final BoolSysProperty ALLOW_NATIVE_DEBUG_BY_DEFAULT = new BoolSysProperty(
"persist.native_debug", defaultBool(R.bool.setting_default_allow_native_debugging));

public static final BoolSetting DENY_CLIPBOARD_READ_BY_DEFAULT = new BoolSetting(
Setting.Scope.PER_USER, "deny_clipboard_read",
defaultBool(R.bool.setting_default_deny_clipboard_read));

// AppCompatConfig specifies which hardening features are compatible/incompatible with a
// specific app.
// This setting controls whether incompatible hardening features would be disabled by default
Expand Down
37 changes: 37 additions & 0 deletions core/java/android/ext/settings/app/AswDenyClipboardRead.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package android.ext.settings.app;

import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.GosPackageState;
import android.content.pm.GosPackageStateBase;
import android.ext.settings.ExtSettings;

/** @hide */
public class AswDenyClipboardRead extends AppSwitch {
public static final AswDenyClipboardRead I = new AswDenyClipboardRead();

private AswDenyClipboardRead() {
gosPsFlag = GosPackageState.FLAG_DENY_CLIPBOARD_READ;
gosPsFlagNonDefault = GosPackageState.FLAG_DENY_CLIPBOARD_READ_NON_DEFAULT;
gosPsFlagSuppressNotif = GosPackageState.FLAG_DENY_CLIPBOARD_READ_SUPPRESS_NOTIF;
}

@Override
public Boolean getImmutableValue(Context ctx, int userId, ApplicationInfo appInfo,
@Nullable GosPackageStateBase ps, StateInfo si) {
if (appInfo.isSystemApp()) {
si.immutabilityReason = IR_IS_SYSTEM_APP;
return false;
}

return null;
}

@Override
protected boolean getDefaultValueInner(Context ctx, int userId, ApplicationInfo appInfo,
@Nullable GosPackageStateBase ps, StateInfo si) {
si.defaultValueReason = DVR_DEFAULT_SETTING;
return ExtSettings.DENY_CLIPBOARD_READ_BY_DEFAULT.get(ctx, userId);
}
}
3 changes: 3 additions & 0 deletions core/res/res/values/config_ext.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,7 @@

<string name="config_first_party_app_source_package_name">app.grapheneos.apps</string>
<java-symbol type="string" name="config_first_party_app_source_package_name" />

<bool name="setting_default_deny_clipboard_read">false</bool>
<java-symbol type="bool" name="setting_default_deny_clipboard_read" />
</resources>
3 changes: 3 additions & 0 deletions core/res/res/values/string_ext.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,7 @@
<string name="notif_hmalloc_crash_title">hardened_malloc detected an error in %1$s</string>
<java-symbol type="string" name="notif_hmalloc_crash_title" />

<string name="notif_clipboard_read_deny_title">%1$s tried to read clipboard</string>
<java-symbol type="string" name="notif_clipboard_read_deny_title" />

</resources>
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.android.server.clipboard;

import android.content.ClipData;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.GosPackageState;
import android.content.pm.PackageManagerInternal;
import android.ext.SettingsIntents;
import android.ext.settings.app.AswDenyClipboardRead;
import android.os.Process;

import com.android.internal.R;
import com.android.server.LocalServices;
import com.android.server.ext.AppSwitchNotification;
import com.android.server.pm.pkg.GosPackageStatePm;

public class ClipboardAccessHelper {
static final ClipData dummyClip = ClipData.newPlainText(null, "");

static boolean isReadBlockedForPackage(Context ctx, String pkgName, int userId) {
var pmi = LocalServices.getService(PackageManagerInternal.class);
ApplicationInfo appInfo = pmi.getApplicationInfo(pkgName, 0, Process.SYSTEM_UID, userId);
if (appInfo == null) {
return true;
}
GosPackageStatePm ps = pmi.getGosPackageState(pkgName, userId);
return AswDenyClipboardRead.I.get(ctx, userId, appInfo, ps);
}

static void maybeNotifyAccessDenied(Context ctx, String pkgName, int userId) {
var pmi = LocalServices.getService(PackageManagerInternal.class);
ApplicationInfo appInfo = pmi.getApplicationInfo(pkgName, 0, Process.SYSTEM_UID, userId);
if (appInfo == null) {
return;
}
var n = AppSwitchNotification.create(ctx, appInfo, SettingsIntents.APP_CLIPBOARD);
n.titleRes = R.string.notif_clipboard_read_deny_title;
n.gosPsFlagSuppressNotif = GosPackageState.FLAG_DENY_CLIPBOARD_READ_SUPPRESS_NOTIF;
n.maybeShow();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -682,7 +682,12 @@ public ClipData getPrimaryClip(
if (clipboard == null) {
return null;
}
showAccessNotificationLocked(pkg, intendingUid, intendingUserId, clipboard);
boolean isReadBlocked = isReadBlockedForPkg(intendingUid, pkg, userId, clipboard);
showAccessNotificationLocked(pkg, intendingUid, intendingUserId, clipboard,
isReadBlocked);
if (isReadBlocked) {
return clipboard.primaryClip != null ? ClipboardAccessHelper.dummyClip : null;
}
notifyTextClassifierLocked(clipboard, pkg, intendingUid);
if (clipboard.primaryClip != null) {
scheduleAutoClear(userId, intendingUid, intendingDeviceId);
Expand Down Expand Up @@ -710,8 +715,13 @@ public ClipDescription getPrimaryClipDescription(
}
synchronized (mLock) {
Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
return (clipboard != null && clipboard.primaryClip != null)
? clipboard.primaryClip.getDescription() : null;
ClipDescription clipDesc = null;
if (clipboard != null && clipboard.primaryClip != null) {
clipDesc = isReadBlockedForPkg(intendingUid, callingPackage, userId, clipboard)
? ClipboardAccessHelper.dummyClip.getDescription()
: clipboard.primaryClip.getDescription();
}
return clipDesc;
}
}

Expand Down Expand Up @@ -809,7 +819,8 @@ public boolean hasClipboardText(
}
synchronized (mLock) {
Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
if (clipboard != null && clipboard.primaryClip != null) {
if (clipboard != null && clipboard.primaryClip != null
&& !isReadBlockedForPkg(intendingUid, callingPackage, userId, clipboard)) {
CharSequence text = clipboard.primaryClip.getItemAt(0).getText();
return text != null && text.length() > 0;
}
Expand Down Expand Up @@ -838,7 +849,8 @@ public String getPrimaryClipSource(
}
synchronized (mLock) {
Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
if (clipboard != null && clipboard.primaryClip != null) {
if (clipboard != null && clipboard.primaryClip != null
&& !isReadBlockedForPkg(intendingUid, callingPackage, userId, clipboard)) {
return clipboard.mPrimaryClipPackage;
}
return null;
Expand Down Expand Up @@ -1047,6 +1059,14 @@ private void sendClipChangedBroadcast(Clipboard clipboard) {
ListenerInfo li = (ListenerInfo)
clipboard.primaryClipListeners.getBroadcastCookie(i);

if (isReadBlockedForPkg(
li.mUid,
li.mPackageName,
UserHandle.getUserId(li.mUid),
clipboard)) {
continue;
}

if (clipboardAccessAllowed(
AppOpsManager.OP_READ_CLIPBOARD,
li.mPackageName,
Expand Down Expand Up @@ -1438,7 +1458,7 @@ private boolean isDefaultIme(int userId, String packageName) {
*/
@GuardedBy("mLock")
private void showAccessNotificationLocked(String callingPackage, int uid, @UserIdInt int userId,
Clipboard clipboard) {
Clipboard clipboard, boolean isReadBlocked) {
if (clipboard.primaryClip == null) {
return;
}
Expand Down Expand Up @@ -1472,6 +1492,18 @@ private void showAccessNotificationLocked(String callingPackage, int uid, @UserI
clipboard.deviceId) == uid) {
return;
}

// Shows a system notification instead, when clipboard access is blocked.
if (isReadBlocked) {
String message =
getContext().getString(R.string.notif_clipboard_read_deny_title, callingPackage);
Slog.d(TAG, message);
Binder.withCleanCallingIdentity(
() -> ClipboardAccessHelper.maybeNotifyAccessDenied(getContext(),
callingPackage, userId));
return;
}

// Don't notify if already notified for this uid and clip.
if (clipboard.mNotifiedUids.get(uid)) {
return;
Expand Down Expand Up @@ -1615,4 +1647,9 @@ private TextClassificationManager createTextClassificationManagerAsUser(@UserIdI
Context context = getContext().createContextAsUser(UserHandle.of(userId), /* flags= */ 0);
return context.getSystemService(TextClassificationManager.class);
}

private boolean isReadBlockedForPkg(int uid, String pkg, int userId, Clipboard clipboard) {
return !UserHandle.isSameApp(uid, clipboard.primaryClipUid)
&& ClipboardAccessHelper.isReadBlockedForPackage(getContext(), pkg, userId);
}
}

0 comments on commit 6ce6622

Please sign in to comment.