| RFC-0080:偵測 VMO 不變性 | |
|---|---|
| 狀態 | 已接受 |
| 區域 |
|
| 說明 | 這項 RFC 提議使用新的 `ZX_INFO_VMO_IMMUTABLE` 旗標,判斷 VMO 是否不可變動,以及是否自建立以來就不可變動。 |
| 作者 | |
| 審查人員 | |
| 提交日期 (年-月-日) | 2021-02-18 |
| 審查日期 (年-月-日) | 2021-03-29 |
摘要
本 RFC 建議使用新的 ZX_INFO_VMO_IMMUTABLE 旗標,判斷 VMO 是否不可變動,以及是否自建立以來就不可變動。
提振精神
新增檢查不變性的機制,主要有三項動機。
提升共用記憶體的使用安全
使用共用記憶體時,很難編寫安全無虞的正確程式。有些程式不需要完全可變動的 VMO,可能只會寫入 VMO 一次,然後只從中讀取資料。
如果可以測試 VMO 的不可變動性等限制,就能更輕鬆地縮小安全範圍並驗證正確性。
FIDL 中的大型訊息支援
我們明確選擇將管道訊息限制為 64k,並讓需要較大尺寸的應用程式負責尋找解決方法。
日後,FIDL 可能會支援大型訊息。其中一個可能的處理方式,是將訊息裝箱,做為行外 VMO。如果 FIDL 定義包含 Box<Type>,FIDL 會自動將訊息的該部分複製到 VMO。
另一種處理方式可能是讓所有超過 64k 的 FIDL 訊息自動以 VMO 傳送。這是已撤銷的 RFC 所採用的方法。
在這兩種情況下,郵件收件者都無法驗證郵件在閱讀時不會遭到變造。如果可以確認 VMO 不會遭到修改,就能更輕鬆地驗證 VMO 是否安全無虞,甚至可能發現最佳化機會。
Overnet
Overnet 是管道層級的 Proxy,可透過網路傳輸訊息。

Overnet 是通透式 Proxy,Overnet 用戶端傳送管道訊息的方式,與傳送至本機程序的方式相同。然後由 Overnet 序列化訊息,透過網路傳送,並在另一端重建。接收程序會以處理其他管道訊息的方式接收訊息。
使用 Overnet 的應用程式會看到以下內容:

Overnet 目前不支援傳送含有 VMO 的訊息。對於需要傳送大於現有 64k 管道限制訊息的 Overnet 用戶而言,這一直是個痛點,否則他們會以 VMO 傳送大量資料區塊 (包裝在 fuchsia.mem.Buffer 或 fuchsia.mem.Data FIDL 型別中)。
在單一機器中,無論 VMO 是否可變動,都會透過參照傳遞。也就是說,在管道中傳送 VMO 不會導致 Zircon 隱含複製該 VMO。這項功能簡單又有效率。 不過,透過網路在多部機器之間傳遞參照會沒有效率,也不切實際。在這些情況下,適合使用傳遞副本。
但問題是,以副本傳遞不一定安全。由於 Overnet 是透明 Proxy,遠端程序會預期對相同 VMO 的參照會保持同步。以副本傳遞會變更 VMO 的語意,並可能導致應用程式發生細微的錯誤。舉例來說,假設媒體播放器程序將 VMO 傳送至遠端音箱程序,然後將歌曲寫入 VMO,音箱永遠不會收到歌曲,也無法播放,因為歌曲在 VMO 首次傳送時並不存在。
幸好,在某些情況下,以副本傳遞是安全的。其中一個例子是抵達 Overnet Proxy 的 VMO 不可變更。應用程式已放棄變更資料的能力,因此可確保任何資料副本都會保持一致。
如果 Overnet 能夠偵測到不可變動的 VMO,遇到可變動的 VMO 時,就能快速失敗。這樣做可以避免細微錯誤,以免應用程式無聲無息地中斷。
設計
在 zx_info_vmo_t 的 flags 欄位中新增 ZX_INFO_VMO_IMMUTABLE 標記,指出 VMO 是否不可變更,且可保證及驗證。
目前,如果 VMO 是使用 zx_vmo_create_child 系統呼叫建立,且設定了 ZX_VMO_CHILD_SNAPSHOT 和 ZX_VMO_CHILD_NO_WRITE 旗標,並取消設定 ZX_VMO_RESIZABLE 和 ZX_VMO_DISCARDABLE 旗標,則 VMO 不可變更。不過,如果日後推出新的不可變更 VMO 建立方式,判斷 VMO 是否不可變更的公式可能會有所變更。
應用程式可自行決定何時將 VMO 設為不可變更。
實作
實作方式相對簡單。計算 ZX_INFO_VMO_IMMUTABLE 值所需的大部分狀態都已儲存在核心中。
例外狀況是指出 VMO 在建立時不可寫入的位元。這類資訊會儲存在 VmObjectDispatcher。
效能
加入這個標記不會直接影響成效。
不過,建立不可變動的 VMO 目前需要支付相當可觀的費用,因此只應在嚴格必要時使用。如果是 Overnet,不可變動的複製作業相對負擔可能較小,因為 Overnet 傳送訊息時已執行大量工作,包括網路通訊。
我們執行了兩項基準測試並進行比較,進一步瞭解不可變動的複製作業成本 (CL):
- 建立 VMO、寫入、讀取、關閉
- 建立 VMO、寫入、不可變更的副本、讀取複製的 VMO、關閉
請務必強調,這項基準反映的是目前的效能,但仍有最佳化複製項的空間,特別是不可變動的複製項。
這項基準是在 Intel NUC NUC7i5DNHE 上記錄。

