Mac OS X Lion 的 Sandbox 技術初探

Mac OS X Lion 的 Sandbox 是一項了不起的創新。當然,我不反對有人批評目前的 entitlement 可選項不夠完備,還需要擴展。在假設今後可能加入新選項的前提下,現有的概念和實現已是巨大的進步。

操作系統侷限於 discretionary access control 和 mandatory access control 兩種安全模型已經太久了!後者概念複雜,除了涉密極高的部門,連電信銀行等大型企業都幾乎無人採用,完全沒可能進入個人計算領域。前者又過於簡單:資源(通常是文件)本身擁有一個訪問限制列表 (access control list) [1];進程被賦予一個系統帳號身份,爲下列情況之一:

  • 用戶的登錄帳號,如果是從普通的系統圖形化工具如 OS X Finder 或者 Windows 的「start」菜單啓動,這是最常見的情況;
  • Supervisor 用戶,如果是用 sudo 或者 Windows 的 lauch as administrator 功能啓動;
  • 其它帳號,如果是通過 login 等工具設定 shell 進程的帳號再從 shell 啓動。

操作系統根據進程的帳號身份和資源的 ACL 決定滿足還是拒絕進程對資源的訪問請求。用戶可能接觸到兩種程序和兩種數據:

  • 非可信程序:其訪問和處理數據的行爲不明;
  • 非可信數據:可能通過 buffer overflow 等手段向程序注入惡意代碼;所以,從系統安全的角度,非可信的數據和程序被等同視爲惡意代碼的潛在載體,下文統稱「惡意代碼」。
  • 可信程序:其訪問和處理數據的行爲經過驗證(比如通過低權限帳號長時間運行而沒有在系統的 audit logs 中發現其可疑行爲被拒絕的紀錄;或者由聲譽良好的組織發佈;或者是操作系統的自帶工具)。系統安全的目的之一是避免可信程序被惡意代碼篡改。如果可信程序遭到篡改,那麼系統中就出現了名義上可信而實質上有惡意的代碼,安全防線即被攻破。
  • 可信數據:用戶自己創建的文件,或者通過其它方式確保不含有惡意成分的數據(比如通過低權限帳號運行的進程打開,而未在系統的 audit logs 中發現其可疑行爲被拒絕的紀錄)。系統安全的目的之一是避免可信數據被惡意代碼篡改,以及避免敏感的可信數據惡意代碼讀取和泄漏。可信數據遭到篡改的後果和可信程序被篡改的危害相同,因爲可信數據本身可以是控制其它程序行爲的配置數據,也可以被修改爲夾帶惡意注入代碼。

在可能接觸到惡意代碼的環境中,要通過 DAC 保護可信程序和用戶的可信數據需要在使用中自覺遵守下列行爲規範:

  • 每個用戶要有多個系統帳號,對應不同的權限級別。
  • 可信數據的 ACL 只對足夠高級別的系統帳號身份開放操作,對可信數據寫操作的權限要求的帳號級別比讀權限更高。
  • 不可信數據的 ACL 要先設置爲對低級別帳號開放。
  • 用戶訪問不可信數據要以低權限級別的系統帳號啓動應用程序。這樣即使不可信數據對程序注入了惡意代碼,也不會破壞或者泄漏可信數據。
  • 一定要用低權限級別的系統帳號啓動不可信的程序。不可信程序只能暫時處理實驗性質的數據(其 ACL 對低權限帳號開放,例如非敏感數據的實驗拷貝)。
  • 當數據足夠可信時,要修改它的 ACL 相應的提高允許訪問的級別。
  • 當程序足夠可信時才用足夠高權限的帳號啓動它,以便處理可信數據。

DAC 的問題

DAC 的概念雖然比 MAC 容易理解,但需要用戶自覺遵守繁瑣的行爲規範 [2]。對於專門的服務器維護人員來說這不算什麼。但是對個人用戶來說,即便我這樣的程序員也很難嚴格遵循所有規範(最多不用 supervisor 用戶登錄,避免系統文件被破壞,對用戶可信數據的保護明顯不夠)。DAC 的主要問題在於靜態性。資源的 ACL 是靜態的。理論上說帳號在整個進程 session 中是靜態的 [3] 。實際中更糟糕,由於缺乏工具(多爲 sudo, login 之類的命令行工具而少有圖形工具,多爲提權 (previlege elevate) 而非限權操作),用戶會在整個 login session 使用同一帳號啓動所有程序(對,我也是這種人)。但是數據和程序的可信度是動態的,可信度的變化的對於不同的訪問操作(讀或寫)也不相同。要求普通用戶根據情況不斷手工微調 ACL 和採用不同帳號的策略是不現實的。

