RFC-0179:基本剪貼簿服務

RFC-0179:基本剪貼簿服務
狀態已接受
區域
  • HCI
說明

我們提議提供基本的剪貼簿服務,讓使用者無論執行何種執行器,都能在元件之間安全地複製及貼上文字內容。

問題
  • 97874
變更
作者
審查人員
提交日期 (年/月)2022-05-16
審查日期 (年/月)2020-07-18

摘要

這個 RFC 引進了兩種新的架構提供的通訊協定 fuchsia.ui.clipboard.Writerfuchsia.ui.clipboard.Reader,以及實作這些通訊協定的服務,可讓使用者對文字內容執行複製與貼上作業。

提振精神

許多現代化使用者使用的作業系統都有圖形殼層,提供了剪貼簿功能 (請參閱先前的圖片),可讓使用者以互動方式將資料複製到系統提供的記憶體緩衝區或其他管道,然後再將資料貼到另一個位置。

過去,Fuchsia 以模組化代理程式的形式實作詳細的剪貼簿通訊協定,但這段程式碼已於 2019 年移除。

在本 RFC 中,我們提議推出新的剪貼簿通訊協定和實作方式,讓 Fuchsia 產品選擇整合。最需要的是複製及貼上 Unicode 文字,因此這會是第一個疊代的焦點。

許多現有作業系統的剪貼簿設施是最初在設計時未提供安全防護機制,讓任何程序可以隨時觀察和/或修改剪貼簿,而不需要使用者告知或意圖。Fuchsia 的設計宗旨是在設計時考量安全性:

  • 遵循最低權限原則,透過精細的功能保護剪貼簿存取權
  • 嘗試限制剪貼簿存取,意味著在前景視窗 ( Fuchsia 風景術語中為「檢視」) 提供輸入焦點
  • 只有在不得已的情況下,才提供背景剪貼簿存取功能

相關人員

講師

davemoore@google.com

審查人員

Fuchsia HCI:neelsa@google.com、quiche@google.com

安全性: Paler@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 輸入團隊中討論文件審查
  • 在福奇 (Fussia Security) 諮詢時間討論

設計

權限等級

圖形殼層環境中剪貼簿的存取權範圍可分為三個層級:

  1. 依據圖形殼層決定的明確使用者動作
    元件只有在元件目前具有輸入焦點時,才得以存取剪貼簿。
  2. 聚焦
    具有輸入焦點的元件隨時可以存取剪貼簿。
  3. 無限制
    元件隨時可以存取剪貼簿。

在本 RFC 中,我們僅涵蓋 (2) 與焦點相依的範圍。

目前不規劃範圍 (1) 和 (3) 的設計和實作,需要另一個 RFC。

用途

對於初始 RFC,我們會考慮幾個簡單但常見的用途:

  • 在網路瀏覽器中,將網頁內文的網址複製到網址列
  • 將殼層指令從網路瀏覽器複製到終端機
  • 將資訊從網路瀏覽器複製到工作站產品的意見回饋對話方塊 (在 Flutter 中實作)

通訊協定與服務

我們在合作夥伴 SDK 中推出了兩個可探索的新 FIDL 通訊協定:fuchsia.ui.clipboard.FocusedReaderRegistryfuchsia.ui.clipboard.FocusedWriterRegistry。這些通訊協定將由新的元件 clipboard.cm 實作及公開,該元件會在工作階段運作領域中執行。該元件將包含在工作站產品中,並可用於任何其他需要它的 Fuchsia 產品。

獲得 FocusedWriterRegistryFocusedReaderRegistry 功能的用戶端元件,可以分別要求 fuchsia.ui.clipboard.Writerfuchsia.ui.clipboard.Reader 的執行個體。使用者隨時都可能要求這些連線 (假設其具備有效的 ViewRef),但如果用戶端的檢視畫面沒有輸入焦點,則 WriterReader 的方法會傳回錯誤。

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;
};

實作

