Suzu macOS Playground

M11 · 階梯 2 · L2

APFS 與檔案系統機制

APFS 容器/volume role、snapshot 與 copy-on-write clone(共享 extent + refcount)、SSV cryptographic seal(Merkle tree authenticated-root)、firmlink 與 /System/Volumes/Data 的卷融合、xattr 與 quarantine/provenance 元資料,以及 FileVault 全卷加密與「macOS Data Protection」相對 iOS 逐檔 class key 的真實邊界與威脅模型。

● 前置:M01 diskutil · ls · xattr · mount_apfs · fdesetup · stat

一個磁碟,一堆「卷」——APFS 容器模型

打開 diskutil apfs list,你會發現 macOS 的「一顆硬碟」不是一個檔案系統,而是一個 APFS 容器(container),裡面塞了好幾個共享同一塊空間的 volume。容器層做空間配置(space sharing),每個 volume 只在需要時才吃實際 block——所以你看不到傳統分割區那種「先切死大小」的浪費。

macOS 的系統安裝是一組有**角色(role)**的卷,組成所謂 volume group:

卷(role)內容特性
System作業系統本體(/)唯讀、被 SSV 封印(見下)
Data使用者資料、第三方 App、可寫狀態可寫,掛在 /System/Volumes/Data
Preboot / Recovery / VM開機前環境、recoveryOS、swap各司其職

你平常看到的「一個 /」其實是 System(唯讀)與 Data(可寫)透過 firmlink 融合出來的單一視圖——這點下面會展開。

⚠ 觀念校正:APFS 是檔案系統結構,不是權限機制。它管的是 extent、snapshot、clone、加密金鑰封裝;權限的准駁(DAC/SIP/Sandbox/TCC)是另一套機制(M05/M06/M07/M09)。本章談「資料怎麼擺、怎麼封印、怎麼加密」,不談「誰能存取」——別把兩者混為一談。

Snapshot 與 clone:copy-on-write 與共享 extent

APFS 的核心是 copy-on-write(CoW)。檔案內容存成一段段 extent(指向實際 block 的範圍),多個檔案/快照可以共享同一個 extent,靠 refcount(參照計數) 記錄「這塊 block 被幾個人指著」。

  • clone(cp -c):複製一個檔案時不真的搬資料,只是讓新檔指向同一批 extent、把 refcount +1。直到其中一方被寫入,才把被改的那段 extent 複製出來(CoW),兩邊從此分家。所以 clone 一個 10 GB 檔幾乎是瞬間、且一開始不佔額外空間。
  • snapshot:對整個 volume 在某時點做一個唯讀凍結視圖。它同樣靠共享 extent + refcount——快照不複製資料,只是「釘住」當時所有 extent 不被回收。之後 volume 繼續寫入時走 CoW,新舊各自指各自的 extent。

這個 refcount 模型解釋了兩件常讓人困惑的事:(1) 為什麼 snapshot「幾乎不佔空間」——因為一開始全是共享;(2) 為什麼刪了大檔卻沒釋放空間——因為某個 snapshot 還釘著那些 extent,refcount 不歸零就不回收。

接 M14 取證:snapshot 的「釘住舊 extent」特性對 DFIR 是金礦——攻擊者刪檔、改 plist 之後,較舊的本機 snapshot 可能仍保有被刪/被改前的版本。M14 的 Time Machine 取證正是建立在這個 CoW snapshot 機制上。

下面把 clonefile 與 copy-on-write 視覺化:clone 後兩檔共享同一批 extent(不佔額外空間),改寫某塊才 COW 出新 extent——snapshot 同理:

APFS clone / COW

檔案 A
共享 extentCOW 私有 extent邏輯大小 6 塊 · 實體佔用 6 塊(共享只算一次)

clonefile 複製=兩檔共享同一批 extent,不佔額外空間;改寫某塊才 copy-on-write 出新 extent(只有那塊變私有)。snapshot 同理:對「當下狀態」釘一份共享參照,之後的改動才分裂——M05 的 SSV seal 就是蓋在這種 snapshot 上。教學抽象,非真實 APFS 結構。

SSV:把整個系統卷「封印」成一棵 Merkle tree

System 卷不只是唯讀,它被 SSV(Signed System Volume)/ authenticated-root 機制做了 cryptographic seal(密碼學封印):

  • 系統卷上每個 block 的雜湊逐層往上聚合,組成一棵 Merkle tree(雜湊樹),最終收斂成一個 root hash(seal),這個 seal 由 Apple 簽署。
  • 開機時驗證這個 seal:磁碟上只要有一個 byte 被竄改,受影響 block 的雜湊就變,往上一路傳導到 root hash 不符 → 系統拒絕以該卷開機。
  • 執行期:讀取系統卷上的 block 時,可按需重算雜湊比對該層 Merkle 節點,所以「離線改一個檔,開機後再用」這條路被封死。

