RFC-0034:空值終止字串

RFC-0034:以空字元結尾的字串
狀態已遭拒
區域
  • FIDL
說明

我們建議 FIDL 中的字串以空值結尾 (同時保留字串的大小,就像目前的情況一樣)。此外,請變更 C 繫結,以便使用 uint8_t* 做為字串資料

作者
提交日期 (年-月-日)2019-02-08
審查日期 (年-月-日)2019-02-21

拒絕理由

摘要

我們建議:

  1. FIDL 中的字串會以空值結尾 (同時保留字串的大小,就像目前的情況一樣);

  2. 變更 C 繫結,以便使用 uint8_t* 處理字串資料。

提振精神

目前的 FIDL 字串編碼很容易讓您不小心編寫不安全的程式碼。C 和低階 C++ 繫結會用於 Fuchsia 系統中權限最高的程式碼,因此應特別重視這些層級所暴露的風險。

我們不小心發現這類錯誤,但系統化稽核 Fuchsia 樹狀結構中的程式碼相當困難,而且無法避免這些錯誤在我們的樹狀結構或第三方程式碼中重複發生。

設計

這項提案建議變更 FIDL 字串的編碼,加入單一終結字節,並將值設為零。這不會讓 FIDL 字串與 C 字串相容,但如果 FIDL 字串用於 C 字串,系統會將其解讀為比預期短,而非比預期長,因此較為安全。

線路格式

電匯編碼字串的新定義如下 (變更處已加粗顯示):

  • 代表文字的 UTF-8 編碼字元變長度序列。
  • 可為空值;空字串和空字串不同。
  • 可指定大小上限,例如 string:40 可指定最大 40 個位元組的字串 (不含空字元終止符)。
  • 字串內容含有空值結束符號。
  • 編碼器和解碼器一律必須驗證字串的最後一個位元組後面有空值位元組,如長度所示。
  • 儲存為 16 位元組的記錄,其中包含:
    • size:不含空值終結符號的 64 位元不帶正負號的程式碼單元數量 (位元組)
    • data:64 位元狀態指示或離線字串資料的指標
  • 在轉移時進行編碼時,data 會指出內容是否存在:
    • 0:字串為空值
    • UINTPTR_MAX:字串非空值,資料是下一個離線物件
  • 解碼後供使用者消費時,data 是指向內容的指標:
    • 0:字串為空值
    • <valid pointer>:字串非空值,data 位於指定的記憶體位址

字串的表示方式如下:

  • string:非空值字串 (如果遇到空值資料,就會發生驗證錯誤)
  • string?:可為空值的字串
  • string:Nstring:N?:字串,長度上限為 N 個程式碼單位

這會造成線路格式變更,特別是長度可被 8 整除的字串會增加 8 個位元組 (會對齊 8 個位元組,並以空值結束,導致系統再新增 7 個位元組做為填充,以便再次對齊 8 個位元組)。

編碼器需要更新,才能在經過編碼的字串中加入空值結束符號,並驗證字串內容中沒有空值字元。解碼器需要更新,以便檢查字串內容中是否有空字元,但長度指示處應有空字元終結符。

C 繫結

目前,C 繫結會將字串表示為 char*size_t。如果 char* 傳遞至需要 C 字串的函式,可能會導致錯誤解讀。繫結會變更為使用 uint8_t*,因此將字串資料指標傳遞至 strchr()printf("%s") 將無法編譯。

導入策略

這是破壞性的線路格式變更。在所有 FIDL 用途中,都需要仔細協調其部署作業。

在某些建構時間旗標後方,您需要更新下列程式碼:

  • //zircon/system/ulib/fidl/walker.h (正確驗證字串)
  • //zircon/system/host/fidl/lib/flat_ast.cpp (更新 StringType::Shape)
  • //zircon/system/host/fidl/lib/c_generator.cpp (更新 EmitLinerarizeMessageProduceInterfaceClientImplementation 等)
  • //sdk/lib/fidl/hlcpp/string.cc
  • //garnet/public/lib/fidl/rust/fidl/src/encoding.rs
  • //third_party/go/src/syscall/zx/fidl/encoding.go (更新 marshalStringunmarshalString)
  • //third_party/go/src/syscall/zx/fidl/encoding_new.go (更新 mString)
  • //sdk/dart/fidl/lib/src/types.dart (更新 StringType)
  • llcpp 繫結
  • 其他語言的樹狀結構外繫結

