RFC-0137:捨棄 FIDL 中的未知資料

RFC-0137:捨棄 FIDL 中的不明資料
狀態已接受
區域
  • FIDL
說明

讓 FIDL 繫結捨棄不明資料,而非保留及代理這類資料。

問題
Gerrit 變更
作者
審查人員
提交日期 (年-月-日)2021-08-25
審查日期 (年-月-日)2021-10-13

摘要

大多數 FIDL 繫結會保留不明的資料表欄位和聯集變化版本,讓使用者程式碼檢查並重新編碼原始位元組和句柄。這種行為會造成安全性和隱私權風險,大幅增加 FIDL 的複雜度,導致線路格式遷移作業困難,且無法在所有繫結中實作。我們建議讓繫結捨棄未知的資料,以產生 表 1 所示的行為。

表 1:針對含有不明資料的彈性類型所做的變更

類型 是否可以存取未知項目? 可以重新編碼嗎? 不明 Proxy
位元
列舉
桌子 是 → 是 →
聯集 是 → 僅限序數 是 → 是 →

背景

彈性型別是 FIDL 中用於編寫可進化的 API 的重要功能。這些屬性是在 RFC-0033:處理不明欄位和嚴格性中引進,自 2020 年底起,所有繫結皆可使用這些屬性。使用彈性類型時,即使有不明的會員,解碼作業仍會成功。FIDL 表格一律是彈性的,但位元、列舉和聯集可標示為嚴格或彈性。使用彈性位元和列舉時,未知值就是整數。不過,如果是未知的資料表欄位或聯集變化版本,則值會由原始位元組和句柄組成,我們稱之為「未知資料」

目前大多數的繫結會在網域物件保留不明資料。例外狀況是 LLCPP,因為其設計限制導致難以支援。對於其他情況,保留未知資料可啟用下列行為。假設程序 A、B 和 C 透過 FIDL 通訊。如果 A 和 C 知道新表格欄位,但 B 不知道,且該欄位是從 A 傳送給 B 再傳送給 C,那麼 C 會收到並瞭解該欄位,而 B 則不會。換句話說,B 會代理未知的資料。應用程式也可以根據結構定義的假設,解讀未知資料,例如假設前四個位元組一律是 ID。不過,這類情況是人為設計的,因此建議您直接使用 FIDL 類型建立更佳的模型。代理程式是保留不明資料的唯一實際用途。

提振精神

在設計 FIDL 時,我們會盡量使用最少的功能解決實際問題。保留未知資料的功能並未遵循這些原則。自實作以來,Fuchsia 幾乎沒有使用到這項功能,而且在其他 FIDL 工作中,這項功能一再成為複雜因素

這讓我們質疑代理未知資料的優點。這是否是一個好的想法?我們認為並非如此,至少不是預設行為。對於真正用於 Proxy 的 FIDL 伺服器,這項功能可能會很實用,可做為選擇加入功能。不過,這些情況會更適合使用 FIDL 中的專屬 Proxy 支援功能,因為這項功能可解決問題的所有層面,而不僅是不明資料。

即使我們假設預設的 Proxy 是可行的,Proxy 也只能在直接重新編碼 FIDL 網域物件時才會運作。不過,在進一步處理前,通常會 (也建議) 將這些物件轉換為更豐富的應用程式專屬類型。這項做法與 Proxy 處理方式相反,因為 Proxy 處理方式是盡可能不經過處理,直接傳遞已編碼的訊息,或直接重新編碼已解碼的訊息。舉例來說,Rust crate fidl_table_validation 會在將 Fidl 網域物件轉換為應用程式網域物件時提供驗證功能。因此,如果有參與者使用這種模式,在複雜系統中透過多個中繼傳送資料表的對等端就無法依賴所有欄位都到達最終目的地。

無論您是否需要使用 Proxy,保留不明資料都會帶來幾項缺點。這會讓線路格式遷移作業更加困難。在遷移期間,所有對等端都會讀取舊版和新版格式,因此可以安全地開始寫入新格式。由於這項變更無法同時在所有地方發生,因此對等端可能會同時收到舊格式和新格式的訊息。假設它收到了兩個表格,其中一個表格包含不明的資料表欄位,然後嘗試在單一訊息中對兩個表格進行編碼。在這種情況下,要保留不明資料的唯一方法,就是在每個信封中加入電報格式中繼資料,但這會增加不可接受的複雜性和額外負擔。

另一個缺點是,FIDL 繫結之間的功能相容性。在支援原地解碼的繫結 (例如 LLCPP) 中,很難選擇可同時擁有句柄和表示未知句柄的網域物件表示法。對於已知資料,解碼器會覆寫其存在指標,在網域物件中插入句柄。對於不明資料,解碼器只知道在句柄表中要略過的句柄數量,而非其存在指標的位置。因此,您必須同時傳回網域物件和重新封裝的句柄表,才能傳回擁有網域物件。相反地,這些繫結只是不支援保留不明資料。這可能會讓在其他繫結中依賴此功能的使用者感到意外,並增加我們的測試負擔,因為所有涉及不明資料的案例都需要進行兩次 GIDL 測試。