這項作業將分為以下幾個階段進行:

  1. 將新的 fuchsia.ui.clipboard FIDL 程式庫提交 (如上所示) 以進行 API 審查。
  2. 實作在工作階段運作範圍中執行的新剪貼簿伺服器元件,以公開 fuchsia.ui.clipboard.FocusedWriterRegistryfuchsia.ui.clipboard.FocusedReaderRegistry 通訊協定。
  3. 使用可管理景觀檢視畫面的簡單元件,示範與新通訊協定的整合。
  4. 將新通訊協定的支援功能整合至 Chromium 和 Flutter 執行器。

效能

新增新服務將會使用二進位檔的額外儲存空間,以及二進位檔和剪貼簿內容的記憶體。每個註冊剪貼簿存取權的用戶端都會保持開啟的 Zircon 管道來使用資源。

安全性考量

必須接受安全性審查

跨元件通訊

剪貼簿服務引入了新的跨元件通訊管道。這為元件開啟新的可能性,意味著元件有意或無意間惡意利用彼此的安全漏洞。

不受信任的內容

剪貼簿服務不保證 ClipboardItem 資料或 MIME 類型提示的可信度。因此,客戶「不應」信任他們收到的內容,且「應」驗證資料是否適合其用途。

特別是,用戶端「應」在權限較低的「沙箱」程序內,使用比 C/C++ 更安全的程式設計語言執行任何剖析、解譯或轉換複雜格式 (詳情請參閱2 規則)。

如果是 ClipboardItemData.text 變體,系統會自動透過每個用戶端和剪貼簿服務中使用的 FIDL 程式庫執行 UTF-8 驗證。

不過,即使使用有效的純 UTF-8 文字,一個元件透過剪貼簿傳送任意文字的功能仍有可能會開啟大門,進而防範各種攻擊向量,包括:

  • 文字小工具發生溢位錯誤
  • 文字轉譯堆疊發生錯誤
  • 同形攻擊 (誘騙使用者以看起來與不同字元相近的字符誘騙使用者,例如以保險手段將使用者帶往網路釣魚網域)
  • 特定文字剖析錯誤
  • 將非預期的程式碼貼到指令提示中

若是會處理或顯示任何第三方或使用者提供內容的應用程式,這些問題大多已成為疑慮,但剪貼簿卻帶來其他挑戰。惡意應用程式可以藉由回應有效的使用者複製指令,將非預期的資料 (未明顯選取) 放入剪貼簿,誘騙使用者貼上混淆代理

未經授權的存取行為

上文所述,元件可能會在未獲授權或不知情的情況下,嘗試讀取或寫入剪貼簿。

因應這個狀況:

  • 可用於授予或拒絕精細複製及貼上功能的通訊協定
  • fuchsia.ui.clipboard.Focused* 通訊協定中,要求剪貼簿存取權只能授予有輸入焦點的前景檢視畫面

在剪貼簿 API 的「未來擴充」中,我們可能會提供觀察剪貼簿 API 事件的通訊協定,供系統殼層在存取剪貼簿時,顯示視覺通知。

日後,隨著新增不受信任的元件和新的剪貼簿使用案例,我們必須重新考慮未經授權的讀取嘗試是否應直接傳回空白的剪貼簿項目 (而非 ClipboardError.UNAUTHORIZED),以減少剪貼簿存取揭露的資訊。

ViewRef 和焦點驗證

剪貼簿服務依賴景觀的聚焦鏈系統,以判斷目前聚焦的檢視區塊,因此有權存取剪貼簿。因此,剪貼簿對剪貼簿聚焦的判定方式只限於 View 的輸入焦點的判定結果,這有幾個缺口:

  • 您可以輕鬆複製構成聚焦鏈結的 ViewRef,從某個元件複製到另一個元件。透過這個機制,惡意元件可能會為了輸入焦點而合作冒用彼此。(儘管如此,ViewRef 的原始擁有者至少要信任已複製 ViewRef 的收件者)。
  • 焦點變更須遵守競爭狀況

