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 的真實邊界與威脅模型。
一個磁碟,一堆「卷」——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
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 的素材來源;把本章的「資料怎麼擺、怎麼留痕」接到「攻擊者過閘時系統在哪裡留下訊號」。