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

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

讓 FIDL 繫結捨棄不明資料,而非保留及 Proxy 處理。

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

摘要

大部分的 FIDL 繫結會保留未知的資料表欄位和聯集變化版本,讓使用者程式碼能夠檢查原始位元組及控點,並重新進行編碼。這種行為會造成安全性和隱私權風險,並增加 FIDL 的複雜度、讓線上格式遷移作業變得困難,而且無法在所有繫結中實作。因此,建議您改為讓繫結捨棄不明資料,導致表 1 所示的行為。

表 1:具有不明資料的彈性類型變更

類型 可以存取不明資料嗎? 可以重新編碼嗎? 有不明 Proxy 嗎?
位元
列舉
桌子 是 → 是 →
聯集 是 → 僅限 Ordinal 是 → 是 →

背景

彈性類型是 FIDL 中重要的功能,用於編寫可變動的 API。RFC-0033:處理不明欄位和嚴格程度中導入,自 2020 年底起,這些欄位都可在所有繫結中使用。使用彈性類型時,即使有不明成員,解碼作業仍會成功。FIDL 資料表一律具有彈性,而位元、列舉和聯集可以標示為嚴格或彈性。使用彈性位元和列舉時,未知的值只是整數。然而,如果是未知的資料表欄位或聯集變化版本,這個值會包含原始位元組和控點,也就是我們稱為「不明資料」

目前大多數的繫結都會保留網域物件中的不明資料。例外狀況是 LLCPP,因為其設計限制導致難以支援。至於其他情況,保留不明資料即可啟用下列行為。假設程序 A、B 和 C 透過 FIDL 進行通訊,如果 A 和 C 知道新資料表欄位,但 B 沒有,且該欄位從 A 到 B 到 C,則 C 即使 B 略過此欄位,C 仍會收到並瞭解該欄位。也就是 B 將未知的資料 Proxy。應用程式也可以根據結構定義的假設解讀不明資料,例如前四個位元組一律為 ID。不過,這類案例會透過 FIDL 類型直接存在競爭,並改善模擬品質。使用 Proxy 是保留不明資料的唯一實際用途。

提振精神

設計 FIDL 時,我們會盡力使用必要的最少功能解決真實問題。保留未知資料的功能不符合這些原則。從實作以來,它在 Fuchsia 中幾乎未使用或完全未使用,而且在其他 FIDL 工作中反覆出現複雜因素

我們因此會思考為不明資料執行 Proxy 的重要性。從一開始就採用這種做法嗎?我們堅持不採用或至少不是預設行為。針對實際上為 Proxy 的 FIDL 伺服器,這可能會做為選擇啟用功能。不過,在 FIDL 中提供專屬的 Proxy 支援,在這類情況中會更佳,因為這個方式可以解決問題的所有層面,而不只是未知資料。

即使我們假設預設使用 Proxy 是合適的做法,但只有在直接重新編碼 FIDL 網域物件時才會運作。不過,在進一步處理之前,將這些物件轉換為更豐富的應用程式特定類型是很常見的 (建議採用)。這種做法反而與採取 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

社交內容:這份 RFC 的草稿已傳送給 FIDL 團隊審查。

設計

彈性位元和列舉的未知值處理方式則維持不變。

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

  • 繫結「不得」儲存不明的位元組和處理的網域物件,除非繫結是專為 Proxy 設計。

  • 繫結「必須」關閉所有不明的控點。

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

  • 繫結「必須」成功將資料表的已知欄位重新編碼,且「不得」包含不明欄位 (因為這類欄位會導致這些欄位隱藏這些欄位)。

  • 使用不明變化版本編碼彈性聯集時,繫結「必須」失敗並傳回錯誤。

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

  • 繫結「不應」提供任何機制來區分沒有未知欄位的資料表,與含有不明欄位的資料表。如果繫結提供深度相等函式,應視為相等。

  • 繫結「必須」提供判斷彈性聯集有不明變化版本的機制,並「應」提供不明序數的存取權 (即網域物件的未知變化版本僅應儲存序數)。如果繫結提供深度相等函式,不明的變化版本應與 NaN 一樣,即使序數相同也是如此。

在 Rust 中,第二點暗示了 Eq 特徵從彈性聯集和連帶包含的類型中移除,就像浮點已經一樣。

實作

實作主要是刪除負責保留所有繫結中的未知程式碼。我們認為在實際工作環境中使用了不明資料存取子。如果有,我們必須瞭解用途,並設法解決問題。

目前,LLPP 無法將含有不明欄位的資料表重新編碼。您必須根據設計變更內容,才能只對已知欄位成功編碼。

安全性考量

本提案可提升安全性,因為這麼做可減少以隱含方式傳遞的資訊與功能。保留未知的資料時,即可輕鬆傳遞任意位元組,並透過不相關的元件處理。被捨棄時,資料界線會由 FIDL 結構定義準確編碼,讓系統更容易稽核。

隱私權注意事項

本提案會限制傳輸可能含有機密資訊的不明資料,因此有助於保護隱私權。

測試

測試通常發生在 GIDL 中。涉及不明資料的 success 測試會分成兩個部分:decode_successencode_success (僅對已知資料表欄位進行編碼) 或 encode_failure (聯集無法編碼)。使用不明資料的值表示法也會改變。GIDL 不應再剖析未知位元組和處理,而是使用 123: unknown 語法在 123 序處表示未知的信封。

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

執行嚴格/彈性和值/資源組合的所有測試應保留,但若使用彈性值類型的控點來解碼不明資料,則不會再失敗。

說明文件

下列說明文件需要更新:

缺點、替代方案和未知

替代方法:視需要保留未知

我們不會完全移除對保留未知資料的支援,而是會繼續支援,而不只是預設提供支援。舉例來說,該屬性可透過類型上的屬性選擇啟用,但可能僅限於值類型,以減少 Proxy 不明控制代碼的問題。不過,這個做法會讓少量使用的功能更加複雜,而且無法解決傳輸格式遷移問題。

缺點:彈性類型不一致

本提案的缺點是,這樣彈性類型的行為會變得較不一致,可能較不易直覺。為瞭解釋這個情況,這樣做有助於將位元、列舉、資料表和聯集分類,依表 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{}; };

首先,我們失去酬載軸之間的一致性。目前,從 BitsTable 或從 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。

確認聲明:這項提案是由 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 伺服器。FIDL API 評分量表不是讀取-修改-寫入模式,而是建議使用部分更新模式

擲回

Apache Thrift 捨棄不明欄位