一般來說,需要保留不明資料會大幅增加 Fidl 的複雜度。這項複雜性不僅限於實作,還會因為與其他功能的互動而影響使用者。舉例來說,值和資源類型之間的差異只會影響 API 相容性,不會影響 ABI。不過,後來發現,如果針對彈性值類型接收不明句柄,就會產生不可避免的 ABI 影響。這個特殊情況只會出現在需要在網域物件中保留不明資料的情況下。

相關人員

誰會受到這項 RFC 是否通過的影響?(此為選用部分,但建議您填寫)。

主持人:pascallouis@google.com

審查者:abarth@google.com、yifeit@google.com、ianloic@google.com

諮詢對象:bryanhenry@google.com

Socialization:我們已將本 RFC 的草稿傳送給 FIDL 團隊,請他們提供意見。

設計

對於彈性位元和列舉的不明值,處理方式仍維持不變。

解碼資料表和彈性聯集時:

  • 繫結不得在網域物件中儲存不明的位元組和句柄,除非繫結是專門用於 Proxy。

  • 繫結項目必須關閉所有未知的句柄。

重新編碼先前解碼的資料表和彈性聯集時:

  • 繫結項目必須成功重新編碼資料表的已知欄位,且不得包含不明欄位 (這表示會儲存這些欄位)。

  • 當您使用未知的變化版本對彈性聯集進行編碼時,繫結務必失敗並傳回錯誤。

關於表格和彈性聯集的網域物件:

  • 繫結應避免提供任何機制,以便區分沒有不明欄位的資料表,以及已捨棄不明欄位的資料表。如果繫結提供深層相等函式,則應視為相等。

  • 繫結務必提供機制,以便判斷彈性聯集是否有不明的變數,並應提供對不明序數的存取權 (也就是說,網域物件的不明變數應只儲存序數)。如果繫結提供深層相等函式,則未知的變化版本應會像 NaN 一樣運作,即使序數相同,也會比較不相等。

在 Rust 中,後者表示從彈性聯集和透過傳遞包含一個的類型中移除 Eq 特徵,這點已在浮點值中實現。

實作

實作方式主要是刪除負責在所有繫結中保留未知項目的程式碼。我們認為,未知資料存取器並未用於任何實際用途。如果有,我們必須瞭解用途,並嘗試找出解決方法。

目前,LLCPP 無法重新編碼含有不明欄位的資料表。這項設定必須依設計變更,才能成功編碼已知欄位。

安全性考量

這項提案可提升安全性,因為會減少隱含傳遞的資訊和功能。當未知資料保留時,很容易透過不明就裡的元件傳遞任意位元組和句柄。當其遭到捨棄時,資料邊界會由 FIDL 結構定義正確編碼,讓系統更容易進行稽核。

隱私權注意事項

這項提案可限制傳輸不明資料 (可能包含機密資訊),因此有助於提升隱私權。

測試

測試大多是在 GIDL 中進行。涉及不明資料的 success 測試會分成兩個部分:decode_successencode_success (只對已知的資料表欄位進行編碼) 或 encode_failure (無法對聯集進行編碼)。含有不明資料的值的表示方式也會改變。GIDL 應不再剖析不明的位元組和句柄,而是使用語法 123: unknown 來表示序數 123 的不明信封。

您可以移除將 LLCPP 和非 LLCPP 區隔開來的許可清單和拒絕清單。所有繫結都會針對不明資料採用相同的編碼/解碼行為。此外,在 fxrev.dev/428410 中新增的 LLCPP 專屬單元測試可以移除,改用 GIDL 測試。

應保留測試,以便執行嚴格/彈性和值/資源的所有組合,但使用彈性值類型的句柄解碼未知資料時,系統將不再失敗。

說明文件

需要更新下列文件:

缺點、替代方案和未知事項

替代做法:選用保留未知值

我們並非完全移除保留不明資料的支援功能,而是可以繼續支援,只是不會預設為支援。舉例來說,您可以選擇在類型上使用屬性,或許可以限制為值類型,以減輕對不明句柄代理的疑慮。不過,這種做法會讓較少使用的功能變得更加複雜,而且無法解決線路格式遷移問題。

缺點:彈性類型不一致

這個提案的缺點是,它會讓彈性型別的行為變得不一致,也許也會讓使用者不易理解。為說明這項概念,我們沿著兩個軸線將位元、列舉、表格和聯集分類,如表 2所示:代數型別 (產品或加總) 和酬載 (是否含酬載)。

表 2:彈性類型的分類

產品類型 加總類型
不含酬載 位元 列舉
附酬載 桌子 聯集

目前,所有彈性型別都會代理未知資訊。這項提案會打破沿著兩個軸線的對稱性。舉例來說,請考慮下列 FIDL 類型:

// Product types (multiple fields set)
type Bits  = bits  {    A = 1;         B = 2; };
type Table = table { 1: a struct{}; 2: b struct{}; };

// Sum types (one variant selected)
type Enum  = enum  {    A = 1;         B = 2; };
type Union = union { 1: a struct{}; 2: b struct{}; };