注意 SSV 的「seal」與 M05 教的 SIP/rootless 是兩件不同的事,在 M05 已強調過這個分野:

  • SIP / rootless(執行期 MAC):系統跑著時,MACF policy 擋對 restricted 路徑的修改。
  • SSV / authenticated-root(開機期 seal):開機時用 Merkle root hash 驗整卷封印。

本機 diskutil apfs list 的實測就是活證據:System 卷顯示 Sealed: Yes。即使有人把 SIP 關了,這個 seal 仍可獨立存在(M05 §SSV 的雙層獨立)。

⚠ 威脅模型邊界:SSV 防的是「離線竄改系統卷後拿來開機」與「執行期讀到被改過的系統檔」。它不防「已取得足夠權限、在執行期於 Data 卷或記憶體中動手腳」的攻擊——系統卷唯讀且封印,但你的家目錄、LaunchAgents、可寫狀態都在 Data 卷,那裡沒有 seal。把 SSV 理解成「保證系統本體出廠完整」,不是「保證整台機器沒被入侵」。

firmlink:把唯讀 System 與可寫 Data 縫成一個 /

System 卷唯讀,但 /usr/local、/Users、/private/var 這些路徑明明要可寫。macOS 用 firmlink 解決:firmlink 是一種卷間的固定連結,把 System 卷上的某個路徑「投射」到 Data 卷對應位置。對照表在 /usr/share/firmlinks。

  • firmlink 不是 symlink:symlink 是一個內容為目標路徑字串的特殊檔,使用者可建可改;firmlink 是系統定義、跨卷、開機期建立的融合關係,一般使用者不能隨意產生。
  • 效果:你 cd /Users 落到 Data 卷、cd /System/Library 落到 System 卷,但看起來就是同一棵 /。這也是為什麼 restricted 與可寫區能在同一個路徑樹下共存。

接 M07 Sandbox:理解 firmlink 與卷融合,才看得懂 M07 的 container 重導向——App 以為自己在寫 ~/Library/...,實際被 sandbox 重導向到容器路徑;而這些路徑底層又落在 Data 卷的哪裡,是由 APFS 卷結構決定的。

xattr 與 provenance:附在檔案旁邊的元資料

APFS 檔案除了內容,還能掛 extended attributes(xattr)——鍵值對形式的旁掛元資料。前面模組已經碰過幾個關鍵的:

  • com.apple.quarantine(接 M04):下載來源標記,Gatekeeper 首次執行評估的依據。
  • com.apple.provenance:macOS 較新版本用來記錄「這個 App 是經由哪條核可路徑落地/被允許執行」的來源憑據,與 Gatekeeper/首次啟動信任決策關聯。
  • com.apple.rootless:標記 SIP 保護關係(你在 ls -lO@ /bin 就會看到 restricted 旁掛這個 xattr)。

重點不是背 xattr 名字,而是理解:檔案系統層的元資料是信任決策的輸入之一。M04 的 quarantine、M14 的 kMDItemWhereFroms 取證,本質都在讀這些旁掛資料。攻擊者「洗掉 quarantine」或防禦者「靠 provenance 追來源」,操作對象都是這層 xattr。

FileVault 與「macOS Data Protection」:最常被誤解的威脅模型

這節是本章的勘誤核心,請逐句讀。許多人把 iOS 的資料保護模型直接套到 macOS,這是錯的。

事實一:Apple Silicon「一律」有硬體儲存加密——FileVault 不等於「有沒有加密」

在 Apple Silicon Mac 上,內建儲存出廠就由硬體加密引擎加密,金鑰由 SEP(Secure Enclave) 管理。所以「沒開 FileVault = 資料明文存在磁碟上」這句話是錯的。

那 FileVault 到底加了什麼?關鍵在金鑰怎麼被保護:

  • 卷的內容用 VEK(Volume Encryption Key) 以 XTS-AES 加密。
  • 未開 FileVault:VEK 仍存在(資料確實是密文),但它的解封不綁定使用者密碼——開機到登入畫面前,系統即可取得 VEK 還原資料。機密性此時主要依賴裝置實體安全 + SEP。
  • 開 FileVault:把 VEK 包進一個由使用者密碼派生的 KEK(Key Encryption Key)。沒有使用者密碼(或對應 recovery key),登入前就拿不到 VEK——這才是 FileVault 真正提升的安全屬性。

