RFC-0145:Eager 套件更新

RFC-0145:急切套件更新
狀態已接受
區域
  • 軟體推送
說明

系統單體外部套件的急就更新。

問題
Gerrit 變更
作者
審查人員
提交日期 (年-月-日)2021-10-15
審查日期 (年-月-日)2021-12-13

摘要

在完整系統更新之外更新套件的機制,並在重新啟動時保留這些套件,同時考量元件架構互動和更新套件的驗證程序。

提振精神

我們需要提供一種機制,讓套件擁有者能夠在裝置上發布軟體更新,而不需要單一全域整合程序。這樣一來,Fuchsia 平台和基礎系統就能與個別套件體驗 (例如網路瀏覽器或支援資料) 分開發布。

我們的目標是支援第一方 (1P) 產品和套件,包括在 Fuchsia 樹狀結構外建構的產品。我們目前沒有支援第三方 (3P) 產品或套件的規定,但這項設計不會阻止我們日後加入支援。

相關人員

協助人員: hjfreyer@google.com

審查者:

  • ampearce@google.com - 安全性
  • computerdruid@google.com - 軟體推送
  • geb@google.com - 元件架構
  • hjfreyer@google.com - FEC、元件平台
  • jsankey@google.com - 結構化設定
  • marvinpaul@google.com - 伺服器基礎架構
  • camrdale@google.com - 鈷藍

諮詢:

列出應審查 RFC 但不需要核准的人員。

  • aaronwood@google.com
  • abarth@google.com
  • bryanhenry@google.com
  • ddorwin@google.com
  • gstai@google.com
  • 軟體推送團隊

社會化:

這份 RFC 已透過多輪內部交流,與審查人員、軟體提交團隊和潛在客戶分享。

RFC 格式定義

本文件中的關鍵字「MUST」、「MUST NOT」、「REQUIRED」、「SHALL」、「SHALL NOT」、「SHOULD」、「SHOULD NOT」、「RECOMMENDED」、「MAY」和「OPTIONAL」應依 IETF RFC 2119 的說明進行解讀。

需求條件

Fuchsia 平台需求

套件更新

套件可獨立於 Fuchsia 系統的其他部分更新。套件可在系統重新啟動時更新,這表示套件更新不能「僅」在系統更新時進行。

元件關係

套件擁有者必須能夠獨立於基礎系統之外,為多套件群組發布合格的版本,並僅針對該群組中的套件和元件推送更新。

套件關係應可修改 (或設為無效),以便只支援單一套件更新。

注意:我們不會在本 RFC 中完整說明這項規定。這項功能對應到後續的工作階段,我們會在後續設計中處理。這項設計不應阻礙我們日後處理這項要求。

A/B 更新:只有在套件中的所有 blob 都已更新時,才使用套件的更新版本

應先將套件完全下載並驗證,再提交更新。

系統版本依附元件

積極更新的套件可能會宣告對 Fuchsia 平台特定 ABI 修訂版本的依附元件 (如 RFC-0002 所定義,這是此工作的依附元件),且必須由支援所需 ABI 修訂版本的 Fuchsia 平台版本下載。

指標

套件更新系統必須支援與系統更新流程類似的指標,並為套件擁有者提供指標,例如成功更新、下載次數、更新的位元組大小 (目前系統更新沒有這個指標) 等等。

我們必須允許套件擁有者根據元件版本和執行中的版本,取得元件的指標,而這不一定是最近一次提交至磁碟的版本。

不要在下載更新時封鎖元件啟動

也可以解讀為「不要讓工作階段重新啟動等事件變得非常緩慢」。如果我們在工作階段重新啟動或重新啟動時檢查套件的更新,且沒有急迫更新檢查器,很容易讓工作階段重新啟動速度變慢。這是因為裝置可能必須下載整個套件或一組套件,才能完成工作階段重新啟動程序。

重新啟動系統後,成功套用的更新應不會還原原狀

如果裝置在重新啟動時沒有網路存取權,我們就不得在重新啟動時改用先前版本的套件,且如果網路不穩定,我們也不得阻擋應用程式啟動,除非該應用程式過於過時。

我們可能會定義套件「過時」的定義,並在套件更新前拒絕啟動

如果應用程式明顯過時,且已知有可用的更新,我們日後可能會進入「強制更新模式」,以免啟動已知有安全漏洞或已知有問題的軟體版本。

在用戶端程式碼中含有單一網址的套件,必須能夠根據裝置情境包含不同的程式碼或資料

許多開發人員不想或無法在每次依附套件可用的更新版本推出時變更程式碼或資訊清單。這類情況的例子可見於模組設定,其中會為模組元件編碼特定元件網址。開發人員為了因應依附元件的每項變更而變更這些設定,會帶來不理想的體驗,而且還有其他無法變更的情況。

這表示單一套件網址必須代表套件的不同版本,實際取決於套件的使用位置和時間 (不過,特定套件的更新規則也可以嵌入系統 ABI 等需求)。

由於單一元件網址無法準確判斷正在執行的軟體版本,因此我們需要新增指標和意見回饋整合功能,以便取得用於偵錯流程的資訊。

要求單一相容版本全域資訊清單的解決方案,長期而言並不可行

多位客戶可能會希望發布套件,而無須與 Fuchsia 或產品擁有者協調或整合。需要從伺服器取得預先定義元組中所有套件版本的單一資訊清單的解決方案,可能無法滿足這類需求。(這也會違反「軟體提交」目標,也就是平台應能產生可安裝任意軟體的產品,而這些軟體在產品建構期間並未知悉)。

將套件下載作業分散至一天的各個時段

如果將套件下載作業限制在一天中的特定時間 (例如在工作階段重新啟動時),裝置可能會在嘗試更新的時間範圍內持續處於離線狀態。更新應在一天中的任何可用時間下載。

套件代管和發布程序的要求

其中部分要求也會導致平台功能支援伺服器端實作。

套件存放區

