Apple Darwin OSとXNUカーネルの詳細分析
(tansanrao.com)- AppleのDarwinは、macOS、iOS、そして現代のApple OSのUnix系基盤であり、XNUはMachとBSDを1つのカーネル内に結合したハイブリッドカーネルである
- XNUはMachのタスク、スレッド、仮想メモリ、ポートベースIPCを維持しつつ、BSDサービスを同じカーネルアドレス空間に置くことで、純粋なマイクロカーネル方式のメッセージ伝達コストを削減する
- NeXTSTEPのMach 2.5+4.3BSD系譜は、1996年のAppleによるNeXT買収後、Mac OS XとDarwinへと受け継がれ、その後FreeBSDコード、I/O Kit、64ビット、ARM、Apple Silicon対応が段階的に加えられた
- macOSとiOSは、サンドボックス、コード署名、SIP、APFS、DriverKit、QoSスケジューリング、Jetsam、圧縮メモリといった機能を、カーネルとユーザー空間の協調によって拡張してきた
- XNUの進化は、カーネルを書き直すというより、Mach/BSD基盤を維持しながら、性能が必要な部分はカーネル内に統合し、隔離が必要な部分はIPCとユーザー空間へ分離する方式に近い
DarwinとXNUの出発点
- Darwinは、macOS、iOS、そしてAppleの現代OSプラットフォームを支えるUnix系の中核オペレーティングシステムである
- 中心には「X is Not Unix」の略であるXNUカーネルがあり、MachマイクロカーネルのコアとBSD Unixの構成要素を結合している
- この構造は、Machのメッセージ伝達ベースの設計とBSDの安定性・POSIX互換性を併用し、モジュール性と性能のバランスを狙うものだ
Mach、NeXTSTEP、Mac OS Xへと続く歴史
- Machは、1985年にCarnegie Mellon UniversityでRichard RashidとAvie Tevanianが率いたプロジェクトとして始まった
- メモリ管理、CPUスケジューリング、IPCのような低レベル機能だけをカーネルに置き、ファイルシステム、ネットワーク、ドライバーはユーザー空間サーバーに置こうとするマイクロカーネル設計だった
- タスク、スレッド、Machポート、copy-on-write、メモリオブジェクトといった概念がカーネルの中核オブジェクトとして定着した
- NeXTSTEPは、1989年にMach 2.5カーネルの上に4.3BSD Unixサブシステムを載せてリリースされた
- NeXTは純粋なマイクロカーネル方式よりも、BSDコードをカーネルアドレス空間に統合する方を選び、性能を優先した
- Objective-CベースのDriverKitも含まれており、その後AppleのXNU系譜へとつながった
- Appleは1996年にNeXTを買収し、NeXTSTEPを新しいMac OS Xの基盤として選択した
- Rhapsodyプロジェクトが始まり、NeXTのMach/BSDハイブリッドカーネルがAppleに入った
- その後XNUは、OSFMK 7.3ベースのMach 3.0系コードと4.4BSD・FreeBSDコードを取り込んだ
DarwinとMac OS Xの初期の発展
- Appleは1999年にMac OS X開発者プレビューを出し、2000年にDarwin 1.0を公開して、XNUカーネルと基本的なUnixユーザー空間を開発者に開いた
- Mac OS X 10.0 Cheetahは、2001年にDarwin 1.3.1ベースで商用リリースされた
- 初期の変更の焦点は、BSD層、ネットワーク、ファイルシステム、スレッディング性能を強化することにあった
- Mac OS X 10.1 Pumaは、スレッド管理性能とリアルタイムスレッド対応を改善した
- Mac OS X 10.2 Jaguarは、IPv6、IPSec、
mDNSResponder、HFS+ジャーナリングを含んだ - Mac OS X 10.3 Pantherは、FreeBSD 5カーネルの改善と細粒度のカーネルロックを統合し、マルチプロセッサ活用を強化した
- Mac OS X 10.4 TigerはUNIX 03認証を受け、FreeBSDの
kqueue/keventを導入し、Intel Mac移行のためのクロスプラットフォーム基盤を維持した
64ビットとiPhone OSがもたらしたモバイル要件
- Mac OS X 10.5 LeopardはDarwin 9ベースで、64ビットカーネル実行、64ビットドライバー、ASLR、サンドボックス、DTraceを導入した
- 2007年の初代iPhone OSもDarwin 9ベースでリリースされ、XNUはARMモバイルデバイスにまで拡張された
- 初期のiPhoneはRAMが限られ、スワップを使えなかったため、メモリ不足時にバックグラウンドアプリを終了するJetsamメカニズムを使用した
- iPhone OSはサードパーティアプリをサンドボックス内で実行し、バイナリに厳格なコード署名を要求した
- Mac OS X 10.6 Snow LeopardはPowerPC対応を終了し、Intel中心の64ビット・マルチコア最適化を強化した
- Grand Central Dispatchと
libdispatchはユーザー空間ライブラリだが、カーネルのスレッドプールとスケジューリング支援を利用する - OpenCLもGPUコンピューティングのために、ユーザーフレームワークとカーネルドライバーの緊密な統合を必要とした
- Grand Central Dispatchと
- iOS 4は、バックグラウンドアプリの優先度区分とマルチコアARM SoC対応に合わせてスケジューラを調整した
現代のmacOSとiOSにおけるカーネル機能の拡張
- OS X 10.9 Mavericksは、圧縮メモリとタイマー合体を追加した
- 圧縮メモリは非アクティブページをRAM内で圧縮し、ディスクスワップを減らす
- タイマー合体はCPUのウェイクアップ時点をそろえ、消費電力を下げる方式である
- OS X 10.11 El CapitanはSystem Integrity Protection、すなわちSIPを導入した
- SIPはBSD層のMandatory Access Controlフレームワークを通じてカーネルが強制し、rootプロセスであっても重要なシステムファイルやプロセスを変更できないようにする
- macOS 10.13 High SierraはAPFSをデフォルトのファイルシステムとして導入した
- XNUのVFS層は、APFSのスナップショット、クローニング、コンテナレベル暗号化をサポートするよう拡張された
- 同じ時期に、サードパーティkextのロードにはユーザー承認が必要になった
- macOS 10.15 Catalinaは現代的なDriverKitを導入した
- DriverKitは多くのドライバーをカーネル外のユーザー空間Driver Extensionへ移した
- カーネルはIPCと共有メモリを通じて、ユーザー空間ドライバーに限定的なハードウェアアクセスを提供する
- Catalinaは読み取り専用システムボリュームも導入し、SIP保護を強化した
Apple Silicon時代のXNU
- 2020年のmacOS 11 Big SurとDarwin 20は、Apple Silicon Macに対応した最初のリリースである
- XNUはiOSを通じてすでにARMに対応していたが、Apple Silicon Macではヘテロジニアスなbig.LITTLE CPU構造まで考慮する必要があった
- スケジューラは、高優先度で重いスレッドを性能コアに、低QoSまたはバックグラウンドスレッドを効率コアに配置できるよう、異種コアを認識する
- QoSクラスはApple Siliconで、コア種別の選択にも影響し得るスケジューリングヒントとして使われる
- Apple Siliconのユニファイドメモリ構造では、カーネルメモリマネージャとGPUドライバーがバッファ共有を管理する
- Mach VMの抽象化は、ユーザー空間とGPUの間でコピーの代わりにVM再マッピングによってメモリオブジェクトを共有するのに適している
- ARM64バックエンドはPointer Authenticationをサポートし、例外フレームとシステムポインタにPACキーを使用してROP攻撃の緩和に寄与する
- XNUはmacOS、iOS、watchOS、tvOS、bridgeOS、visionOSといった複数のAppleプラットフォームの共通基盤であり続けている
XNUのハイブリッドカーネル構造
- XNUのMachとBSD構成要素は1つのカーネルバイナリにリンクされ、同じアドレス空間を共有する
- MachとBSDの間に保護境界はなく、カーネル内部ではIPCメッセージではなく通常の関数呼び出しで相互作用する
read()のようなUnixシステムコールは、別個のBSDサーバーにメッセージを送るのではなく、カーネル内のBSDファイルシステムコードに直接入る
- Machは中核的なカーネルインフラを担う
- タスクとスレッドの生成・終了、コンテキストスイッチ、低レベルのスケジューリング、ロック、タイマー、スケジューリングキューを管理する
- 各BSDプロセスはMachタスクに、各スレッドはMachスレッドに対応する
- Mach VMは仮想アドレスマップ、メモリオブジェクト、copy-on-write、IPCベースのメモリ共有を提供する
- BSDはUnixとしての性格とサービスを提供する
- PID、ユーザーID、シグナル、POSIXスレッド、ファイルシステム、ネットワーク、Unix IPC、デバイスI/O、権限とセキュリティフレームワークを管理する
- VFSはHFS+、APFS、NFSといったファイルシステムを扱い、メモリマップトファイルではMach VMとvnode pagerを通じて接続される
- サンドボックスとSIPは、BSDセキュリティモジュールとMachタスクポート制限の協調によって動作する
- I/O KitはXNUの第3の柱であり、制限されたC++形式で書かれたオブジェクト指向ドライバーフレームワークである
- デバイスとドライバーをクラス階層で表現し、ドライバーはカーネル内でC++オブジェクトとして実行される
- ユーザー空間にはI/O Registryプロパティとuser clientインターフェースを通じて限定的なアクセスを提供する
- 現代macOSのDriverKit以前は、大半のドライバーがカーネル内でkextの形で動作していた
Mach IPCとシステムサービス
- XNUはUnixシステムコール経路にはMachメッセージを使わないが、ユーザー空間サービスやカーネル・プロセス間通信にはMach IPCを広く活用する
- Machポートはさまざまなカーネルオブジェクトのユーザー空間ハンドルとして使われる
- 各タスクにはタスクポートがあり、権限を持つプロセスはそれを通じて別のタスクを検査または制御できる
- イベントや通知もMachメッセージで伝達される
- WindowServerはユーザー入力イベントをカーネルからMachメッセージとして受け取る
- Grand Central Dispatchは内部的にMachポートを使い、イベント待ちスレッドをスリープさせる
kqueue/keventはMachポートメッセージとファイルディスクリプタを同時に待機できる
- AppleのXPCフレームワークはMachメッセージの上に構築されている
- XPC接続は内部的にMachポートに基づく
- Machポートの権限モデルは、Keychainの
securitydのようなサービスで呼び出し元権限の確認に使われる - Machメッセージはout-of-lineメモリとポート権限を渡せるため、高レベルRPCの構成に使われる
- MIG、すなわちMach Interface Generatorは、カーネルとユーザー空間の間のインターフェース定義とメッセージ送受信コードを生成するために使われる
スケジューラとスレッド管理
- XNUスケジューラはMachの優先度ベースのラウンドロビンスケジューラから出発したが、デスクトップとモバイルの要件に合わせて大きく修正された
- Machは歴史的に0〜127の範囲のスレッド優先度を定義しており、XNUは
sched_priやbase_priのような値を使用する- タイムシェアリングスレッドは使用量に応じて優先度が変化し得る
- リアルタイムスレッドは固定優先度を使用する
- XNUはCPUごとの実行キューとスケジューラインタラプトで、効率とロードバランシングを扱う
- iOSのアプリサンドボックスとバックグラウンド実行は、作業ロールまたは優先度グループの概念をスケジューラに反映する
- QoSクラスはiOS 8とOS X 10.10以降、スケジューリングに統合された
- user-interactive、user-initiated、default、utility、backgroundといったクラスが優先度帯とスケジューリングに影響する
- Grand Central DispatchやNSThreadで作られたスレッドはQoSを継承する
- Apple Siliconでは、バックグラウンドQoSスレッドが効率コアに配置されることがある
- リアルタイムオーディオと重要作業のために、リアルタイムキューとデッドラインベースのスケジューリングもサポートする
メモリ管理とMach VM
- XNUのメモリ管理はMach VMサブシステムが中心である
- 各Machタスクは、VM mapとVM regionで表現される仮想アドレス空間を持つ
fork()はメモリ全体を即座にコピーせず、copy-on-writeを使用する- 親と子は書き込みまで同じページを共有する
- Machはメモリオブジェクトとpagerの概念を使用する
- 匿名メモリのデフォルトpagerはユーザー空間の
dynamic_pagerデーモンが担当し、必要に応じてスワップファイルを管理する - ファイルメモリは、カーネル内のBSD層にあるvnode pagerがファイルシステムコードと相互作用する
- 匿名メモリのデフォルトpagerはユーザー空間の
- Mavericksの圧縮メモリは、カーネル内にcompression pagerを追加して実装された
- メモリプレッシャーが高いとき、非アクティブページをすぐディスクへ送らず、RAM内のcompressor poolに圧縮保存する
- 圧縮で足りない場合はディスクスワップを使用する
- 物理メモリ管理はアーキテクチャ依存層であるpmapが担当する
- pmapはページテーブル、またはそのアーキテクチャに対応する構造を管理する
- ARM64ではセキュリティ機能とキャッシュ関連の問題もpmapに結びつく
dyldの共有キャッシュは、複数のプロセスに同じ物理ページを読み取り専用でマップして効率的に使用する
仮想化サポート
- Intel Macでは、OS X 10.10からHypervisor.frameworkが提供され、ユーザー空間仮想化をサポートした
- Intel VT-xを使用して、ユーザー空間プロセスが仮想マシンモニターのように動作できるようにする
xhyveのようなツールや一部の仮想化アプリがこの機能を活用する
- Apple Siliconでは、macOS 11のVirtualization.frameworkがARM64向けカーネル内ハイパーバイザーの上で動作する
- 開発者はユーザー空間でLinuxまたはmacOS VMを実行できる
- 任意のサードパーティハイパーバイザーをカーネル内に許可するのではなく、Appleのフレームワークと権限を通じてアクセスする方式を使う
- カーネルの観点から見たハイパーバイザー機能には、ゲスト物理メモリ管理、機密命令のtrap-and-emulate、vCPUインターフェースの公開が含まれる
- Machスケジューラはホスト側から見てスレッドであるvCPUをスケジューリングし、メモリサブシステムはゲストメモリのマッピングに使われる
- iOSでも特定の条件と権限の下で仮想化機能が可能であり、脱獄されたA14デバイスでハイパーバイザーを有効化してLinux VMを実行した事例がある
Secure EnclaveとExclaves
- macOSは機密性の高い作業とデータを保護するために、Secure Enclaveとexclavesという2つの隔離メカニズムを使用する
- Secure EnclaveはApple SoCに統合された専用のハードニング済みサブシステムである
- iPhone、iPad、T2またはApple Silicon Macなどに存在する
- 独自のマイクロカーネルベースのオペレーティングシステムを実行し、暗号鍵や生体情報のような機密情報を管理する
- メインのアプリケーションプロセッサやカーネルが侵害されても、重要なデータを分離する目的を持つ
- ExclavesはmacOS 14.4とiOS 17で登場した、より新しいセキュリティ構造である
- 機密作業をメインXNUカーネルと同じ権限ドメイン内に置く代わりに、一部の重要リソースを別個の「externally located」ドメインへ分離する
- Apple IDサービス、オーディオバッファ、センサーデータ、インジケーター管理コンポーネントのようなリソースが対象である
- ExclaveKextClient.kext、ExclaveSEPManagerProxy.kext、ExclavesAudioKext.kextのような特殊なkextとprivate frameworkが管理に関与する
- この分離は、メインカーネルが侵害されてもexclave内の作業を絶縁し、追加の防御層を提供する
長期的な設計方針
- DarwinとXNUは、完全なマイクロカーネルでも完全なモノリシックカーネルでもない混合設計である
- Machベースのコアは新しいアーキテクチャとシステム機能に適応する助けとなり、BSD層はPOSIX互換環境とUnixツール・APIを提供した
- AppleはPowerPCからIntel、ARMへのCPU移行と、iPhone、Apple Watch、Apple Vision Proのような新しいデバイスカテゴリをXNUベースで受け入れた
- カーネルの変化は主に3つの方式で進む
- 新機能は既存カーネルの上に拡張する
- 性能が重要な構成要素はカーネル内に統合する
- 隔離が必要な構成要素はMach IPCとユーザー空間を通じて分離する
- Darwinの公開ソースリリースは、商用ハイブリッドカーネルを研究者が覗ける窓を提供するが、公開範囲には制限がある
1件のコメント
Hacker Newsのコメント
Machの仮想メモリシステムは4.4BSDやFreeBSDだけでなく、NetBSD[0]、OpenBSD[1]にも入ったが、DragonFly BSD[2]には入っていないようだ
[0] https://netbsd.org/docs/kernel/uvm.html
[1] https://man.openbsd.org/OpenBSD-3.0/uvm.9
[2] https://www.dragonflybsd.org/mailarchive/kernel/2011-04/msg0...
FreeBSD 4の時点ではカーネルのコードベースに元のMachコードは残っておらず、これは1990年代後半にはすでに完了していたので、FreeBSDをMachと結び付けられるのはごく初期の分岐元・基盤という段階くらいだ
NetBSDとOpenBSDもしばらくは続けていたが、Mach設計の性能、SMP/スケーラビリティ、ネットワーキングの限界に突き当たり、Chuck Cranorが設計・主導したUVM(Unified Virtual Memory)として全面的に書き直され、OpenBSDは後にこの実装を取り込んで現在も使っている
現存するBSD群[1]の中でMachを使い続けているのはXNU/Darwinだけで、しかもMach 2.5ではなくMach 3だ。Mach 2.5、3、4(GNU/HurdはMach 4)があったが、互換性は低く、主にシステム全体のアーキテクチャレベルの影響を共有する程度なので、共通の影響を受けた別個の設計と見るほうがよい
[0] そもそもその痕跡自体も多くはなかった
[1] DragonBSDが今も生きているのか死んでいるのかもよく分からない
Darwinは中核コンポーネントが急進的に変わる速さが興味深い。システムコールの下位互換性の放棄、必須のコード署名、動的実行ファイルのロード速度を上げるために個別のシステムライブラリファイルをなくしたdyld_shared_cacheまで、ノスタルジーや聖域なしに結果重視で設計している
Appleのような大手ハードウェア企業にしかできないアプローチに見える
[1] https://www.theregister.com/2025/03/08/kernel_sanders_apple_...
記事では、スワップファイルを管理するpagerデーモンがユーザー空間で動き、カーネルメモリもスワップできるとしていたが、ユーザー空間デーモンがカーネルメモリをどうスワップするのかは説明していない
特殊なデーモンに対するハードコードされた例外があるのか、特殊なシステムコールを使うのかが気になる。ユーザー空間メモリ管理の具体的な内容はどこでさらに見られるだろうか?
https://web.mit.edu/darwin/src/modules/xnu/osfmk/man/memory_...
ただしDarwinがこの機能を実際に使ったことがあるかは定かではなく、少なくともここ20年ほどは使っていない。dynamic_pagerはこのインターフェースを使ったことはなく、XNUがスワップ不足を通知するとスワップファイルを作成し、
macx_swapon、macx_swapoffシステムコールでカーネルに渡すという、はるかに限定的なMachインターフェースを使っていた。実際のスワッピングはカーネルが行っており、昔のdynamic_pagerのコードはここにある:https://github.com/apple-oss-distributions/system_cmds/blob/...
その機能も今ではカーネル側に取り込まれ、現在のdynamic_pagerは実質的にほとんど何もしない:
https://github.com/apple-oss-distributions/system_cmds/blob/...
カーネルメモリの大半は固定(wired)されていてページングできないが、カーネルが
IOMallocPageableのような方法で明示的にページング可能なメモリを要求することはでき、そのメモリはディスクにスワップ可能だ。ただしほとんど使われず、この種のコードはデッドロックを避けるよう注意する必要がある。ユーザー空間がもはや「ページング」自体に関与しなくなっていても、FSKitやFUSEベースのユーザー空間ファイルシステム、ディスクイメージ上のファイルシステム、ユーザー空間ネットワーキング拡張を経由するNFS/SMBのように、一段か二段下の層でユーザー空間が介在することは珍しくない。ただし最後の部分は間違っているかもしれない。ユーザー空間で詰まるファイルシステムは確かにあり得るが、そのようなファイルシステム上にスワップを置くことはサポートされていない可能性があるDarwinカーネルの話を見るたびに、Appleが単に Linuxをフォーク してその上にOSサービスを載せていたらどれほど違っていたのか気になる
特にAppleがDarwinにどれほど執着しているかを見ると、オープンソースが失ったものとAppleが投じなければならない時間・費用に対する見返りのバランスが悪く見えて、あまり良い印象を受けない
1990年代後半のLinuxも明らかにより良い選択肢ではなく、OS Xが数バージョンを経てコンシューマPCで最も成功したUNIX系OSとして定着した後では、Linuxベースへ切り替えるのは短期的な利益がほとんどなく、コストとリスクだけが大きい選択だった
AppleがクラシックMacOSをあと5年引っ張っていたか、Linuxが5年早く成熟していたなら、OS Xへの移行は大きく違っていたかもしれない。しかし2.6以前のLinuxカーネルのためにXNUを捨てるのは理にかなわなかった
現在の基準で見れば、FreeBSD はDarwinの利点とLinux的なオープンソースの性格をかなり併せ持っている。Appleへの依存がますます強まることなく、より安全な環境を求めるなら、FreeBSDやほかのBSDも配布先として検討に値する
https://en.m.wikipedia.org/wiki/MkLinux
Macintosh GUIとアプリケーションのエコシステムをLinuxへ持ち込む作業は行われなかったようだ。ただしNeXT買収前からAppleは、68k Mac向けのA/UXや、その後のSolarisおよびHP-UX向けMacintosh Application Environmentを通じて、Unix上でMacintosh環境を動かしており、後者はMac OSをUnixプロセスとして実行していた。記憶が正しければ、Macintosh Application Environmentの作業がRhapsodyのBlue Box、のちのMac OS X Classic環境の基盤になった。理論上はMacintosh Application EnvironmentをMkLinuxへ移植することも想像できる。1996年にはBSD関連訴訟の和解後だったため、現代的な自由オープンソースBSDもすでに存在していた
もちろん1990年代半ばに、クラシックMac OSをLinux、FreeBSD、BeOS、Windows NTのような現代的OS上のプロセスとして実行するのは、コンシューマ向けデスクトップ戦略としては現実的ではなかった。ワークステーション級のリソースが必要だったうえ、Appleはなお68k Macをサポートしており、Mac OS 8も一部の68030/68040機で動いていた。G3/G4時代ならもっと現実的で、2000年代には各クラシックMacintoshプログラムを現代的OS上の別個のMac OSプロセスとして動かすことも可能だっただろうが、Jobsの復帰がなければAppleは1998年を越えられなかっただろう。しかもNeXT買収によって、Cocoa、IOKit、Quartz(Display PostScriptの後継)、そのほか現在の中核技術もMacにもたらされた
別の見方をすると、AppleはSafariをChromiumの上へ移行すべきだという提案に似ていると感じる
Linuxをフォークしていたなら、法的にすべてのカーネルモジュールをオープンソースとして公開しなければならなかった可能性がある。人類全体にとっては前向きだったかもしれないが、Appleが望む方向ではなかっただろう
この記事には愛情と膨大な作業量が注ぎ込まれている。この歴史の大半を実際に体験し、NeXTSTEPのコードをWindowsへ移植してみたことがあり、GNUStepの再現の試みを掘り下げ、YellowBoxとOpenStepを覚えていて、内部構造の本を読み、WWDCのコンテンツを継続的に見てきた立場からすると、複数のシステムがどのように進化したかについての自分の記憶とほぼ完全に一致している
JobsはTorvaldsをMac OS Xの作業に引き入れようとしたが、Linusは断った: https://www.macrumors.com/2012/03/22/steve-jobs-tried-to-hir...
I/O KitがこのC++サブセットで書かれたのが単に速度のためだけなのかはよく分からない。当時は議論があった。AppleがMacOS Xを発表した際、既存ソフトウェアとは互換性がなく、すべてのパートナーがObjective-Cで書き直さなければならないとしたからだ
反応が良くなかったため、Appleは方針を後退させ、C++アプリケーション向けのAPI層である Carbon と、Objective-CベースのFoundationの土台となる Core Foundation を導入した。Obj-C++が存在する理由もここにある。興味深いのは、メモリ管理をトールフリーブリッジにした点だ。つまりC/C++の世界で割り当てられたオブジェクトを、追加のオーバーヘッドなしにObj-Cへ渡せる
AppleはXNUの周辺に、より良い 自由オープンソースコミュニティ を育てるべきだった。ARMへ移行した今でも、x64で動作可能なディストリビューションが存在すべきだった
Darwinをこれほど深く理解したいと思っていたので、良い記事だった
この記事の最後でも引用されているね。macOSの歴史の中で長く残る資料だ
基盤となるNTカーネルはPOSIX準拠を許容できるほど柔軟なので、こうした内容を扱えば興味深い記事になるはず
歴史の整理としては良いが、AppleのオペレーティングシステムをLinuxやWindowsと分ける優れたセキュリティ上の取り組みがかなり省かれている。Appleが現在セキュリティ面でどれほど先行しているかは、十分に評価されていないように思う。いずれこの認識が広がって、機密性の高い環境で働く人々にはCISOがMacの使用を求めるようになるかもしれない
核心はコード署名システムである。これによってアプリに権限を与えたり、サンドボックスに閉じ込めたりでき、その強制が実際に維持される。Appleは大半のUNIXのようにELFを使わず、Mach-Oという形式を使う。ELFとMach-Oの違いの大半は重要ではないが、Mach-Oが署名済みコードディレクトリを格納する追加セクションをサポートしている点は重要だ。コードディレクトリはコードページ群のハッシュを持ち、カーネルはこのデータ構造をある程度理解しており、dyldはバイナリやライブラリがロードされるときにこれを関連付けられる。XNUはコードディレクトリ署名を確認し、VMMサブシステムはコードページが必要時にロードされる際にハッシュを計算し、ディレクトリ内の署名済みハッシュと一致するか検証する。そのためコードディレクトリハッシュは、Appleエコシステム内のどのプログラムにとっても一意な識別子のように振る舞える。ここには1つバグがあり、この関連付けがMach vnode構造にぶら下がっているため、署名済みバイナリを上書きしてから実行すると、新しいファイル署名が有効でもカーネルが怒ってプロセスを殺す。新しい状態を認識させるには、ファイル全体を実際に置き換えなければならない
この基盤の上に、Appleはコード要件を載せている。これはコード署名のさまざまな属性に対する制約を表現する小さな式言語で書かれたプログラムだ。たとえば「このバイナリはAppleが署名していなければならない」「認証局Yの基準で主体Xが署名した任意のバージョンのバイナリを許可する」「このバイナリはcdhash Zを持たなければならない」、つまり正確にそのバイナリでなければならない、といった要件を書ける。バイナリは、ほかの主体に対して自分がどの要件で識別されたいかを示す指定要件も公開できる。最初はやりすぎに見えるが、プログラムが進化しても安定した偽造不能なアイデンティティを保てるようにしてくれる
カーネルはタスクの署名アイデンティティをポート経由で他のタスクに公開する。ユーザー空間ライブラリが制約言語を解釈し、そのポートに要件を課せる。たとえば、あるプログラムがシステムキーチェーンに鍵を保存すると、ユーザー空間で実装されたkeychainデーモンはRPCを送ってきたプログラムの指定要件を検査し、その後の鍵使用要求と一致するか確認する
このシステムは権利付与(entitlements)として抽象化されている。権利付与は許可を表すkey=valueの組だ。オープンなシステムなので、アプリが独自の権利付与を定義することもできるが、大半はAppleが定義する。いくつかは純粋に任意選択型で、要求するだけでOSが自動かつ無言で許可する。最初は役に立たなそうに見えるが、App Storeがアプリの機能を事前に説明できるようにし、より一般的には、アプリが不要なものにアクセスできないようにする最小権限の姿勢を可能にする。いくつかはプロビジョニングプロファイルのような追加証拠を必要とする。これはAppleが提供する署名済みCMSデータ構造で、おおむね「指定要件Xを持つアプリは制限付き権利付与Yを使ってよい」という意味なので、これを使うにはAppleの許可を得る必要がある。また、いくつかは実質的に汎用署名フラグシステムのように乱用されており、セキュリティとは無関係だ
この仕組みは、ユーザー空間とXNUの協調によってさらに拡張される。バイナリに署名できることは出発点にすぎず、多くのプログラムにはデータファイルもある。ここでAppleのセキュリティシステムはやや継ぎはぎに見える。カーネルはデータファイルの整合性検査には関与しない。代わりに、やや恣意的なバンドルディレクトリ構造の特別な場所にplistが置かれ、そのplistにはバンドル内のすべてのデータファイルのハッシュがファイル単位で入っており、plistのハッシュはコード署名に含まれ、最終的にGatekeeperが初回実行時に全体を検査する。カーネルはGatekeeperにプログラム実行を許可するか尋ね、Gatekeeperはファイルに付いた、Webブラウザや展開ツールのようなGUIツールが伝播させる拡張属性の存在に基づいて判断する。Finderのようなユーザー空間のOSコードは、プログラムが最初にダウンロードされたときGatekeeperを呼び出して確認させ、Gatekeeperはバンドル内の全ファイルをハッシュ化して、バイナリに署名された内容と一致するか検査する。だからmacOSでは初回起動時に遅い「Verifying app」ダイアログが出る。これはmmapを使わず大きなデータファイルを開くアプリが止まらないようにする方式に見えるが、高速ネットワークでは最適化されていないGatekeeper検証のほうがダウンロード自体より遅いことがあり、惜しい。Appleはストア外配布をレガシー技術と見なしているため、あまり気にしていないようだ
最後にSeatbeltがある。サンドボックス規則を表現するLispベースのプログラミング言語だ。これらのファイルはユーザー空間である種のバイトコードにコンパイルされ、カーネルが評価する。言語はかなり洗練されており、すべてコード署名アイデンティティを基盤として、さまざまなシステム構成要素がどう相互作用し、何ができるかを任意の規則で表現できる
この仕組みには、最近のリリースでようやく塞がれた明白な穴があった。データファイルにはコードを含められるのに、一度しか検査されないという点だ。ElectronやJVMアプリは可搬形式でコードを含むので、実際そうなっていた。そのため、あるアプリがデータファイルを修正して別のアプリにコードを注入し、コード署名を回避できた。最新のmacOSでは、これを防ぐためSeatbeltが実行中のすべてのアプリをサンドボックス化している。私の知る限り、現代のmacOSにはサンドボックス外のコードはない。サンドボックスポリシーの1つは、アプリが許可なしに別アプリのデータファイルを変更できないようにすることだ。ポリシーはかなり洗練されていて、Appleが確認した同一の法的主体が署名したアプリ同士なら相互に変更でき、アプリがコード要件に合致する別アプリからの変更を許可することもでき、必要なときにはユーザーが権限を与えることもできる。これを確認するには、Settings -> Privacy & Security -> App ManagementでTerminal.appの権限をオフにして再起動した後、
vim /Applications/Google Chrome.app/Contents/Info.plistのようなコマンドを実行すればよい。ファイル権限はrwなのに、vimは読み取り専用として見るここから先はAppleで働いていないので、私の理解もここまでだ。カーネルはアプリバンドルを理解していないと認識しており、
open()システムコールをどうやって読み取り専用に変えるかを決めているのかも確信がない。推測では、デフォルトのSeatbeltポリシーがカーネルに対し、バンドル形式とSQLite権限データベースを読めるセキュリティデーモンへのupcallを行わせ、そのデーモンが開こうとしている側の指定要件を、バンドルとサンドボックスが表現したポリシーと比較して判断しているのだと思うそうした機能にセキュリティという名前が適切だとは思わない
私の考えでは、セキュリティは常にコンピュータの所有者または利用者の安全を指すべきだ
Appleのこの種の機能はセキュリティ向上に使えるかもしれないが、主な設計目的は、コンピュータを販売したベンダーが、本来はすでに自分のものではないはずの機器を理論上の所有者がどう使うかを、より強く統制することにある。つまり、エンドユーザーがどのプログラムを実行するかをAppleが決められるようにする方向だ