⚠ 勘誤(必記,DESIGN.md §11 #10):未開 FileVault 不等於「沒加密」。Apple Silicon 一律有硬體儲存加密、SEP 管金鑰;FileVault 的作用是把 VEK 包進以使用者密碼派生的 KEK。未開 FileVault 時,機密性僅依賴裝置實體安全與 SEP,不是「裸奔」。

事實二:macOS 是「全卷加密」,不是 iOS 那種逐檔 class key 全套

iOS 有一套完整的 Data Protection:每個檔案用自己的 per-file key 加密,per-file key 又被綁到不同的 class key(如 NSFileProtectionComplete),class key 隨裝置鎖定/解鎖狀態而可用或被丟棄——這讓 iOS 能做到「鎖屏後連系統服務都讀不到某些檔案」。

macOS 沒有把這套逐檔 class key 機制當成桌面資料的主要保護。macOS 桌面上,使用者資料的機密性主要由 FileVault 這個 volume 級(XTS-AES)全卷加密承擔;per-file class key 在 macOS 桌面多數情況不啟用。(macOS 上確實存在 Data Protection 的 API 表面,但它不是 iOS 那種貫穿全系統、逐檔分級的常態保護。)

⚠ 勘誤(必記,DESIGN.md §11 #9):macOS 沒有 iOS 等級的逐檔 class key 全套。macOS 多數檔案實際只受 FileVault(volume 級 XTS-AES)保護;per-file class key 在 macOS 桌面多數情況不啟用。不要把 iOS 「鎖屏即逐檔不可讀」的心智模型套到 macOS。

事實三:FileVault 的威脅模型——防什麼、不防什麼

這是最容易導致誤判的地方,措辭要精確:

  • FileVault 防的是:靜態資料(data at rest) 在未登入 / 關機 / 離線狀態下的機密性——也就是裝置實體失竊、硬碟被拔出離線讀取這類場景。沒有使用者密碼,登入前的資料受 KEK 保護而無法還原。
  • FileVault 不防的是:已經登入、正在運行的系統上的本機惡意程式。一旦使用者登入、卷被解鎖,資料對作業系統(以及在系統上以足夠權限運行的程式)就是明文可讀。FileVault 不是 anti-malware、不是執行期存取控制——那是 DAC/Sandbox/TCC(M05/M06/M07/M09)的職責。

換句話說:FileVault 對抗的是「拿到你的硬碟」的攻擊者,不是「拿到你已解鎖的 session」的攻擊者。 把這兩個威脅模型分清楚,才不會誤以為「開了 FileVault 惡意程式就讀不到我的文件」——那是錯的。

⚠ 整合視角:靜態加密(FileVault)、開機完整(SSV/Secure Boot,M01/M05)、執行期存取控制(DAC/SIP/Sandbox/TCC,M05–M09)是三個正交的防線,各防各的威脅。把任一個誤當成另一個的替代品,就是威脅模型錯置。

🟢 真機操作卡(在你自己的 Mac 上唯讀觀察,macOS 26 適用)

以下全部是唯讀查詢,不改任何狀態,在自己的機器上安全執行:

# ── APFS 容器 / volume role / snapshot 與封印 ──
diskutil apfs list                          # 容器、各 volume 的 role、Sealed、FileVault、Encrypted 欄位
diskutil apfs listSnapshots /               # 系統卷上的 APFS snapshot(唯讀凍結點)
diskutil info /                             # 看 / 的卷資訊(含是否 sealed / 加密狀態)

# ── firmlink / 卷融合(看唯讀 System 與可寫 Data 怎麼縫起來)──
cat /usr/share/firmlinks                    # System↔Data 的 firmlink 對照表
ls -l@O /                                   # 根目錄各項的 restricted/sunlnk 旗標與 xattr

# ── xattr / quarantine / provenance(檔案系統層的信任元資料)──
xattr -l ~/Downloads/* 2>/dev/null          # 看下載檔的 com.apple.quarantine 等旁掛元資料
ls -lO@ /bin                                 # 看 restricted + com.apple.rootless(接 M05)

# ── FileVault 狀態(只查不改)──
fdesetup status                             # FileVault 是否開啟(On/Off)

⚠ 危險 / 授權邊界:本章一律唯讀。任何改動——mount_apfs 把 snapshot 掛成可寫、刪/建 snapshot、fdesetup 開關 FileVault、停用 authenticated-root/SSV、改 LocalPolicy——都會改變磁碟狀態或降低整機安全,部分需 recoveryOS/owner 認證,屬授權研究/鑑識流程(DESIGN.md §10)。做 DFIR 取證時,從 snapshot 取資料務必唯讀掛載(mount_apfs -s … -o ro …)並保全原證,絕不在原始證據卷上寫入(接 M14)。


下一步:M14 偵測、監控與 DFIR — APFS 的 snapshot / CoW / FSEvents 正是取證 timeline 的素材來源;把本章的「資料怎麼擺、怎麼留痕」接到「攻擊者過閘時系統在哪裡留下訊號」。