人們採取過一些方法來彌補 DAC 的靜態缺陷。一是權限隔離 (privilege separation) 。讓程序中可能訪問不可信數據的代碼從主進程分離出去,運行在低權限的進程中 [4] 。這種方案的侷限在於它只能彌補用戶身份的靜態缺陷而不能解決 ACL 的靜態缺陷,不過它能滿足一定適用範圍的需求 —— 通常可信程序和數據的 ACL 會把讀訪問開放給權限較低的帳號,把寫訪問限制於權限較高的帳號,權限隔離可以防止可信數據和程序遭到篡改。但是對於可信數據泄漏等方面,仍然存在靜態 ACL 固有的問題。

另一種彌補 DAC 靜態缺陷的方法是動態 ACL 擴展:在操作系統本身爲資源設定的靜態 ACL 基礎上,通過某種擴展爲資源再強加一個更容易動態修改的 ACL。從基本概念來看,這是個改進,但是我一直對其很反感,因爲從來沒有一個真正可靠的動態 ACL 擴展實現:

  • 幾乎所有動態 ACL 擴展都不是內核的有機組成部分,而是通過一些 daemon/service 加 kernel-hook 實現的半吊子監視器。除去各種具體的 bug 不一一列舉,這種半吊子監視器甚至被暴露出普遍在多核系統上存在 race condition,會 grant 錯誤的,甚至是任意權限給惡意代碼。
  • 由於不是操作系統的內建組成部分,難以保證所有應用程序都遵守動態 ACL。
  • 儘管比修改靜態 ACL 方便,動態 ACL 的修改仍然需要過多的用戶手工操作。
  • 要實現比較細緻的安全保護,動態 ACL 需要動態的進程帳號來配合,而後者超出了一般動態 ACL 擴展的控制範圍。

DAC 模型始終讓用戶和安全專家充滿挫折感。它的困難在於缺乏有效的工具同時動態調整兩個靜態因素 —— 帳號和 ACL。通過半個多月的閱讀文檔,討論,以及編程測試,我發現 OS X Lion sandbox 完全解決了上述問題。它是 kernel,系統服務和 UI framework 的完整組成部分,提供動態權限的模式脫離了基於資源的靜態 ACL,基本不需要用戶額外的手工干預。

基本 Sandbox —— Container 目錄

首先回顧一下現有的 DAC 機制如何決定一次訪問是否被允許。

如上圖所示,DAC 機制下一次資源訪問的具體步驟是:

  1. 應用程序向 kernel 提出訪問資源的請求。我們日常討論的時候經常把整個過程簡化描述爲「應用程序讀文件」。但是從技術細節上來說,應用程序不可能繞過 kernel 直接訪問資源。
  2. Kernel 檢查資源的 ACL,確認當前程序的帳號是否被允許此次訪問。
  3. 如果允許,kernel 將相應的信息(如文件內容,或者允許後續文件操作的文件引用)返回給應用程序。

Sandbox 下的一次訪問如下圖所示。操作系統 kernel 會爲每個 sandbox-ed 應用定義一個 sandbox 範圍。在文件訪問方面,最嚴格的也是每個進程 session 初始狀態的 sandbox 範圍是分配給應用程序一個只供它使用的目錄,稱爲 container 目錄。Sandbox-ed 應用只能在這個目錄裏讀取和創建文件 [5]。由於可訪問的資源限制在 container 目錄之內,這些資源的 ACL 沒有實質的作用。

這和 iOS 的 sandbox 機制類似。但是對於桌面計算環境來說,大多數應用程序產生和讀取文件的操作必需能在任意目錄下進行。OS X Lion sandbox 的最大優勢是提供安全而且友好的方式擴大 sandbox 範圍。

必要的噪音