首先,我們會失去酬載軸的一致性。目前,從 Bits 轉換至 Table 或從 Enum 轉換至 Union 可增加功能,允許每個成員攜帶酬載。在這個提案中,這項功能會導致無法保留未知項目。

其次,在代數型軸上,我們失去了一致性。目前,TableUnion 都允許在解碼含有不明資料的物件後重新編碼。在這個提案中,Table 可以重新編碼,但 Union 則不行。

我們認為,為了避免「動機」一節所述的複雜性,這種以實用性取代一致性的權衡是值得的。不過,以下提供其他設計,可讓您保有更一致的風格。

替代做法:捨棄所有不明資訊

為提升一致性,我們可以捨棄所有不明資訊,包括容易儲存的不明整數。也就是說,您可以為位元和列舉使用單一不明狀態,並捨棄不明序數,以及陣列的酬載。表格 3 顯示產生的行為。

表 3:表 1 的調整:捨棄所有不明資訊

類型 是否可以存取未知項目? 可以重新編碼嗎? 不明 Proxy
位元 是 → 是 →
列舉 是 → 是 → 是 →
桌子 是 → 是 →
聯集 是 → 是 → 是 →

替代做法:選用的彈性聯結

為了提高一致性,我們可以要求彈性聯集一律為選用,然後將不明變數解碼為缺少的聯集。這樣一來,您就能重新編碼聯集,讓其與資料表保持一致。表 4 顯示產生的行為。

表 4:表 1 的調整:選用的彈性聯結

類型 是否可以存取未知項目? 可以重新編碼嗎? 不明 Proxy
位元
列舉
桌子 是 → 是 →
聯集 是 → 是 →

替代方法:記住是否捨棄不明欄位

在建議的設計中,繫結使用者無法判斷在解碼資料表時是否捨棄不明欄位。另一種做法是在資料表網域物件中儲存布林值或一組不明的序號。使用者可以透過 has_unknown_fields() 等函式查詢此資訊。舉例來說,儲存空間服務可能會在這種情況下失敗,以免資料遺失。

這個替代方案的缺點是,它會為表格網域物件新增額外的隱藏狀態。這些不再是簡單的值類型,而是欄位的加總值。舉例來說,這會引發 == 運算子是否應考量此類布林值標記的問題。

替代做法:重要欄位

前文所述,檢查 had_unknown_fields() 的唯一實際用途,是當其傳回值為 true 時失敗。我們可以接受表格和彈性聯集成員上的屬性,而不是在繫結中提供該存取子,以便選擇採用該行為:

type Data = table {
  1: foo string;
  @important
  2: bar string;
};

這個屬性的作用是在該欄位的封包標頭中,設定新保留位元。當解碼器遇到重要位元集的不明欄位時,就會失敗。換句話說,@important 屬性會選擇不採用前向相容性,並以動態版本的形式運作,類似於我們允許在位元、列舉和聯集上使用的靜態 strict 修飾符。

這個替代方案可能需要在這個 RFC 之上,另行提出專屬的 RFC。

致謝:這個想法是由 yifeit@google.com 提出。

替代做法:保留值/資源的 ABI 影響

這項提案可消除 RFC-0057 的 ABI 影響,並將此視為捨棄不明資料所帶來的改善。不過,ABI 影響是可接受的,因此應保留這項設定。

降低 ABI 影響的優點 (此提案):

  • 這可讓嚴格/彈性和值/資源更具獨立性。兩者交集處不再有特殊情況。由於我們在撰寫各自的 RFC 後才發現這個問題,因此使用者可能也會感到意外。
  • 這麼做可只中斷 API,而不會中斷 ABI,因此更容易將型別從值轉換為資源。如果沒有程式碼中斷 (適用於無法在某些繫結中直接參照的要求和回應類型),此轉換就不會再悄悄變更行為。

保留 ABI 影響的優點 (這個替代方案):

  • 更準確地模擬介面的意圖。如果您表示不預期處理常式 (使用值類型),但在執行階段收到處理常式,這表示有差距,因此失敗是適當的。
  • 如果我們改變心意,可以稍後移除 ABI 影響。反方向切換則較有可能造成中斷。

既有技術與參考資料

Protobuf

通訊協定緩衝區的設計在這一點上有過來回的討論。在 proto2 中,系統會保留未知欄位並建立 Proxy (就像目前的 FIDL)。在 proto3 中,這項行為已變更為在解碼期間捨棄未知欄位 (如這項提案)。不過,這個決定後來又被撤銷,因此在 3.5 以上版本中,proto3 又會保留不明欄位。

這引發了一個問題:如果我們接受這項提案,FIDL 會遵循相同的路徑嗎?我們認為答案是否定的,因為 FIDL 和 Protobuf 占用不同的設計空間。由於有兩種用途 (中介伺服器和讀取-修改-寫入模式),Protobuf 必須恢復舊的保存行為。這兩種情況在 Fuchsia 中都很少見。相較於使用中介 Proxy 伺服器,Fuchsia 的安全性和隱私權原則鼓勵直接通訊。FIDL API 評分標準建議使用部分更新模式,而非讀取-修改-寫入模式。

節儉

Apache Thrift 會捨棄不明欄位