執行不可變動的複製作業時,最多 64 KiB 的費用為 2 微秒,超過此大小就會開始增加。超過 2 MiB 後,費用會大幅增加。
不過請注意,相較於整體建立-寫入-複製-讀取-銷毀流程,複製的相對成本會隨著大小增加而降低:

如果是 64k VMO,不可變動的副本會增加 20% 的時間,這是一筆相當可觀的負擔,但隨著大小增加,負擔會逐漸減少至零。
安全性考量
請務必驗證透過這項技術聲稱不可變更的 VMO 確實不可變更。如果 VMO 遭到誤認為不可變動,就會造成安全漏洞。
隱私權注意事項
沒有影響。
測試
系統會透過單元測試驗證標記實作項目。
說明文件
目前說明文件缺少 ZX_INFO_VMO_* 標記,讀者會參照存放區。為配合這項異動,我們將撰寫說明文件,介紹 ZX_INFO_VMO_* 選項。
缺點、替代方案和未知事項
決定不可變動性的不同屬性有多個標記
這項 RFC 建議使用單一 ZX_INFO_VMO_IMMUTABLE 標記來判斷不變性。使用者端可以改用幾個不同的旗標來判斷這項資訊:
ZX_INFO_VMO_IS_CHILD_SNAPSHOTZX_INFO_VMO_IS_INITIALLY_WRITABLEZX_INFO_VMO_IS_RESIZABLEZX_INFO_VMO_IS_DISCARDABLE
根據基礎狀態設定多個旗標的好處是,不需要專門的不可變動旗標,且只會公開可搭配不同組合使用的基本屬性。
不過,透過這些標記判斷不變性相對複雜,而且不變性本身可視為基本屬性。有了專用的不變性標記,就能更輕鬆避免安全錯誤。此外,單一標記可讓您更輕鬆地導入建立不可變動 VMO 的新方法。
檢查是否還有最後一個參照
您可能會想使用 ZX_INFO_HANDLE_COUNT 檢查程序是否持有 VMO 的最後一個控制代碼,並假設如果持有,其他程序就無法突變 VMO。不過,這項假設並不正確,因為控制代碼只是 Zircon 中的一種參照。舉例來說,VMO 的記憶體對應不會計入 ZX_INFO_HANDLE_COUNT 值,因此程序無法使用這個值判斷是否具有 VMO 的專屬存取權。
透過 FIDL 將安全資訊傳達給 Overnet
FIDL 語言可能會有所變更,因此可以標示 VMO 為不可變更或「可透過 Overnet 傳輸」。如果 Overnet 能夠判斷特定 VMO 是否具備這項屬性,就能決定是否要傳送或拒絕 VMO。
您可以透過下列方式將這項資訊傳達給 Overnet:
- 並將其插入把手上未使用的鑽頭
- 新增機制,將位元儲存在 Overnet 可讀取的 VMO 上
- 在 FIDL 訊息標頭中保留 64 位元,將 VMO 控制代碼標示為不可變動
- 讓 Overnet 透過通訊協定與用戶端通訊
可惜的是,這些方法都有缺點,不僅複雜,也不如建議的解決方案。
既有技術和參考資料
VMO 密封 已廢棄的 RFC 提議了一種機制,可限制對 VMO 執行的動作。這項功能可用於建立及測試不可變動的 VMO,而不必進行複製。不過,與簡單的不變性檢查相比,VMO 密封對 VMO 實作的侵入性高出許多。
最近,我們為 zx_vmar_map 推出安全功能,例如 ZX_VM_ALLOW_FAULTS。日後可能需要將處理不可變動性的功能新增至 zx_vmar_map。