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 字串使用,系統會將其解讀為比預期短,而非比預期長,這樣比較安全。

Wire Format

電匯編碼字串的新定義如下 (變更部分以醒目方式標示):

  • 代表文字的 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 (更新 marshalString, unmarshalString)
  • //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 建構作業,我們可以在堆積上為字串分配空間、將位元組複製到該空間,然後將指標傳回給呼叫端。這項整合作業並非微不足道,可能會導致 ASan 和發布版本之間出現重大行為差異,而且無法協助在 Fuchsia 建構系統以外工作的開發人員。

停止使用 C 和 C++

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

不要向 C 公開原始字元

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

先前技術和參考資料

DBusCapt'n Proto 和 CORBA1 會傳送長度並以空值終止,但 protobuf 不會以空值終止。