積極更新的套件必須單獨託管於發布基礎架構,且不受 Fuchsia 系統更新套件和 blob 影響。這可確保代管 Fuchsia 平台套件的存放區不會受到應用程式任何套件變更的影響。這表示平台套件目前不會積極更新,但我們日後可能會放寬這項限制。

如果產品想要提供自己的發布基礎架構,只要符合本 RFC 中指定的規定和設計要點即可。

更新管道

急速更新套件必須支援以管道為基礎的推出作業,並使用與用於發布產品其餘部分的管道組不同。舉例來說,Chrome 可能會比執行的產品使用更多或更少的管道。

這需要用戶端支援團隊協商正確的管道指派。

階段推出

階段性推出是管道管理的延伸功能,可讓使用者根據應用程式觸發百分比推出 (例如 1%、10% 等),以便遵循最佳管理做法。

您也應提供緊急更新機制,以便將重要的推送內容快速推送至 100% 的使用者。請注意,平台提供緊急更新機制,可在五小時內完成 100% 的推播。注意:這超出本 RFC 的範圍,但屬於長期需求。

這需要用戶端支援團隊協商正確的分階段推播群組。

堆疊的石雕

「踏腳石」是指套件版本,任何裝置在升級至最新可用版本前,都必須下載及執行這個版本。

目前我們沒有任何產品明確要求在短期內支援套件階梯,但這項設計將其視為長期需求,我們很可能會收到相關要求。我們不會在實作此 RFC 時明確支援這些功能,但我們不會排除日後支援這些功能的可能性。

這項功能需要用戶端支援,才能協商下載的版本。

安全性規定

  • 基礎集合中的套件無法覆寫獨立可更新的存放區中的套件,請參閱可更新套件群組的必要屬性
  • 為了符合 Fuchsia Verified Execution(FVX) 規範,軟體提交堆疊從磁碟載入記憶體的任何內容,都必須在載入時重新驗證,而非只在下載時驗證一次。請參閱跨重開機驗證的相關章節
  • 讓您輕鬆瞭解及控制哪些套件可包含可執行檔
    • 請參閱「可執行性的控制項」。不含可執行程式碼的套件仍屬於敏感安全性套件,但不屬於 FVX 實作範圍。
  • 輕鬆稽核套件版本的內容。請採用明確的簽署核准程序,以便根據技術控管措施 (而非僅是程序控管措施) 簽署,這項要求超出本 RFC 的範圍,但其他文件會涵蓋這項要求。
  • 套件必須遵守經過驗證的啟動鏈所涵蓋的反回溯機制 - 請參閱跨重新啟動驗證相關章節

設計

本節將嘗試擷取含機器碼的套件更新作業的整體流程。我們會說明執行可行性和程式碼驗證的檢查位置,以及開發人員體驗。我們會強調所需狀態與目前功能之間的差距,並據此制定實施策略。

我們會以 Chrome 做為代表性用途,因為這項服務非常適合用於這個第一階段的工作:

  • 這是要更新的單一套件 (例如 chrome)
    • 沒有必須同步更新的依附元件
  • 套件包含可執行的機器碼
  • 需要管道和分階段推出支援

可更新套件的必要條件屬性

本節將詳細說明建議設計在短期內支援的套件類型。我們預計會在長期內放寬許多這些限制。

  • 在程式碼和設定檔中以非 fuchsia.com 網址參照,以免與基礎套件混淆
  • 必須鎖定特定系統 ABI (依賴 RFC-0002 的實作)
  • 無法依賴設定資料套件 (也就是說,您不能預期設定資料會隨著結構定義或內容中的套件而變更)
  • 可能需要結構化設定
  • 必須在 Fuchsia 全球整合和 Fuchsia 版本認證之外,提供可在 SDK 中執行的認證程序
  • 必須符合基底圖片 2 個副本所剩空間的一半,且不假設與基底圖片檔案重複 (以便套件進行 A/B 更新)
  • 無法依附非基礎套件 (即其他可更新套件)
  • 完整系統 OTA 以外的套件更新不得以不相容的方式變更套件中元件公開的 ABI,且不得變更這些元件從其他非系統套件使用服務的預期 ABI,除非有針對這些互動提供版本策略
    • 可為單一架構或多個架構的套件
  • 套件的依附元件必須處理該套件的解析錯誤 (例如,有些備用版本的邊緣情況會嘗試降級套件,而 Fuchsia 會拒絕執行該套件)。

總覽

我們建議使用已用於正式系統更新檢查的 Omaha 用戶端,檢查套件更新。

我們會為 omaha-client 新增支援功能,以便檢查多個套件和系統映像檔的更新。

我們會將 omaha-clientpkg-resolver 基礎架構整合,以便在本機機器上,抽象化開發人員使用的是實際的 Omaha 伺服器,還是開發伺服器。

我們將新增可更新套件的垃圾收集支援功能,以及安全措施,確保套件更新不會阻擋系統 OTA。

我們會整合 Omaha 伺服器基礎架構,根據特定套件網址協商適當的套件版本。

我們將用途分為兩個主要事件:更新檢查和套件下載,以及套件解析。前者會在計時器上執行,或在開發人員手動要求時執行,並觸發套件下載和快取作業。每當元件架構解析套件時,後者就會執行。

以下將詳細介紹新的套件解析架構。我們會在下節詳細討論。

整體架構

更新檢查和套件下載

檢查更新的時機

omaha-client 會執行狀態機器人,可針對每個「應用程式」進行設定 (對於 Omaha,這表示一組用戶端正在檢查更新的軟體)。

目前在實際環境中,系統更新的 omaha-client 檢查間隔為五小時,並會自動調整為加減一小時。我們建議您為系統映像檔和套件更新使用相同的更新檢查間隔,以便減少 Omaha 伺服器的負載,並在有系統更新和套件更新可用時簡化實作。(請注意,我們預計會在一段時間內,將系統更新檢查與套件更新檢查分開,無論是間隔時間還是代管都一樣。這是為了簡化初始導入程序而採取的短期簡化做法)。

檢查更新的位置 (存放區設定)