下圖顯示了一個 sandbox-ed 應用如何擴展自己的 sandbox 範圍。在 OS X Lion 系統中有一個 Powerbox 服務進程。Sandbox-ed 應用程序向 Powerbox 提出請求擴展 sandbox 範圍。Powerbox 執行步驟 2-6 ,通過 DAC 機制決定是否可以加入新文件到 sandbox 範圍。如果得到允許,通知 kernel 擴展該應用的 sandbox 範圍。

問題的關鍵是 Powerbox 基於什麼原則來擴展 sandbox 範圍。這是一個危險的敏感操作。通常安全系統的設計慣例要求這種操作釋放出用戶能感受到的「噪音」,即圖中第二步的「necessary noise」。用戶在接收到噪音之後判斷程序的提權操作是否可疑。如果可疑就中止整個過程。

這種噪音的呈現形式既不能弱到讓用戶無所察覺地擴展 sandbox 範圍,也不能強到過於干擾用戶。Windows UAC 就是這方面一個著名的失敗設計 —— 強迫用戶過於頻繁地回答「是」或「否」的問題讓用戶養成不假思索點擊 OK 的習慣動作。(而 UAC 還只是一個簡單的動態修改進程帳號的功能,並非 sandbox 這樣完整的動態授權方案。)

OS X Lion sandbox 機制的聰明之處在於把這種提示做得不着痕跡又不可能被忽略。當應用程序請求 Powerbox 爲其擴大 sandbox 範圍時後者會彈出 Open/Save File Panel 讓用戶指定讀取或者保存/創建的文件。這是把原本屬於應用程序職責的兩個操作 —— 提示用戶打開和保存文件(注意是提示而不是打開/保存的操作本身,後者依然屬於應用程序)—— 移交給 Powerbox 作爲擴展 sandbox 範圍的「噪音」形式。噪音從唐突的「是」或「否」問題形式,變成了用戶早已熟悉而且一定會謹慎執行的操作。在 OS X 原有的 DAC 安全系統基礎上,整個方案涉及多個模塊改進和協作:kernel 提供系統調用 (system call) 允許服務進程  Powerbox 修改 sandbox-ed 應用的 sandbox 範圍;Powerbox 提供 Open/Save File Panel,並且保證它們和應用本身 UI 的無縫集成(包括在正確的位置顯示 panel,繪製 custom accessory sub-panel 以及在 Powerbox 和應用之間正確雙向接力 accessory sub-panel 的動作);Cocoa 要保證 sandbox-ed 應用在 API 調用方面和非 sandbox 應用程序保持源代碼一致。這是任何操作系統之外的第三方產品都無法做到的,也要求操作系統本身具有高度的整體性。

Sandbox 實測

由於 OS X Lion 發佈時間不長,關於 sandbox 的文檔和討論並不多。我寫了一個程序來驗證上述概念。

驗證程序的 UI 如上所示。在文本框中輸入文件的路徑。點擊 Test 按鈕,程序就會在 console 中打印出對這個文件是否有讀操作的權限。UI 上還有兩個 checkbox。原本打算在「Open Panel」選中的時候,讓 Test 操作忽略文本框中的路徑,(通過 Powerbox)開啓 Open File Panel 讓用戶指定被測試文件。但在測試中發現了一個驚喜:Cocoa 的 NSTextField 支持從 Finder 拖放 (drag-and-drop) 文件到文本框。其中 drop 的過程已經被 Powerbox 接管,Powerbox 會把被拖放的文件加入目標程序的 sandbox 範圍然後繼續原來的 NSTextField 行爲(顯示文件路徑)。整個過程等同於用 Open File Panel 打開文件,因此「Open Panel」的 checkbox 沒有采用。

Test 按鈕的行爲由下面的代碼實現:

首先只關心 24-30 行。如果文本框的內容是通過拖放從 Finder 中得到(也就是 Powerbox 已經擴展了 sandbox 範圍),那麼 console 的輸出爲「Data is available: YES」。如果是鍵盤輸入,那麼輸出結果爲「Data is available: NO」。