這些項目應使用 fidl_compatibility_test 進行測試,執行測試並確認預期的系統穩定性。

實際上,您需要與發布團隊和 Chromium 等外部團隊協調,才能完成變更。

人體工學

這可讓 C 和低階 C++ 繫結更容易正確使用,且不會影響其他繫結。

說明文件和範例

應更新線路格式說明文件 (如上所述)。

回溯相容性

這項變更與 API 相容,但與 ABI 不相容。

成效

這不會影響建構效能,但會對效能造成以下輕微影響:

  • 每個字串平均需要額外傳送一個位元組

安全性

這項異動特別針對使用 FIDL 的記憶體不安全語言,修正潛在的安全漏洞。

測試

這項測試會使用相容性測試套件進行。應擴充該套件,確保系統能正確處理 7、8 和 9 個位元組長的字串等邊緣情況。

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

這會使 FIDL 訊息的大小平均增加一個字串的位元組。對於長度無法被 8 整除的字串,系統不會進行任何變更。如果字串的長度能以 8 除盡,長度會增加 8 個位元組。

替代方案

什麼也不做

我們可以維持現狀,並依賴程式碼審查和文件,確保使用者不會編寫錯誤的程式碼。採用新的低階 C++ 繫結後,這個問題也會稍微緩解。這個微妙問題所造成的錯誤類型,可能會帶來嚴重的潛在危險,因此我們無法視而不見。

空值結束,但禁止內嵌空值位元組 / 使用修改過的 UTF-8

(這是原始提案)

'\0' 是有效的 UTF-8 字元,並存在於 FIDL 使用者要交換的 UTF-8 字串中。禁止使用空值位元組會導致 FIDL 不適合用於許多用途,而使用經修改的 UTF-8 會增加額外負擔,將可能含有空值位元組的一般 UTF-8 轉換為經修改的 UTF-8。

請改用 uint8_t 而非 char

如果 FIDL 字串資料指標為 uint8_t 而非 char,則在未轉換的情況下,無法將其傳遞至標準字串函式或 printf。這麼做有助於找出這類錯誤,但無法防止錯誤發生。

在偵錯版本中使用有效的 ASCII 填入邊框間距

七個字串中有八個會在結尾加上零填充位元組。在偵錯版本中,我們可以將這些零變更為有效的 ASCII 字元,這樣在列印或剖析時,這些錯誤就會顯示出來。這些內容很容易就會被忽略。

一律將字串移出行

C/C++ 繫結可為字串和空終止符分配空間,並複製及終止所有收到的字串。這會對 C 和低階 C++ 繫結造成不可接受的效能成本。

將字串移出 ASan 建構的程式碼行

位址淨化器會檢查程式碼是否超出堆積配置。對於 ASan 版本,我們可以為堆疊上的字串分配空間、複製其中的位元組,然後將該指標傳回給呼叫端。將其整合至 C 繫結並非易事,可能會導致 ASan 和發布版本之間的行為有重大差異,且無法協助開發人員在 Fuchsia 建構系統之外工作。

停止使用 C 和 C++

如果 Google 無法在 2019 年編寫安全的 C++ 程式碼,那麼 C++ 就不適合使用。很遺憾,許多裝置驅動程式庫作者都會選擇使用 C 和 C++,許多供應商也可能會選擇將現有的 C/C++ 驅動程式移植至我們的平台。

不要將原始字元公開給 C

我們可以向 C 公開不透明結構,並要求 C 中的任何字串存取作業,從解碼的訊息緩衝區中複製字串。

既有技術與參考資料

DBusCapt'n Proto 和 CORBA 1 會傳送長度和空值結束符號,而 protobuf 不會傳送空值結束符號。


  1. CORBA 規格 3.3,第 2 部分,第 9.3.2.7 節。