我們會為每個可立即更新的套件新增 Omaha 用戶端設定,並根據每項產品編譯至 SWD 堆疊的設定。這會包含系統中哪些套件網址應參照該套件的設定。該設定會包含套件的名稱和網址,以及 Omaha 應用程式 ID 和其他預設設定選項 (例如管道)。

這會產生不錯的安全性副作用:急切更新的套件無法覆寫 user\* 版本中其他存放區的套件,因為這些套件的主機名稱已硬編為不同的值。積極更新的套件永遠無法覆寫基礎套件 (請注意,備用版本不在基礎套件中),因為在 /system/static_packages 清單中具有名稱的套件永遠不會傳送至網路進行解析。

您必須更新 petals 和 fuchsia.git 中的程式碼,以便不再參照套件的 fuchsia.com 網址,而是參照與新存放區相對應的主機名稱,例如 chrome-fuchsia-updates.googleusercontent.com

我們需要在套件更新設定中新增額外中繼資料,這些資料會由套件解析器剖析。具體來說,我們會新增布林值,指定套件是否應包含可執行的程式碼。這個布林值會用於執行階段可執行性限制,類似於 pkgfs 中已包含的限制。詳情請參閱「可執行性控管」一節。

在啟動時,omaha-client 會獲得可更新套件的設定,這些設定來自 vbmeta目前無法設定執行階段設定;也就是說,為了對 omaha-client 進行自動化測試,我們需要繼續建構 vbmeta,就像在 Omaha E2E 測試中所做的那樣。

套裝方案協商