安全性審查結果

  • 這個 MVP API 設有限制,限制攻擊途徑。
  • 我們依賴基礎 FIDL 反序列化程序,在剪貼簿服務接收到 UTF-8 字串時正確驗證字串。這是一種攻擊途徑,但我們認為這項服務安全無虞,因為服務的字串反序列化作業依賴 Rust 的 std::str::String 實作,這在 Fuchsia 和更廣泛的 Rust 生態系統中都經過大量記憶體不足的測試。
  • 我們認為 ViewRef 解決方案是追蹤焦點和使用者意圖的良好基礎,但有上述注意事項

隱私權注意事項

必須接受隱私權審查。

未經授權貼上

如果未經授權,擅自存取剪貼簿的內容,可能會有隱私權風險。這樣一來,惡意元件就會趁機取得使用者在鍵盤上儲存的任何私人資料。如所述,fuchsia.ui.clipboard.Focused* 通訊協定要求檢視畫面至少具有輸入焦點 (並因此會顯示在前景中),以降低這個風險。不過,這也表示應用程式可以在取得焦點後立即擷取剪貼簿內容,即使這不是使用者的意圖也一樣。在「日後」中,系統殼層通知會在元件讀取剪貼簿內容時提醒使用者。

側邊管道攻擊事件

在日後的剪貼簿服務疊代中,新增任意資料類型和長度後,記憶體分析可能會揭露相關資訊 (例如剪貼簿緩衝區的大小) 可能提示其內容的資訊。

剪貼簿內容持續性

這個階段僅支援複製短字串,因此剪貼簿內容會儲存在記憶體中,而非磁碟上。

系統不會透過檢查來記錄或公開剪貼簿內容。

跨安全性環境存取

Fuchsia 產品可能會在不同的安全性環境中執行 UI 元素,例如代表不同的使用者,或預先驗證情境中運作。Fuchsia 必須防止在安全性情境邊界之間共用剪貼簿內容。例如,如果使用者複製了密碼,然後在之後鎖定螢幕,則無法將密碼貼到鎖定畫面對話方塊。

只要在每個安全性情境中執行剪貼簿服務的個別執行個體,即可達成這項區隔。

測試

這項功能將使用單元和整合測試進行測試:

  • 剪貼簿服務中的單元測試
  • 整體剪貼簿服務整合測試
  • 針對剪貼簿服務、Fit、輸入管道及 Flutter 或 Chromium 執行器之間的互動進行整合測試。

說明文件

fuchsia.ui.clipboard API 將以 fidldoc 記錄。

將透過適當加註、可管理景觀檢視畫面的簡單元件說明這項通訊協定的使用 (請參閱實作)。

缺點、替代方案和未知

目前還沒有可行的替代方法,是在使用者端的作業系統上提供全系統的剪貼簿服務。執行者無法單憑這項功能提供,因為他們無法在不同的執行階段之間複製及貼上資料。

在描述的設計選項中,替代方法可能是使用限制更嚴格的「殼層媒體、依賴焦點」剪貼簿通訊協定,或是完全無限制的通訊協定 (請參閱存取層級)。

雖然殼層中介方法的安全性較高,但是對於許多用途而言,上述做法可能會過於嚴格。舉例來說

  • 防止應用程式在內容選單中提供複製及貼上指令
  • 幹擾 Chromium 型執行器中的網路剪貼簿 API 功能

雖然這種方式沒有限制,但對於某些小眾應用程式而言很有用,但可能無法預設提供大量隱私風險。

我們先根據輸入焦點,從中間地的存取層級著手,以便:

  • 一開始就優先處理剪貼簿服務中的 部分安全性和隱私權保證
  • 在執行者的 Fuchsia 剪貼簿整合時,提倡最低權限原則
  • 避免對現有執行器實際整合時太過寬鬆的限制

未來的工作

  • 提供觀察剪貼簿 API 事件 (讀取和寫入) 的通訊協定,供系統殼層在存取剪貼簿時用來顯示視覺通知。

  • 展開系統支援的資料格式和酬載大小組合,特別是透過傳送用戶端擁有的 VMO 來使用。

先前的圖片和參考資料

紫紅色

Fuchsia 先前是以模組架構代理程式的形式實作最小的剪貼簿 API,可允許具有 fuchsia.modular.Clipboard 能力的任何元件儲存或擷取 UTF-8 字串。(這項功能已於 2019 年 11 月清除)。

