| RFC-0034:以空值終止字串 | |
|---|---|
| 狀態 | 已遭拒 |
| 區域 |
|
| 說明 | 我們建議 FIDL 中的字串以空值結尾 (同時保留字串大小,如同目前的情況)。此外,請將 C 繫結變更為使用 uint8_t* 做為字串資料 |
| 作者 | |
| 提交日期 (年-月-日) | 2019-02-08 |
| 審查日期 (年-月-日) | 2019-02-21 |
拒絕原因
摘要
我們建議:
FIDL 中的字串會以空值結尾 (同時保留字串大小,與現行做法一致);
將 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:N、string: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(更新EmitLinerarizeMessage、ProduceInterfaceClientImplementation等)//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 中的任何字串存取權,都必須從解碼的訊息緩衝區複製字串。
先前技術和參考資料
DBus、Capt'n Proto 和 CORBA1 會傳送長度並以空值終止,但 protobuf 不會以空值終止。