我們將套件協商定義為將套件網址 (例如 fuchsia-pkg://chromium.org/chrome) 轉換為 (主機名稱、Merkle) 元組的程序。這項轉換作業的確切輸出內容會受到多項變數影響,包括:

  • 套件所屬的更新管道
  • 裝置上執行的系統版本
  • 是否正在進行階段性推出作業,以及更新檢查器屬於哪個階段性推出作業群組。

我們建議您盡可能在伺服器端進行套件協商,以便簡化用戶端程式碼。

omaha-client 會向套件更新設定中列出的 Omaha 伺服器傳送要求,至少包含下列資訊:

  • 應用程式 ID (這是 Omaha 服務的套件 ID)
  • 套件更新管道
  • 套件更新版本 (通常會在應用程式 ID 中編碼)
    • Flavor 用於要求套件的不同變化版本,例如含有偵錯符號或工具的版本。由於會在應用程式 ID 中編碼,因此實際工作裝置無法在沒有系統 OTA 的情況下變更在現場執行的版本
  • 支援的系統 ABI (通常會在平台版本欄位中編碼)
  • 階段推出成員
  • 目前的套件版本 (格式為 A.B.C.D,其中 A-D 是 32 位元整數的字串表示法,符合 Omaha 的版本號碼規格),以便 Omaha 執行降級防護功能
    • OMCL 會從套件最新已提交版本的 CUP 中繼資料,或備用版本的 vbmeta 版本中繼資料中擷取此值,如果磁碟上目前沒有套件版本,則會將此欄位設為 0.0.0.0
  • 支援的系統架構 (例如 x64、arm64 等,請與 Fuchsia 支援的架構清單中的名稱相符)

Omaha 伺服器會為裝置計算正確的套件版本,並傳回 blob 的梅克勒固定網址供下載,其中包含要下載的 (套件主機、套件名稱、梅克勒根目錄) 三元組。我們必須先根據 Omaha 提供的梅克爾根,檢查回應內容,才能使用其資料。回應格式可能與系統 OTA 所用的目前 update 套件格式相符。

回應中會包含該應用程式裝置的同類群組,其中包含管道資訊。由於整個回應都會簽署,我們可以利用這項功能有效判斷啟動時的套件管道。回應同類群組也用於變更後續要求的管道 (請參閱下文的管道支援部分)。

Omaha 回應必須實作用戶端更新通訊協定 (CUP),該通訊協定會為 Omaha 回應提供簽名。我們將為 Omaha 用戶端新增對此通訊協定的支援。必須使用 CUP,才能將回應持續儲存至磁碟,並在日後根據儲存在基本套件中並在 vbmeta 中取得根權限的公開金鑰,重新驗證回應。

管道支援

Omaha 通訊協定透過其 cohort 概念支援管道。

由於可更新套件可能使用與基礎系統不同的管道組合,我們需要為開發人員提供類似 fuchsia.update.channelcontrol 的新 API,以便管理開發中套件的管道設定。我們會在 CUP 回應中儲存管道資訊,這與我們在 vbmeta 中儲存管道資訊以便進行系統更新的方式類似。我們打算透過擴充功能,將這個 API 公開為 CLI 工具,並可能提供給希望在執行階段變更元件管道的元件開發人員。pkgctl

如果裝置同時有已提交的更新和套件的備用版本,即使備用版本較新 (即使已提交的版本較備用版本舊),我們仍應使用已提交更新的管道資訊,以免透過更新備用版本覆寫管道指派。我們仍必須先驗證已提交回應的簽名,才能使用其管道資訊。

如果 Omaha 要求包含不存在的管道,Omaha 伺服器應重新導向至實際存在的管道,或使用備用版本。

在 Fuchsia 裝置上使用管道控制 API 要求管道變更時,該設定不會在重新啟動時保留,除非是以包含管道的已簽署 CUP 回應的形式保留。我們不希望信任未簽署的資料,這也是政策規定。下一次的更新檢查會使用記憶體內管道,且在正確的套件版本提交至磁碟後,系統會儲存該更新檢查的回應。

系統映像檔中的 Omaha 設定應包含急速更新套件的預設管道。如果特定套件沒有持續性 CUP 回應,Omaha 應使用預設管道。

如果管道變更導致同一個套件版本傳回 (因為套件位於兩個管道),則下載流程應會照常進行。pkg-resolver 將不會下載任何新的 Blob,而是會傳回磁碟上已有的 Blob。

階段推出支援

伺服器會根據隨機骰子擲骰結果 (這是現有的 Omaha 功能),將裝置指派給同類群組,而同類群組 ID 則用於追蹤裝置是否應接受分階段推出的功能。伺服器端的每個 App ID 擲骰結果皆獨立運作。

持久性所需的大小

Omaha 要求和 CUP 回應相當小,只有數百位元大小。每個套件可能只需要 1 KB 以下的空間來儲存中繼資料。

下載檔案包

一旦 omaha-client 取得 Omaha 伺服器計算的最終梅克爾錨定套件網址,就會觸發下載套件程序,並使用新的通訊協定 (可能稱為 fuchsia.pkg.cup) 將套件下載至 pkg-resolver 元件。如果裝置上已安裝所要求的套件版本,pkg-resolver 就不會重新下載該版本。

如果裝置上沒有新版套件,pkg-resolver 會下載新版套件,並保持開啟該套件傳回的目錄 (以防垃圾收集)。將句柄傳回至套件目錄之前,pkg-resolver 必須將 CUP 要求/回應組合提交至儲存空間,以利日後重新驗證。

pkg-resolver 必須保持這個目錄句柄處於開啟狀態,以免套件遭到垃圾收集 (這項設計假設我們有垃圾收集功能,可追蹤處於開啟狀態的套件,這項功能目前仍在開發中)。必須放棄舊版套件的句柄。

我們也會在 pkg-resolver 的設定中,為 pkg-resolver 下載的每個可更新套件新增大小限制。下載完成後,如果套件大於設定的上限,pkg-resolver 會刪除與套件相關聯的所有 Blob,並以適當的錯誤條件讓更新失敗。

以下是這個流程的圖表:

套件下載流程

如果下載失敗,會發生什麼情況?

下載失敗必須防止更新檢查的 CUP 結果提交。新的解析作業會繼續使用先前提交的版本。

空間管理和垃圾收集

下載套件更新時,我們需要維護幾個與空間相關的變數:

  • 系統一律有足夠的空間可快取核心產品體驗 (例如 Chrome) 執行所需的套件。
  • 系統更新始終有足夠的空間可供安裝,或是我們可以建立足夠的空間來安裝系統更新。

如果允許急迫套件更新使用足夠的不可回收空間來防止系統 OTA,就必須使用 FDR 來修正。如要避免發生這種錯誤情況,就必須變更目前的垃圾收集程序。

我們的垃圾收集機制目前使用下列條件來保留套件:

  • 如果套件位於目前執行中的基礎套件組合中,請保留該套件
  • 如果套件是非基礎套件的最新解析版本,且是在目前的啟動期間解析,請保留該套件
  • 否則,允許刪除套件。

這項策略目前可行,但有幾個明顯的缺點:

  • 可能會刪除系統日後需要的套件 (如果這些套件尚未在目前的啟動期間解析)
  • 但可能不會刪除在目前啟動期間已解決但不再需要的套件 (例如隨著時間累積的測試套件)

我們一直不願意變更目前的垃圾收集方案,直到我們停止使用 pkgfs 來代管套件快取。這項工作已接近完成,我們可以對垃圾收集方案實作變更,以便維護這些不變量,也就是防止垃圾收集程序收集目前正在使用的套件。

針對及時更新的短期實作 (在我們推出以套件追蹤為基礎的垃圾收集功能之前),我們會針對系統更新和及時套件更新採用嚴格的大小檢查。也就是說,可更新的套件「必須」符合產品擁有者預算的空間,我們會在套件上傳時檢查其空間需求 (目前由中央管理)。長期來說,更完善的垃圾收集實作方式將可提供更多彈性。

儲存空間用盡時會發生什麼情況?

雖然我們會盡力在發布時檢查套件大小,並確保所有內容都符合預算,但裝置在下載更新時,空間不足的情況仍有可能發生。

為解決這個問題,我們將新增「核子選項」來管理空間,當 OTA 期間空間不足時,系統會以新的重新啟動原因 (OUT_OF_SPACE_PANIC) 重新啟動裝置,並在網路連線後立即執行 OTA,無須等待檢查間隔。這麼做會觸發垃圾收集作業。系統更新檢查器應在 OTA 完成前,阻擋急迫的套件更新。

我們會在 system-updater 中新增設定選項,決定是否應在 NO_SPACE 錯誤發生時重新啟動系統。我們會讓產品擁有者在 user* 版本中將此選項設為 true,但在 _eng 版本中,預設值應為 false,以利偵錯。如果這個選項設為 true,且 system-updater 在嘗試安裝更新時空間不足,system-updater 就會觸發系統重新啟動,並顯示 OUT_OF_SPACE_PANIC 原因。

重新啟動時,系統更新檢查元件 (omaha 用戶端或 system-update-checker) 會在開機時啟動。如果更新檢查元件發現重新啟動原因是 OUT_OF_SPACE_PANIC,就會立即嘗試執行 OTA,同時抑制套件更新。

我們也會在 OUT_OF_SPACE_PANIC 重新啟動時加入退避,確保如果裝置連續多次重新啟動,且 OUT_OF_SPACE_PANIC 是原因,我們就不會繼續循環重新啟動。

從可更新套件啟動元件

下次透過元件架構解析元件時,就會使用可更新套件中的元件。在某些產品中,夜間工作階段重新啟動會自動處理元件重新啟動,因此可能不需要更新通知。不過,其他產品的元件可能會需要更新通知,以便向使用者顯示「請重新啟動」通知。詳情請參閱「未來工作」一節。

新元件的解析流程

所有套件網址仍會採用 fuchsia-pkg://{hostname}/{package_name} 格式。不過,由於某些套件網址的套件協商作業會由裝置上的 Omaha 用戶端管理,而某些套件則會透過其他方法 (例如 cache_packagesbase_packages) 管理,因此我們需要套件解析流程,讓套件堆疊能夠區分應透過 Omaha 管理的套件,以及應透過其他中繼資料管理的套件。

為此,我們建議採用以下解決流程。如果呼叫使用 fuchsia.pkg.PackageResolver 通訊協定解析特定套件網址,pkg-resolver

  1. 套用基礎固定和重寫規則
  2. 判斷要解析的重寫網址是否由 Omaha + CUP 管理,如果是的話...
  3. 請管理 CUP 套件的內部程式庫,為與該網址相關聯的目錄句柄提供相關資訊。
  4. CUP 程式庫會擷取該網址在最近一次提交的 CUP 回應中快取的 Merkle 指定套件,或是在遇到錯誤且符合使用備用版本的條件時,改用備用版本。對於所選套件版本,它會直接前往 pkg-cache 並傳回目錄句柄 (以避免在寫入新套件時讀取套件的各種競爭狀態)。

以下是這個流程的圖表:

可更新套件解析流程

可執行性的控制項

pkg-resolver 是實際裝置上有效解析路徑的一部分,表示需要參與 FVX 執行程序。我們需要信任的金鑰清單將包含 CUP 金鑰。

我們會在 pkg-resolver 中實作可執行性控制項,確保在 user* 版本中,只有以下套件可包含可執行程式碼:

  • 在基地
  • 由 Omaha 管理,並在 pkg-resolver 設定中設為可執行檔
  • 透過現有的 pkgfs_static_packages 許可清單機制進行執行可行性控制

這些控制項的具體實作方式將是後續文件的主題。

驗證重新啟動時下載的中繼資料

驗證簽章和可稽核性

為了在重新啟動時保留可更新的套件,我們需要記住其 Merkle 根目錄。我們會保留 Omaha 要求和 CUP 回應。當我們使用已儲存的資料尋找套件的 Merkle 時,會驗證以下事項:

  • 要求/回應組合的簽名與信任的金鑰相符
  • 這項要求等同於裝置有網路連線 (符合 Omaha App ID、管道和版本) 時,我們要求的內容。
  • 回應符合簽名參數且為有效回應,
  • 回應的預期版本大於或等於系統映像檔中的備用版本
  • 回應包含支援執行中系統 ABI 版本的套件

如果符合所有這些條件,pkg-resolver 中的 CUP 模組就能從 CUP 回應中傳回已儲存的 Merkle。

這種做法有缺點:我們需要剖析已儲存的請求和回應,才能取得已儲存 Merkle 根的值。不過,我們認為只要將實際執行剖析作業的元件隔離,就能降低實作時的風險。

防止復原

這個解決方案會使用版本回溯點,依賴 vbmeta 提供的回溯保護機制。如果 CUP 回應中的預期版本號碼比系統映像檔設定中所需的最低版本還舊,我們會改用 OTA 映像檔中所含的套件版本 (如果有),且不會提交回應中的版本。

以下是使用此配置實作復原防護機制的範例,假設可更新套件的 1.0 版有安全漏洞,而該漏洞已在 1.1 版中修補。我們需要指示 Omaha 停止提供 1.0 版本,並開始提供 1.1 版本,然後向車隊發布系統 OTA,並提供 1.1 版本的新備援版本。在沒有 Replay Protected Memory Blocks 或等效功能的裝置上,這項功能可保證效能不會比目前的單體行為更差。

對於沒有設定備用版本的套件,我們會新增以時間戳記為基礎的備用版本,並確認該備用版本比該套件的每個 CUP 回應都舊。系統必須先啟用這個模式,才能支援更新系統先前未知的套件。回溯時間會設為系統回溯時間,目前是產生系統映像檔的 Fuchsia 版本的時間戳記。目前我們沒有針對更新系統映像檔中未知的套件提出任何規定,但我們應以此為前提進行設計。

早期啟動作業所需的套件備用版本

在啟動程序期間或裝置連上網路前,可能需要更新可更新套件中的某些套件 (例如,這些套件可能會為開箱體驗 (OOBE) 提供使用者介面,用於設定網路)。這表示如果某些套件過時,我們無法依賴網路下載這些套件,必須在系統更新映像檔中加入這些套件的版本。我們將這些套件稱為「備用版本」,因為這些套件通常只會在沒有其他更新版本的套件時才會使用。

備用版本的設計壓力有以下幾點:

  • 必須能在沒有網路連線的情況下執行
  • 因為 OOBE 可能需要這些資料,因此必須在恢復原廠設定 (FDR) 後保留
  • 必須透過系統更新發布,且符合系統更新的資格
  • 我們必須盡量減少備用版本使用的空間,尤其是與其他套件版本搭配使用時
    • 在必須儲存備用版本、目前執行中的版本,以及尚未執行的新版套件時,就表示我們需要一次儲存三個特定套件副本 (假設我們禁止系統更新和套件更新同時發生)
  • 我們必須盡力避免系統執行套件第 N 版,但系統更新會強制降級至套件第 N-1 版的情況,因為避免降級是提供給開發人員的實用屬性
  • 在防止套件降級的同時,我們也必須避免因降級而拒絕系統更新

特定套件擁有者應與產品擁有者協調,決定是否需要套件的備用版本。並非所有可更新的套件都需要備用版本。

為了在 OTA 映像檔中加入備用版本,我們會將備用版本套件放入系統更新套件,並在系統的 system_image 套件中加入該套件的版本中繼資料。版本管理中繼資料 (主要是 Merkle 根目錄) 可讓 pkg-resolver 回應套件要求,即使沒有網路連線也一樣。由於版本中繼資料已納入 vbmeta,因此不會受到 FDR 刪除。

在系統更新期間,我們會進行垃圾收集並刪除更新套件的未提交版本,但我們可能仍需要儲存三個套件版本,其中包含備用版本:目前系統映像檔中的備用版本、已下載且可能正在執行的已提交版本,以及新系統映像檔中的新備用版本。

使用備用版本的時機

只有在 CUP 中繼資料中,備用版本比磁碟上套件的最新版本「較新」(具有較大的版本號碼) 時,CUP 模組才會傳回備用版本的目錄。

如果套件的最新更新版本已損毀,手動恢復原廠設定會清除儲存的 CUP 中繼資料,並讓我們根據條件 1 將系統重設為備用版本。日後,我們也可以使用 Blob 損毀處理程序,判斷更新套件是否損毀,並使用備用版本 (這項功能已在「未來工作」中提及)。

從技術層面來說,新系統映像檔中的備用版本可能會從最近提交的套件降級。不過,為了使用降級的備用系統,新系統映像檔必須不支援最近提交套件的必要 ABI。我們發現以下情況:系統發布時,使用較新的系統 ABI,以及較舊的備用版本,支援該新系統 ABI 的情況不切實際,但我們會調查是否要新增伺服器端檢查,以避免發生這種情況。

如果系統更新升級備用版本,而該版本高於最近提交的版本,我們應根據 CUP 中繼資料中的版本字串,優先使用最近更新的套件版本。

指標

可更新套件中的指標

為了讓第一方套件開發人員受惠,我們會新增 Cobalt 指標,自動根據產生指標的元件套件版本進行匯總。Cobalt 目前在記錄任意字串 (例如可更新套件的雜湊) 方面有某些限制。我們不打算直接在套件格式中嵌入版本號碼或任意其他版本字串,而是在用戶端端的 CUP 中繼資料中嵌入這些版本號碼,並使用這些資料向工具回報版本。由於 pkg-resolver 可以存取套件的 CUP 中繼資料,因此可以將已提交套件的版本字串回報給 Cobalt。

指標傳送至 Cobalt 的方式有幾種,但都必須支援版本資訊:

  1. 從元件直接記錄
  2. 代表其他元件記錄資料,例如 Sampler 會讀取元件檢查資料,並以 Cobalt 指標的形式傳播
  3. 代表許多人從元件記錄資料,例如記錄統計資料。這可能看起來像是 2. 的一般情況,但代表我們選擇的任何解決方案都需要可擴充性。

Fuchsia 使用 Cobalt 時,需要整合可更新套件,並符合以下幾項規定:

  1. 元件擁有者不想使用以 Merkle 根目錄細分的版本資訊主頁,他們偏好人類可讀的字串
  2. 為了準確匯總人類可讀的版本字串,我們需要在裝置上執行這項操作
  3. 我們無法為每個套件的新版本更新 Cobalt 註冊表,因為這會違背獨立套件更新發布流程的用意
  4. 提供給 Cobalt 的元件版本資料,可能會是指不再執行的元件版本,例如記錄統計資料
  5. 我們不需要為短期解決方案支援 v1 元件 (因為無法動態更新),但我們確實需要為「可」更新的每個元件回報版本字串。Cobalt 會為每個套件提供版本字串,但我們不會強制要求所有套件都必須使用新格式,才能為任何套件啟用版本回報功能。

為了將套件的版本字串記錄到 Cobalt,我們建議採用一套系統,讓套件的 Merkle 根與指標一起傳播,以便追蹤哪個元件版本產生特定指標。

我們會修改 Cobalt 記錄通訊協定,允許使用選用的套件 ID 欄位。一旦 Cobalt 元件本身具有與特定指標組相關聯的 Merkle 根,即可將該 Merkle 根對應至以字串為基礎的版本字串,藉此在本機匯總指標。它也可能會將這個梅克爾根與其他來源 (例如記錄統計資料或取樣器資料) 的元件名稱結合。

我們必須依裝置上的版本字串匯總資料,而非將資料傳送出裝置,才能符合 Cobalt 的隱私權規定。由於裝置上可能會有人類可讀的版本資訊,因此建議在裝置上進行對應,而非在後端系統上執行。

客戶也希望能使用人類可讀的任意字串,將指標劃分為不同區塊。目前,個別套件可記錄其版本,因此支援此功能,但沒有全系統的系統或標準。Android 支援此功能,稱為版本名稱

為此,我們建議在 CUP 中繼資料中新增另一個欄位,並以 Omaha 回應的擴充功能傳送,也稱為 version-name。這個欄位只會用於處理人為版本字串,SWD 堆疊不會保證其唯一性,也不會用於防止降級;套件擁有者必須自行維護版本字串。

驗證版本字串:新版 SWD API

基於隱私權考量,Cobalt 會限制可記錄的字串類型。特別是,Cobalt 無法信任任意字串 (例如版本字串) 不會包含個人識別資訊 (PII)。為了讓我們記錄元件的版本字串 (Cobalt 無法事先得知,因為元件可能會更新為任意版本),Cobalt 需要透過可信任的系統元件驗證特定版本字串。

軟體提交團隊會與 Cobalt 團隊合作,驗證套件 ID (Merkle 根或套件網址),並將其與人類可讀的版本字串建立關聯,以利於遙測。當 Cobalt 嘗試根據套件 ID 找出特定套件的版本時,SWD 堆疊會傳回版本名稱和版本號碼 (如果有)。

我們目前不會針對與系統映像檔相關的任意元件保證版本資訊,但我們日後可能會將包裝檔中的人類可讀版本資訊編碼標準化,並在此擴大保證範圍。

如果記錄元件未將套件 ID 提供給 Cobalt (例如記錄統計資料的情況),Cobalt 應根據套件 ID 回報目前執行中元件的版本。

軟體提交堆疊會預期 Cobalt 會快取對要求的回應,以便使用者可讀取版本字串。Cobalt 元件必須避免為每個指標呼叫版本字串 API,否則會產生不必要的資源成本。我們可能會選擇建立大量 API,讓 Cobalt 定期呼叫,以減少版本字串的 FIDL 流量。

我們可能會在日後決定將版本字串和版本名稱整合至套件格式,但目前不建議這麼做。

關於更新系統本身的指標

我們也需要回報套件下載程序本身的指標,例如成功/失敗指標。如上所述,Cobalt 基於隱私權考量,在記錄任意字串時有一定限制。

不過,由於 Omaha 也有後端指標,且各自採用不同的隱私權保護策略,因此 Omaha 本身可提供執行特定版本的裝置精確數量指標。相較於 TUF 替代方案,這是本解決方案的優勢。

針對更新程序本身的指標,我們打算使用 Omaha 的指標,並與 Cobalt 團隊合作,在極少數情況下 (經過信任的系統元件驗證) 根據任意字串回報維度。

最後,我們會記錄更新基礎架構中可更新套件的版本,以利排解問題。

實作

我們可能會將這項工作分成多個階段,以便隨著功能增加而逐步完成。

第一階段可以包含以 Omaha 為基礎的基本套件擷取通訊協定,這需要:

  • SWD
    • omaha-client 中的多應用程式支援
    • 實作 CUP,並整合至套件解析流程
    • 用於測試的開放原始碼 Omaha 伺服器最小版本
    • 套件設定,將套件網址對應至 Omaha 應用程式 ID。
  • 伺服器端封裝團隊
    • 伺服器端封裝基礎架構中的版本管理,以及基本套件協商,例如管道和降級防護
    • 套件儲存空間,以及與 Blob 儲存空間整合。
    • 伺服器端套件協商 (管道支援、分階段推出等)

第二階段將推出用於實際使用的額外安全機制,以及空間管理所需的安全措施:

  • SWD

    • 實作 CUP 後置停止
    • 空間管理:實作開放套件追蹤和徹底的垃圾收集功能,以便更靈活地管理套件。
    • pkg-cache 以外的可執行性限制
    • 應用程式專屬的評估程序
    • 建構在基礎映像檔中的套件備用方案
    • 新增 Cobalt API 以驗證版本字串
  • 伺服器端封裝基礎架構

    • 啟用 CUP
    • 發布時間來源和程式碼簽署檢查 (簽署前)
    • 強化套件協商支援功能,例如分階段推出、ABI 修訂協商等
    • 發布時間大小檢查
    • 新增套件上傳介面,以便指定版本字串

注意:本節未涵蓋 RFC-0002 的實作方式,該依附元件有其自身的實作計畫。

成效

我們認為這項變更不會對執行階段效能造成重大影響。在推出這項異動時,我們需要密切監控 omaha-client 的記憶體和 CPU 用量,以及 SWD 堆疊的其他部分。我們也會監控啟動和重新啟動時間,以便找出回歸情形。

這項變更確實會導致磁碟用量增加,因為儲存套件需要額外空間,而儲存套件中繼資料也需要少量額外空間。請參閱「空間管理」一節,進一步瞭解這些考量因素。

人體工學

Eng 工作流程

我們將建構新的 _eng 工作流程,以便使用 omaha-client 測試及驗證套件更新。我們預期大多數使用套件的開發人員會在開發過程中使用以 TUF 為基礎的工作流程,但我們需要先建構開放原始碼的 Omaha 伺服器,才能在正式版中發布平台更新,以便執行自動化測試。

我們預期在 SDK 工具之上疊加的產品工具,最終會在 eng 版本中新增 Omaha 設定支援。在這個功能推出前,產品開發人員可能必須直接與 Omaha 設定互動 (這是設定套件測試工作流程的一部分)。

我們也需要擴充指令列工具,以便在執行階段新增可動態設定的更新套件。

回溯相容性

這項工作應與現有的軟體提交堆疊相容,主要原因是我們在開發 Omaha 流程時保留了 TUF 流程,且不會變更套件網址命名空間結構。

安全性考量

這項解決方案涉及許多安全性考量。首先,這項設計首次在使用者裝置上導入間接驗證功能。這項做法本身就存在較高的安全風險,但我們認為這項權衡是值得的,而且可以透過我們現有的所有防禦措施和緩解措施來管理。

以下是與 TUF 替代方案不同的安全性敏感工作:

  • 我們需要新增可信任的通訊協定和剖析器:CUP
  • 我們需要將 CUP 公開金鑰新增至 vbmeta。
  • 我們需要將 omaha-client 設為 fuchsia.pkg.PackageResolver 通訊協定的實作項目
  • 我們需要在 Omaha 用戶端中實作某種程度的可執行性限制,可能類似於「可執行性控制項」一文中所述的個別存放區設定可執行性位元。

所有這些安全性工作都很容易處理,但需要詳細的設計,並與安全團隊協調。

隱私權注意事項

無法選擇不採用伺服器端 Omaha 伺服器收集的任何指標 - 用戶端必須將執行情境 (ABI 修訂版本、應用程式 ID 等) 提供給伺服器,而伺服器可能會決定保留該互動的指標。

Google Omaha 伺服器的 Omaha 指標存取權受到嚴格限制,但確實會提供精確的數字,說明有多少裝置下載或正在執行特定版本的軟體。

如要進一步瞭解 Omaha 通訊協定如何保護隱私權,請參閱Omaha 通訊協定規格

測試

相關章節會說明套件版本的資格。

您必須對導入部分中詳細說明的變更進行全面測試。具體來說,我們需要新增對樹狀結構內 Omaha 伺服器的支援,才能針對 Omaha 用戶端執行整合測試。我們也會投資於整合測試,測試 Omaha 用戶端與套件解析工具之間的新互動。

我們將使用新的 Omaha 伺服器,為新的更新流程進行端對端測試。

我們也需要嚴格測試新的垃圾收集機制,包括填滿磁碟的整合測試,確保系統仍可安裝更新。

最後,我們需要嚴格驗證新的或變更的安全性途徑:CUP 回應驗證和持久性,以及可執行性限制。

說明文件

我們會在推出這些異動時,更新 fuchsia.dev 上的軟體提交說明文件,至少詳細說明以下內容:

  • 可更新套件的模型和狀態轉換圖表
  • 如何設定更新
  • Omaha 伺服器導入作業的必要條件

日後的作業

Update Notification API

除了實作這項初步提案,我們也會提供通知 API,讓用戶訂閱套件更新內容,以便在需要時觸發元件重新啟動。更新通知有兩種用途:

當元件由其父項元件重新解析時,套件解析作業會使用新版本的套件,這可能發生在元件重新啟動或工作階段重新啟動時。

我們建議使用新的元件解析器 API,其功能如下:

  • 解析元件
  • 提供通知管道,用於通知該元件可用的更新
  • 在下載新版本完成且可供使用時,通知管道另一端的持有者,然後提供自動重新啟動機制

我們會透過元件解析器將通知和觸發邏輯傳送至套件解析器,後者會提供類似的 API。我們也會新增元件能力,讓元件向 framework 提出要求,以便通知元件有更新,讓元件觸發自身的重新啟動。

更新套件的 Blob 損毀通知

如果套件下載後遭到毀損,我們應嘗試重新下載毀損的 blob,或使用該套件的備用版本 (如果有),前提是該版本符合使用備用版本的規定

這項功能會整合現有的 blob 損毀通知 API,並針對損毀的更新套件,觸發重新下載或清除快取 CUP 中繼資料的動作。

針對無法啟動的可更新套件進行前景更新

如果可更新套件中的元件無法啟動,我們應為產品擁有者提供選項,在套件系統重新下載套件時,顯示前景更新畫面。這樣一來,使用者就不需要透過 FDR 來取得可執行的備用版套件。

更新可更新元件的自我檢查

部分元件可能會希望自行檢查更新,並觸發下載作業。我們應該調查哪些客戶使用這項功能,以及是否應將這項功能整合至元件架構和軟體提交 API。

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

替代方案:使用 TUF 進行套件協商

我們可以使用更新架構,而非使用以 Omaha 為基礎的通訊協定,藉此發布套件版本中繼資料,並將特定 fuchsia-pkg:// 網址轉換為 Blob 的主機名稱和 Merkle,以便下載。

這項功能的基本設計如下:

  • 將「eager update」函式新增至 pkg-resolver,該函式會複製目前 omaha-client 狀態機器人的部分,並定期檢查套件群組的更新。
  • 全面檢查我們的 TUF 中繼資料格式,並在 Fuchsia 更新伺服器上實作新格式,藉此實作管道、分階段推出、墊腳石等功能。這項工作相當繁重。
  • 當 pkg-resolver 收到特定套件網址的要求時,請要求該套件下載該存放區中所有套件的全新中繼資料 (如果沒有通往更新伺服器的網路或路徑,則使用已快取的中繼資料),並根據要求的內容計算要下載的正確梅克勒根目錄,包括管道、分階段推出的會員資格等,所有內容都必須在裝置上本機快取。也就是說,伺服器無法根據情境變更對用戶端的回應,所有運算都會在本機完成。
  • 為每個急切更新套件儲存已下載的中繼資料,這可能會占用大量空間,具體取決於 TUF 存放區中存在多少管道和版本 (根據我們的估計,如果採用簡單方法,每個套件約為 3 MiB;如果我們進行更多工作來啟用 TUF 中繼資料分割,則每個套件約為 82 KiB)
  • 找出客戶可接受的以 Cobalt 為基礎的指標方法 (提供足夠精確的裝置數量,以便判斷裝置是否執行特定版本等),且不會違反 Cobalt 的設計原則
  • 透過 ffx 新增工程工作流程,以支援急迫更新存放區,並更新開發人員工具,以支援新的 TUF 中繼資料格式。
  • 實作降級防護機制 (Omaha 會在伺服器端提供這項機制)。

這個替代方案有幾個優點:

  • 由於 TUF 中繼資料會持續存在,因此用戶端可以「檢查自己的工作」,因為證明套件正確版本所需的所有資料都會實際存在裝置上。
  • 我們已將 TUF 用於工程流程,因此已有許多相關工具。
  • 套件網址與 TUF 存放區和套件名稱相對應的概念相當實用,而且不需要對應至 Omaha App ID 等其他服務專屬 ID。

這個替代方案也有缺點:

  • 我們已使用 Omaha 進行正式版系統更新,因此這項功能會引進第二個更新伺服器,這是執行正式版 Fuchsia 裝置所需的。
  • 我們必須重新導入 Omaha 提供的許多功能,包括分階段推出、管道支援和踏板。
  • 如果我們在系統映像檔中取得錯誤的套件協商代碼,就必須使用 OTA 裝置來修正,而非在伺服器端修正協商邏輯

我們認為這種方法的缺點大於優點,而且兩種方法之間的差異大多可以抽象化,因此我們採用了上述的 Omaha 方法。

既有技術與參考資料

可更新軟體套件的實作方式非常多。其中一些重要項目包括:

更新中繼資料通訊協定參照如下:

  • Omaha 規格:許多 Google 和非 Google 產品 (包括 Fuchsia) 都會使用這項規格。
  • 更新架構規格,目前在某些地方由 Fuchsia 使用。

附錄:發布時的大小計算

對於有嚴格大小預算的裝置,我們不希望使用過多空間而干擾 OTA,因此需要為套件提供選用的大小預算,並在套件發布時進行檢查。

Fuchsia 目前會在將資產寫入磁碟時進行串流壓縮,而壓縮等級會在系統組合期間決定。為了預先計算特定套件會佔用多少空間,我們需要知道特定系統版本的壓縮等級,因此建議您將 blobfs 壓縮等級和壓縮配置設為系統 ABI 的一部分。這樣一來,我們就能在伺服器上使用相同的壓縮參數,檢查套件是否符合其預算,並將套件傳送至套件存放區,或將套件發布失敗。

在發布程序中,套件伺服器基礎架構會檢查應用程式的壓縮大小 (使用指定 ABI 的壓縮參數和 SDK 工具壓縮的 blob 集合的累積大小) 是否符合目標裝置上為套件所設定的預算。

無論在發布過程中進行多好的大小檢查,最終都有可能會導致大小檢查程序中斷,導致裝置在嘗試下載更新時空間不足。如要進一步瞭解如何管理這項功能,請參閱「空間管理和垃圾收集」一節。

如果不想模擬裝置上的壓縮方式來檢查大小限制,我們可以實作離線壓縮功能,以便確切瞭解套件中特定檔案組合會佔用多少空間。不過,這項工作並未列為優先,且可能需要大幅變更 SWD 和 Storage 程式碼集。