接下來關注剛纔忽略的忽略 16-22 行與 32-25 行。這是爲了驗證一個在討論中困惑了我和同事很久的問題:一個進程的 sandbox 範圍在剛剛啓動時總是隻包括 container 目錄,其後每次擴大範圍都要釋放「噪音」,這讓某些操作非常不便。假設用戶在上一次程序運行 session 中通過 Powerbox 打開過一個文件,那麼在下一 session 中是否可以在某些情況下認爲這個文件是無需用戶確認即可被訪問的?如果按照基本 sandbox 機制,就不可能再無「噪音」的實現目前大多數程序提供的 Open Recent 菜單。特別是 OS X Lion 提出了 automatic termination 和 resume 的概念,程序對用戶呈現的運行狀態和進程 session 不再一一對應,這就需要 sandbox 機制允許在同一個程序的先後兩個進程 session 間無「噪音」地繼承部分 sandbox 範圍。

Sandbox 確實提供了這種機制。在 NSWindow 中提供了 setStorable: 和setRestorationClass: 等方法。不過這些屬於特別爲 document-based 多窗口應用設計的高級接口。在驗證程序裏檢驗了更基本的方式。對於一個已在當前 session 的 sandbox 範圍內的文件,調用NSDocumentController 的 noteNewRecentDocumentURL: 方法會把它加入到 recent document 列表中。這個列表由 Powerbox 爲每個 sandbox-ed 應用程序維護。在該應用下次運行時,只要調用 recentDocumentURLs 方法,Powerbox 就會把列表中的文件無「噪音」地加入 sandbox 範圍。用驗證程序檢驗這個行爲的步驟是:

  1. 點中「Add to Recent」checkbox,
  2. 從 Finder 拖放某文件到文本框,
  3. 按 Test 按鈕,
  4. 退出並且重新啓動驗證程序,
  5. 然後手工輸入剛纔的文件路徑,
  6. 按 Test 按鈕,程序會輸出「Data is available: YES」。

Cocoa 實現的界面會自動反映調用這些方法的效果。noteNewRecentDocumentURL: 會向 Open Recent 菜單增加相應的條目。recentDocumentURLs 會在用戶打開 Open Recent 菜單的時候被自動調用(因此在只實現 Open Recent 菜單的時候無需程序員顯式調用該方法)。刪掉 Open Recent 菜單不會影響這些方法對 sandbox 範圍所起的作用。這個方法可以用於非 document-based 的簡單應用。目前看來這是跨 session 繼承 sandbox 範圍的機制中 Cocoa 和 Powerbox 最直接打交道的部分,其它方法或多或少基於此實現。

結論

OS X Lion sandbox 是個人計算安全模型的一個大發展。它是對 DAC 模式的一個易於理解的擴展。通過 kernel,Powerbox 服務和 Cocoa 框架的無縫集成,給開發者引入的負擔和給用戶帶來的干擾比權限隔離 [6] 、動態 ACL、UAC 等方案要小得多。最後,利用 recent document 列表跨進程 session 繼承 sandbox 範圍的機制進一步避免了向用戶發出不必要的噪音。儘管對 recent document 列表中的文件保護有所削弱,但是和提供的功能相比風險代價是合理的。需要普通用戶注意的是,System Preferences 中 Appearance 下的 Number of recent items 也成爲了一個關係到安全風險的選項。

腳註:

  1. 在 OS X 這樣既提供 BSD Unix 實現又加入了任意 ACL 的系統中,狹義的 ACL 特指文件訪問權限屬性 (user, group, others) 以外的擴展部分。此處的 ACL 爲廣義定義,包括 Unix 的文件訪問屬性。
  2. DAC 本身概念的簡單性正是以行爲規範的複雜爲代價的。
  3. 在一個進程 session 中改變帳號是可能的,但是需要發出「噪音」(見下文解釋)。
  4. 有一種「相反的」權限隔離的方案是主進程運行於較低權限,分離的進程運行於較高權限(當然需要釋放「噪音」提權)。這種方案是用於那些需要偶爾修改系統文件的程序,降低系統文件受攻擊的風險。和本文討論的保護用戶可信數據的場景不同。
  5. 當然,除此之外,必不可少的讀取和調用系統庫文件肯定是允許的。
  6. 但在其它適當的場合採用權限隔離仍然是被鼓勵的,只是說權限隔離不再是彌補靜態 ACL 缺陷的必要方法。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章