Linux:X11

X11 提供多個稱為「選取」的內容儲存區域,最常見的區域為 CLIPBOARDPRIMARY (隱式文字選取剪貼簿)。

傳送應用程式會向 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() 的呼叫。我們建議開發人員盡可能以多種格式設定剪貼簿資料。

接收應用程式也會擷取視窗的全域剪貼簿的控制代碼,檢查可用格式清單 (包括傳送應用程式明確放置的格式,以及 OS 自動轉換提供的格式)、呼叫 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

傳送應用程式會使用支援的 MIME 類型清單建立 ClipData 物件,並在 ClipData 中填入一或多個項目,可以是字串、指向任何資料的內容 URI 或意圖 (適用於應用程式捷徑)。接著,傳送應用程式會取得全域 ClipboardManager 物件的參照,並將 ClipData 物件傳遞至 setPrimaryClip()

如果複製內容 URI,傳送應用程式必須匯出 ContentProvider,以便為該 URI 提供資料。

接收應用程式會取得全域 ClipboardManager 的參照,檢查其是否有主要剪輯,並檢查其是否支援任何 ClipData.Item 的資料類型。如果貼上純字串,接收的應用程式只需呼叫 getText() 即可。若是從內容 URI 貼上,接收端應用程式必須建立 ContentResolver 執行個體,使用該 URI 執行 query(),然後從傳回的 Cursor 擷取資料。

自 Android 12 起,當一個應用程式存取其他應用程式傳送的 ClipData 時,OS 會顯示浮動式訊息。

詳情請參閱 https://developer.android.com/guide/topics/text/copy-paste。

MacOS

整個系統的「貼上板」可透過 NSPasteboard.general 欄位存取。

傳送應用程式會傳遞實作 NSPasteboardWriting 通訊協定的物件陣列 writeObjects() 方法,藉此複製項目。實作程式包含字串和其他常見資料類型,以及 NSPasteboardItem (用於提供自訂資料類型的包裝函式)。NSPasteboardWriting 提供支援的統一類型 ID (UTI、Apple 相當於 MIME 類型) 的清單,以及資料是否立即可用或「承諾」。相對地,NSPasteboardItem 可以直接包裝資料或資料供應商。

在接收端,應用程式可查詢一般 NSPasteboard,以取得其可讀取的類型,包括可由篩選器服務自動轉換的類型。然後選擇要讀取您貼上板中儲存的全部或部分項目。

詳情請參閱 ​​https://developer.apple.com/documentation/appkit/nspasteboard

iOS

iOS 剪貼簿 API 與 MacOS 類似。系統整個貼上板可透過 UIPasteboard.general 存取。

傳送時,您可以透過各種方法,在貼上板中加入一或多個項目,以 UTI 類型加上標籤。您也可以插入 NSItemProviders,以延遲提供值。為方便起見,一些標準資料類型在 UIPasteboard 例項上都會提供自己可讀取/可寫入的陣列屬性:stringsimagesurlscolors,以及這些資料類型的單數版本,以便只存取每種類型的第一個項目

在接收端,可以依索引或類型擷取任何選取的項目。

自 iOS 14 起,如果擷取其他應用程式放在其中的貼上板內容,就會觸發系統通知。為了減少實際貼上錯誤的通知,iOS 可讓用戶端在不存取資料的情況下,查詢貼上板上是否存在特定資料類型 (hasStringshasImages)。

詳情請參閱 https://developer.apple.com/documentation/uikit/uipasteboard。

網路 API

雖然網頁上的剪貼簿互動主要由網路瀏覽器自行處理 (取決於個別作業系統的特性),但您也可以使用 JavaScript API,讓網頁與剪貼簿互動,而不需要執行直接使用者指令。

舊版 ClipboardEvent API 允許指令碼在 DOM Element 上監聽 "cut""copy""paste" 事件,然後存取事件的 clipboardData 欄位,該欄位可允許按 MIME 類型呼叫 setDatagetData。您亦可透過程式輔助方式,在目前聚焦的元素上叫用 "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。