| RFC-0179:基本剪貼簿服務 | |
|---|---|
| 狀態 | 已接受 |
| 區域 |
|
| 說明 | 建議建立基本剪貼簿服務,讓使用者在元件之間安全地複製及貼上文字內容,不論執行元件為何。 |
| 問題 | |
| Gerrit 變更 | |
| 作者 | |
| 審查人員 | |
| 提交日期 (年-月-日) | 2022-05-16 |
| 審查日期 (年-月-日) | 2020-07-18 |
摘要
這項 RFC 導入了兩個新的架構提供通訊協定 fuchsia.ui.clipboard.Writer 和 fuchsia.ui.clipboard.Reader,以及實作這些通訊協定的服務,可讓使用者對文字內容執行複製和貼上作業。
提振精神
許多現代使用者導向的作業系統都提供圖形介面,並具備剪貼簿功能 (請參閱先前技術),可讓使用者以互動方式將資料複製到系統提供的記憶體緩衝區或其他管道,然後將資料貼到其他位置。
過去,Fuchsia 曾以模組化代理程式的形式實作基本的剪貼簿通訊協定,但這段程式碼已於 2019 年移除。
在這份 RFC 中,我們建議導入新的剪貼簿通訊協定和實作方式,Fuchsia 產品可選擇整合。目前最迫切的需求是能夠複製及貼上 Unicode 文字,因此第一版會著重於此。
許多現有作業系統的剪貼簿設施最初設計時並未提供安全防護措施,因此任何程序都能隨時觀察和/或修改剪貼簿,使用者卻毫不知情或無意為之。在 Fuchsia 上,我們致力於設計時就考量安全性,方法如下:
- 根據最小權限原則,透過精細功能保護剪貼簿存取權
- 嘗試限制剪貼簿存取權,但前提是必須在前景視窗 (在 Fuchsia 的 Scenic 術語中稱為「View」) 中具有輸入焦點
- 僅在其他方法皆不可行時,才提供剪貼簿背景存取權
利害關係人
輔導員
davemoore@google.com
審查人員
紫紅色 HCI:neelsa@google.com、quiche@google.com
安全性:palmer@google.com
隱私權:enoharemaien@google.com
Chromium:wez@google.com
Flutter:jmccandless@google.com
諮詢對象
azaslavsky、carolineliu@google.com、chaopeng@google.com、cpu@google.com、 ddorwin@google.com、fmil@google.com、jsankey@google.com、tjdetwiler@google.com
社會化
- 在 Fuchsia 輸入團隊文件審查中討論
- 在 Fuchsia 安全性諮詢時間討論
設計
權限等級
在圖形介面環境中,剪貼簿的存取範圍可分為三種層級:
- 透過 Shell 媒介,取決於焦點
元件只能在回應圖形 Shell 判斷的明確使用者動作時存取剪貼簿,且元件目前必須具有輸入焦點。 - 焦點相關
只要元件具有輸入焦點,隨時都能存取剪貼簿。 - 無限制
元件隨時可以存取剪貼簿。
在本 RFC 中,我們只會涵蓋(2) 焦點相關範圍。
目前我們不打算設計及實作範圍 (1) 和 (3),這類範圍需要另一個 RFC。
用途
在初始 RFC 中,我們考慮了幾個簡單但常見的用途:
- 在網路瀏覽器中,將網頁內文中的網址複製到網址列
- 將網頁瀏覽器中的殼層指令複製到終端機
- 將資訊從網路瀏覽器複製到工作站產品的回饋對話方塊 (以 Flutter 實作)
通訊協定和服務
我們在合作夥伴 SDK 中導入了兩個可探索的新 FIDL 協定,分別是 fuchsia.ui.clipboard.FocusedReaderRegistry 和 fuchsia.ui.clipboard.FocusedWriterRegistry。這些通訊協定將由在工作階段領域中執行的全新元件 clipboard.cm 實作及公開。這個元件會納入工作站產品,並可供任何其他需要這個元件的 Fuchsia 產品使用。
獲得 FocusedWriterRegistry 和 FocusedReaderRegistry 功能的用戶端元件,分別可以要求 fuchsia.ui.clipboard.Writer 和 fuchsia.ui.clipboard.Reader 的執行個體。他們隨時可以要求這些連線 (假設他們擁有有效的 ViewRef),但如果用戶端的檢視畫面沒有輸入焦點,Writer 和 Reader 的方法就會傳回錯誤。
library fuchsia.ui.clipboard;
/// A protocol that allows graphical clients that own
/// [`ViewRef`s](https://cs.opensource.google/fuchsia/fuchsia/+/main:/src/development/graphics/scenic/concepts/view_ref) to request read ("paste")
/// access to the clipboard. Clients can register for access at any time, but `GetItem` calls will
/// only succeed while the view has input focus.
@discoverable
protocol FocusedReaderRegistry {
/// If the `ViewRef` is valid, the clipboard server will allow the client to send commands using
/// the given `Reader`. If the `ViewRef` later becomes invalid, the `Reader`'s channel will be
/// closed.
RequestReader(resource table {
1: view_ref fuchsia.ui.views.ViewRef;
2: reader_request server_end:Reader;
}) -> (table {}) error ClipboardError;
};
/// A protocol that allows graphical clients that own `ViewRef`s to request write ("copy") access to
/// the clipboard. Clients can register for access at any time, but `SetItem` calls will only
/// succeed while the view has input focus.
@discoverable
protocol FocusedWriterRegistry {
/// If the `ViewRef` is valid, the clipboard server will allow the client to send commands using
/// the given `Writer`. If the `ViewRef` later becomes invalid, the `Writer`'s channel will be
/// closed.
RequestWriter(resource table {
1: view_ref fuchsia.ui.views.ViewRef;
2: writer_request server_end:Writer;
}) -> (table {}) error ClipboardError;
};
/// Allows data to be read from the clipboard, i.e. pasted.
protocol Reader {
/// Reads a single item from the clipboard. If the client's `View` does not have input focus, an
/// error will be returned. If there is no item on the clipboard, `ClipboardError.EMPTY` will
/// be returned.
GetItem(table {}) -> (ClipboardItem) error ClipboardError;
};
/// Allows data to be written to the clipboard, i.e. copied.
protocol Writer {
/// Writes a single item to the clipboard. If the client's `View` does not have input focus, an
/// error will be returned.
SetItem(ClipboardItem) -> (table {}) error ClipboardError;
/// Clears the contents of the clipboard. If the client's `View` does not have input focus, an
/// error will be returned.
Clear(table {}) -> (table {}) error ClipboardError;
};
/// Set of errors that can be returned by the clipboard server.
type ClipboardError = flexible enum {
/// An internal error occurred. All the client can do is try again later.
INTERNAL = 1;
/// The clipboard was empty, or the requested item(s) were not present on the clipboard.
EMPTY = 2;
/// The client sent an invalid request, e.g. missing requiring fields.
INVALID_REQUEST = 3;
/// The client sent the server an invalid `ViewRef` or a `ViewRef` that is already associated
/// with another client.
INVALID_VIEW_REF = 4;
/// The client attempted to perform an operation that requires input focus, at a moment when
/// it did not have input focus. The client should wait until it has focus again before
/// retrying.
UNAUTHORIZED = 5;
};
在初始版本中,剪貼簿僅支援複製及貼上大小上限為 32 KB 的 UTF-8 字串。用戶端「可以」為資料指定 MIME 類型,預設值為 "text/plain;charset=UTF-8"。
後續修訂版本將支援 VMO,可複製及貼上任意資料。
/// The maximum length of a plain-text clipboard item in bytes. Although FIDL messages support
/// larger messages, this limit allows space to be reserved for potential other fields in the
/// message. Larger payloads will be supported by VMOs in `ClipboardItemData` in future revisions.
const MAX_TEXT_LENGTH uint32 = 32768;
/// The maximum length of a MIME Type identifier. Per
/// [IETF RFC 4288](https://datatracker.ietf.org/doc/html/rfc4288#section-4.2), a MIME type may have
/// up to 127 characters before and 127 characters after the slash, for a total of 255.
const MAX_MIME_TYPE_LENGTH uint32 = 255;
/// A single item on the clipboard, consisting of a MIME type hint and a payload.
type ClipboardItem = resource table {
/// MIME type of the data, according to the client that placed the data on the clipboard.
/// *Note:* The clipboard service does not validate clipboard items and does not guarantee that
/// they conform to the given MIME type's specifications.
1: mime_type_hint string:MAX_MIME_TYPE_LENGTH;
/// The payload of the clipboard item.
2: payload ClipboardItemData;
};
/// The payload of a `ClipboardItem`. Future expansions will support additional transport formats.
type ClipboardItemData = flexible resource union {
/// A UTF-8 string.
1: text string:MAX_TEXT_LENGTH;
};
實作
這項作業會分幾個階段進行:
- 如上所述,提交新的
fuchsia.ui.clipboardFIDL 程式庫以供 API 審查。 - 實作在工作階段領域中執行的全新剪貼簿伺服器元件,公開
fuchsia.ui.clipboard.FocusedWriterRegistry和fuchsia.ui.clipboard.FocusedReaderRegistry通訊協定。 - 使用管理 Scenic 檢視區塊的簡單元件,展示與新通訊協定的整合。
- 將新通訊協定的支援功能整合至 Chromium 和 Flutter 執行器。
效能
新增服務會使用更多儲存空間來儲存二進位檔,以及二進位檔和剪貼簿內容的記憶體。每個註冊剪貼簿存取的用戶端都會保留開啟的 Zircon 管道,進而耗用資源。
安全性考量
必須通過安全性審查。
跨元件通訊
剪貼簿服務的導入代表了新的跨元件通訊管道。這會為元件開啟新的可能性,讓元件有意或無意地利用彼此的安全性漏洞。
不受信任的內容
剪貼簿服務不保證 ClipboardItem 資料或 MIME 類型提示的可靠性。因此,用戶端「不應」信任收到的內容,且「應」驗證資料是否適合自己的用途。
具體來說,用戶端「應該」在低權限的「沙箱」程序中,以比 C/C++ 更安全的程式設計語言,執行複雜格式的任何剖析、解譯或轉換作業。(詳情請參閱規則 2)。
對於 ClipboardItemData.text 變數,每個用戶端 (以及剪貼簿服務) 使用的 FIDL 程式庫會自動執行 UTF-8 驗證。
不過,即使是採用有效 UTF-8 編碼的純文字,只要某個元件能透過剪貼簿將任意文字傳送至另一個元件,就可能開啟各種攻擊手法的大門,包括:
- 文字小工具中的溢位錯誤
- 文字轉譯堆疊中的錯誤
- 同形字攻擊 (使用實際上不同的字元,以視覺上相似的字形欺騙使用者,例如偷偷將使用者導向網路釣魚網域)
- 應用程式專屬文字剖析中的錯誤
- 將非預期的程式碼貼到命令提示字元
對於處理或顯示任何第三方或使用者提供內容的應用程式而言,這些問題大多已是需要考量的重點,但剪貼簿帶來了額外的挑戰。惡意應用程式可能會在使用者執行有效的複製指令時,將非預期的資料 (即未顯示選取的資料) 放到剪貼簿,誘騙使用者在貼上資料時,成為混淆的代理人。
未經授權的存取
如上文所述,元件可能會在未經授權或使用者不知情的情況下,從剪貼簿讀取或寫入資料。
這項問題的解決方法如下:
- 可細部分別授予或拒絕複製及貼上功能的存取權的通訊協定
- 在
fuchsia.ui.clipboard.Focused*通訊協定中,只有具有輸入焦點的前景檢視畫面才能取得剪貼簿存取權
在日後擴充剪貼簿 API 時,我們可能會提供用於觀察剪貼簿 API 事件的通訊協定,系統殼層可使用這項通訊協定,在剪貼簿遭到存取時顯示視覺通知。
日後,隨著不受信任的元件和新的剪貼簿使用案例加入,我們必須重新考慮未經授權的讀取嘗試是否應以無聲方式傳回空白剪貼簿項目,而非 ClipboardError.UNAUTHORIZED,以減少揭露剪貼簿存取權的資訊。
ViewRef 和焦點驗證
剪貼簿服務會依據 Scenic 的焦點鏈系統,判斷目前聚焦的檢視區塊,因此有權存取剪貼簿。因此,剪貼簿判斷剪貼簿焦點的可靠程度,與 Scenic 判斷輸入焦點的可靠程度相同,但這項判斷有以下缺點:
ViewRefs,這些物件構成焦點鏈,可輕鬆複製並從一個元件傳送至另一個元件。透過這項機制,惡意元件可以互相合作,模擬彼此的輸入焦點。(不過,這至少需要原始擁有者信任複製的ViewRef。)ViewRef- 焦點變更會受到競爭條件的影響。
安全性審查結果
- 這個 MVP API 受到限制,因此可減少攻擊途徑。
- 我們依賴基礎 FIDL 還原序列化功能,在剪貼簿服務收到 UTF-8 字串時,正確驗證這些字串。這是一個攻擊面,但我們認為很安全,因為服務的字串還原序列化作業依賴 Rust 的
std::str::String實作項目,而該項目經過大量測試,確保記憶體安全,不僅在 Fuchsia 中經過測試,也在更廣泛的 Rust 生態系統中經過測試。 - 我們認為
ViewRef解決方案是追蹤檢視畫面焦點和使用者意圖的良好基礎,但請注意上述注意事項。
隱私權注意事項
必須接受隱私權審查。
未經授權貼上
未經授權存取剪貼簿內容會造成隱私權風險,因為惡意元件可能會取得使用者放在鍵盤上的任何私人資料。如上所述,fuchsia.ui.clipboard.Focused* 通訊協定會要求檢視畫面至少要有輸入焦點 (因此必須顯示在前景),才能存取剪貼簿,藉此降低這項風險。不過,這也表示應用程式只要短暫取得焦點,就能擷取剪貼簿內容,即使使用者並非有意這麼做。在未來,每當有元件讀取剪貼簿內容時,系統殼層通知就會提醒使用者。
旁路攻擊
在未來版本的剪貼簿服務中,加入任意資料型別和長度後,記憶體分析可能會揭露資訊 (例如剪貼簿緩衝區的大小),進而洩漏內容。
剪貼簿內容的保留時間
現階段僅支援複製短字串,剪貼簿內容會儲存在記憶體中,而非磁碟。
跨安全情境存取
Fuchsia 產品的 UI 元素可能在不同的安全環境中執行,例如代表不同使用者,或在預先驗證環境中執行。Fuchsia 必須禁止在安全環境界線之間共用剪貼簿內容。舉例來說,如果登入的使用者複製密碼,然後鎖定螢幕,系統必須禁止將該密碼貼到鎖定畫面對話方塊。
在每個安全環境中執行不同的剪貼簿服務執行個體,即可達到這種分離效果。
測試
這項功能會透過單元測試和整合測試進行測試:
- 剪貼簿服務中的單元測試
- 整個剪貼簿服務的整合測試
- 整合測試:剪貼簿服務、Scenic、輸入管道,以及 Flutter 或 Chromium 執行器之間的互動。
說明文件
fuchsia.ui.clipboard API 將以 fieldoc 記錄。
我們會使用簡單的元件來說明通訊協定的使用方式,該元件會管理 Scenic 檢視區塊,並附上詳細註解 (請參閱「實作」)。
缺點、替代方案和未知事項
在面向使用者的作業系統上,提供系統範圍的剪貼簿服務是無可替代的解決方案。單靠執行器無法提供這項功能,因為執行器無法在不同執行階段之間複製及貼上資料。
在上述設計選擇中,替代方法可能是從限制較多的「殼層中介、焦點依附」剪貼簿通訊協定,或完全不受限制的通訊協定開始 (請參閱「存取層級」)。
雖然透過殼層中介的做法更安全,但可能過於嚴格,不適合許多用途。舉例來說,
- 禁止應用程式在內容功能表中提供複製和貼上指令
- 干擾 Chromium 型執行器中的網頁剪貼簿 API 功能
雖然不受限制的方法適用於某些利基應用程式,但隱私權風險過高,不適合做為預設選項。
以輸入焦點為依據,從中間的存取層級開始,我們就能:
- 從一開始就優先在剪貼簿服務中提供某些安全和隱私權保障
- 鼓勵在 Fuchsia 剪貼簿的整合中採用最低權限原則
- 避免限制過於嚴苛,導致無法實際整合現有執行者
之後的作業
提供用於觀察剪貼簿 API 事件 (讀取和寫入) 的通訊協定,系統殼層可使用該通訊協定,在存取剪貼簿時顯示視覺通知。
擴充支援的資料格式和酬載大小,特別是透過傳送端用戶端擁有的 VMO。
既有技術和參考資料
紫紅色
Fuchsia 先前有最基本的剪貼簿 API,實作方式是採用模組化架構代理程式,可讓具備 fuchsia.modular.Clipboard 能力的任何元件儲存或擷取 UTF-8 字串。(這項功能已於 2019 年 11 月停用)。
Linux:X11
X11 提供多個稱為「選取項目」的內容儲存區,其中最常見的是 CLIPBOARD 和 PRIMARY (隱含文字選取剪貼簿)。
傳送應用程式會向 X 伺服器宣告,它「擁有」其中一個選取項目 (以特定資料格式,位於特定視窗中 (XSetSelectionOwner())),然後等待後續事件。
接收端應用程式會在其中一個視窗中,要求將選取內容轉換為支援的特定格式 (XConvertSelection())。
X 伺服器會將要求轉送至傳送應用程式,如果該應用程式支援要求的格式,就會透過 X 伺服器將資料傳送至接收應用程式。如果內容很大,必須分成最多 256 KB 的區塊。
如果原始視窗遭到毀損,選取內容就會遺失,因此在實務上,(1) 大多數應用程式都會將選取內容保留在使用者不會關閉的隱形視窗中,且 (2) 常見的 Linux 發行版本會包含剪貼簿管理工具,可接管選取內容的擁有權,即使原始擁有應用程式結束,選取內容仍會保留。
詳情請參閱 https://www.uninformativ.de/blog/postings/2017-04-02/0/POSTING-en.html。
Linux:Wayland
傳送應用程式 (必須處於焦點狀態) 會通知合成器有 wl_data_source,指出資料來源支援的 MIME 類型,並註冊事件監聽器。然後等待 send 事件。
接收端應用程式 (嘗試貼上時必須處於焦點狀態) 會監聽資料 offer 事件,判斷剪貼簿是否已填入資料。如要貼上內容,應用程式會呼叫 wl_data_offer_receive,並傳遞要求的 MIME 類型和檔案描述元 (通常是管道的寫入端)。
傳送應用程式會收到 send 事件,並寫入指定的檔案描述元;接收應用程式則會讀取另一端。
詳情請參閱 https://emersion.fr/blog/2020/wayland-clipboard-drag-and-drop/。
Windows (win32)
呼叫 OpenClipboard() 並傳入目前視窗的控制代碼,即可取得系統剪貼簿。傳送應用程式會呼叫 EmptyClipboard() 清除所有現有資料,然後呼叫 SetClipboardData(),並傳入整數資料型別 ID 和資料本身。傳送資料的記憶體必須使用 GlobalAlloc() 分配。
有幾種標準剪貼簿資料類型;或者,您也可以呼叫 RegisterClipboardFormat() 取得自訂全域格式 (顯然會持續存在,直到重新啟動為止),或使用特定範圍內的 ID 來表示私有剪貼簿格式。對於非私有格式,OS 會取得傳入物件的擁有權,並負責最終的銷毀作業;對於私有格式,剪貼簿銷毀時,原始視窗仍須負責清除作業。如要進行延遲格式轉換,原始視窗可以將 NULL 資料值傳遞至 SetClipboardData(),然後在回應 WM_RENDERFORMAT 時,轉譯要求的格式,並透過另一次呼叫 SetClipboardData(),將預留位置替換為該格式。建議開發人員盡可能以多種格式設定剪貼簿資料。
接收端應用程式也會擷取其視窗的全域剪貼簿控制代碼、檢查可用格式清單 (包括傳送端應用程式明確放置的格式,以及作業系統提供的自動轉換格式)、呼叫 GetClipboardData() 取得特定格式的剪貼簿物件控制代碼,然後呼叫 GlobalLock() 鎖定該全域資源並存取其內容。
此外,系統也提供方法,讓視窗註冊以監控剪貼簿內容的變更。
詳情請參閱 https://docs.microsoft.com/en-us/windows/win32/dataxchg/using-the-clipboard 和 https://docs.microsoft.com/en-us/windows/win32/dataxchg/clipboard-operations。
Android
傳送應用程式會建立 ClipData 物件,其中包含支援的 MIME 類型清單,並在 ClipData 中填入一或多個項目,可以是字串、指向任何資料的內容 URI,或是意圖 (適用於應用程式捷徑)。傳送應用程式接著會取得全域 ClipboardManager 物件的參照,並將 ClipData 物件傳遞至 setPrimaryClip()。
如果複製內容 URI,傳送應用程式必須匯出可為該 URI 提供資料的 ContentProvider。
接收應用程式會取得全域 ClipboardManager 的參照、檢查是否有主要剪輯片段,然後檢查是否支援任何 ClipData.Item 的資料類型。如果貼上的是純字串,接收應用程式只要呼叫 getText() 即可。如果是從內容 URI 貼上,接收應用程式必須建立 ContentResolver 執行個體,並使用指定的 URI query(),然後從傳回的 Cursor 擷取資料。
在 Android 12 以上版本中,如果某個應用程式存取其他應用程式傳送的 ClipData,作業系統會顯示浮動式訊息。
詳情請參閱 https://developer.android.com/guide/topics/text/copy-paste。
MacOS
系統範圍的「剪貼簿」可透過 NSPasteboard.general 欄位存取。
傳送應用程式會將實作 NSPasteboardWriting 通訊協定的物件陣列傳遞至 writeObjects() 方法,藉此複製項目。實作工具包括字串和其他常見資料類型,以及 NSPasteboardItem,可做為自訂資料類型的包裝函式。NSPasteboardWriting 會提供支援的統一類型識別碼 (UTI,相當於 Apple 的 MIME 類型) 清單,以及資料是否可立即使用或「承諾」提供。因此,NSPasteboardItem 可以直接包裝資料或資料供應商。
在接收端,應用程式可以查詢一般 NSPasteboard 可讀取的型別,包括可由篩選服務自動轉換的型別。然後選擇朗讀剪貼簿中儲存的所有或部分項目。
詳情請參閱 https://developer.apple.com/documentation/appkit/nspasteboard。
iOS
iOS 剪貼簿 API 與 macOS 類似。系統層級的剪貼簿可透過 UIPasteboard.general 存取。
如要傳送,有多種方法可將一或多個項目 (標示 UTI 類型) 新增至剪貼簿。也可以插入 NSItemProviders,這會延遲提供值。為方便起見,系統會在 UIPasteboard 執行個體上,為多個標準資料類型提供專屬的可讀取/寫入陣列屬性:strings、images、urls 和 colors,以及這些屬性的單數版本,方便您存取各類型中的第一個項目。
在接收端,使用者可以依索引或類型擷取任何選取的項目。
自 iOS 14 起,如果擷取其他應用程式放置在剪貼簿中的內容,系統會觸發通知。為減少實際貼上內容前出現的誤報通知,iOS 提供用戶端查詢剪貼簿上是否有特定資料類型 (hasStrings、hasImages) 的功能,且不必存取資料。
詳情請參閱 https://developer.apple.com/documentation/uikit/uipasteboard。
Web API
雖然網頁上的剪貼簿互動主要由網頁瀏覽器本身處理 (視作業系統而定),但也有可用的 JavaScript API,可讓網頁與剪貼簿互動,而不必依賴直接的使用者指令。
舊版 ClipboardEvent API 允許指令碼監聽 DOM Element 上的 "cut"、"copy" 或 "paste" 事件,然後存取事件的 clipboardData 欄位,藉此依 MIME 類型呼叫 setData 或 getData。您也可以透過程式輔助功能,在目前焦點元素上叫用 "cut"、"copy" 或 "paste"。基於隱私權考量,程式輔助貼上功能已無法使用,且在 "cut" 和 "copy" 事件中,系統無法讀取剪貼簿內容。
我們現在提供新的非同步 Clipboard API,並以網站使用者權限為防護機制。如果使用者授予權限,指令碼就能存取 navigator.clipboard,然後存取 writeText() 或 readText(),或 write()
ClipboardItem,其中包含以 MIME 類型做為鍵的一或多個 blob。(在某些瀏覽器中,非圖片 MIME 類型仍處於實驗階段)。
詳情請參閱 https://whatwebcando.today/clipboard.html 和 https://developer.mozilla.org/en-US/docs/Web/API/Clipboard。
ChromeOS
Chrome 擴充功能可以使用上述剪貼簿 API,但須遵守權限規定。