fix(android): prevent crash on large clipboard payload (>1MB) (#16767)

* Limit oversized remote clipboard text on Android

* Add write permission

* Increase MAX_CLIPBOARD_TEXT_CODE_UNITS to 1MB

* fix: revert unrelated workflow permission changes

* fix(android): address review feedback on clipboard size and exception handling

- Replace UTF-16 character count check with a serialized Parcel size check capped at 800 KB, leaving headroom for Android Binder transaction overhead.
- Wrap setPrimaryClip() in a try-catch to safely absorb direct and runtime-wrapped Binder exceptions (e.g. TransactionTooLargeException) without crashing.
- Assign lastUpdatedClipData only after a successful clipboard write to preserve consistent synchronization bookkeeping on failure.
This commit is contained in:
ignat10 authored and GitHub committed 2026-11-02 12:14:30 +08:00
1 parent 23e5bb471a
commit f9586e1714
1 file changed
+23 -2
@@ -7,6 +7,7 @@ import java.util.TimerTask
import android.content.ClipData
import android.content.ClipDescription
import android.content.ClipboardManager
import android.os.Parcel
import android.util.Log
import androidx.annotation.Keep
@@ -17,6 +18,10 @@ import hbb.MessageOuterClass.MultiClipboards
import ffi.FFI
class RdClipboardManager(private val clipboardManager: ClipboardManager) {
companion object {
private const val MAX_CLIPBOARD_PARCEL_BYTES = 800 * 1024
}
private val logTag = "RdClipboardManager"
private val supportedMimeTypes = arrayOf(
ClipDescription.MIMETYPE_TEXT_PLAIN,
@@ -191,7 +196,23 @@ class RdClipboardManager(private val clipboardManager: ClipboardManager) {
return
}
val clipData = ClipData(clipDescription, item)
lastUpdatedClipData = clipData
clipboardManager.setPrimaryClip(clipData)
val parcel = Parcel.obtain()
val isTooLarge = try {
clipData.writeToParcel(parcel, 0)
parcel.dataSize() > MAX_CLIPBOARD_PARCEL_BYTES
} finally {
parcel.recycle()
}
if (isTooLarge) {
Log.w(logTag, "Remote clipboard text is too large for Android clipboard")
return
}
try {
clipboardManager.setPrimaryClip(clipData)
lastUpdatedClipData = clipData
} catch (e: Exception) {
Log.e(logTag, "Failed to set primary clip", e)
}
}
}