ローダブル・カーネル・モジュール

ローダブル・カーネル・モジュールは、オペレーティングシステム  の動作中のカーネルを拡張するコードを含むオブジェクトファイル。現行Unix系システムの多くやMicrosoft Windowsはローダブル・カーネル・モジュールをサポートしているが、呼称はそれぞれ異なる。例えばmacOSでは"kernel extension"  と呼ぶ。他にもカーネル・ローダブル・モジュール  やカーネルモジュール  とも呼ばれる。LKMは一般にCPUやM/Bなど新しいハードウェアやファイルシステムのサポート、通信プロトコル、システムコールを追加するために利用される。LKMの提供する機能が不要になれば、アンロードしメモリを解放することができる。

1. 解説
-----

ローダブル・カーネル・モジュールが存在しない場合、カーネルに必要と思われる機能を全て最初からコンパイルし静的リンクしておかねばならない。その機能の多くは実際には使用されていないにもかかわらずメモリに常駐し、新たな機能が必要になるとカーネル全体の再ビルドと再起動が必須となる。ローダブル・カーネル・モジュールをサポートするOSの多くは、多くの機能をサポートするモジュール群を備えている。

2. オペレーティングシステム
---------------

2.1. Linux
----------

Linuxにおけるモジュールはシステムレベルの機能を実行するルーチンの集まりである。それらは特定のデバイス利用のためのデバイスドライバやファイルシステム、通信プロトコルのためのシステムルーチンを含む、カーネルにリンクされていないオブジェクトファイルであり、システムを再起動せずそれらを利用可能にする。場合によっては、実際にデバイスを利用する場合、MAKEDEVを使用して静的にデバイスファイルを作成する、もしくはudevを利用してホットプラグを行い動的にデバイスファイルを生成する必要がある。

2.1.1. 概要
---------

Linuxのモジュール群は/lib/modulesディレクトリにバージョン毎に置かれ、バージョン2.6以降は拡張子 .ko が付されている。モジュールはカーネルに既に含まれている未解決シンボルを持ち、再配置可能なELFオブジェクトである。これはカーネルを一種の共有ライブラリと見なした場合の動的リンクされたライブラリといえるかもしれないが、GNU Cライブラリに動的リンクされた実行ファイルをロードする際に使用するld-linux.soなどといったローダを使用するのではなく、modutilsパッケージに含まれるinsmodコマンドを用いてカーネル空間にロードする。insmodコマンドを起動しモジュールをロードすると依存関係の解決など様々なチェックに通され、問題なければカーネル内に存在するシンボルの解決を行う。バージョン2.6以前はシンボル解決までの複雑な処理はユーザー空間のコマンドであるinsmodが処理していたが、パフォーマンス低下の問題からバージョン2.6からはinsmodはモジュールロードに使用するメモリアドレスにモジュールを単にコピーするだけの簡単なコマンドになっており、シンボル解決も含めた残りの処理はすべてカーネル空間で処理している。これはFreeBSDオペレーティングシステムにおける"Dynamic Kernel Linker"と類似している。モジュールのアンロードはロードの逆処理となるが、通常はモジュールの依存関係をチェックし、依存しているモジュールを検出した場合エラーを返しアンロードしない。そのような場合は全ての被依存モジュールを先にアンロードする、または特定のオプションを指定し依存しているモジュールも同時にアンロードする。

カーネルに既にロードされているモジュールはlsmodコマンドを用いて一覧表示できる。

2.1.2. モジュールのビルド
----------------

Linuxカーネルのモジュールは後述する互換性の問題から、カーネルがリリースされる度にリビルドする必要がある。モジュールのビルドはカーネルソースコードに含まれるデバイスドライバに対するものとカーネルソースコードに含まれない外部のカーネルモジュールに対するものの2通りあるが、いずれもカーネルビルドのインフラを利用している。 通常、カーネルソースコードに含まれる一部のデバイスドライバはカーネルコンフィグレーションにてカーネルに直接静的リンクするか、もしくはモジュールとして構築するかのいずれかを選ぶことができる。モジュールとして構築することを選択した場合、カーネルソースのMakefileにはmodulesというターゲットが存在し、予めこのディレクトリでカーネルを作成した後、このmodulesターゲットを実行する事でモジュールのビルドと依存関係の解決用ファイルが生成される。そして、make installを実行すると、システムにカーネルとモジュールを同時にインストールする。

公式のカーネルソースはメインライン  カーネル、もしくはメインストリーム  カーネルと呼ばれるが、目下開発中で未だメインラインに採用されていないデバイスドライバ、GPLと非互換なライセンスで配布されているためメインラインにマージされないモジュール、または自由な改変が全く許されないプロプライエタリなモジュールをビルドするには、カーネルビルドのインフラを間接的に利用する。したがって外部のカーネルモジュールのビルドのためには、カーネルのビルドが正常に終了した完全なカーネルソースコードのコピーがあればよい。もしくはその中から一部のビルドツールやヘッダファイルを抽出したものを用意すればよい。前者はユーザーが独自にカーネルをビルドすることで得られる。後者は、ユーザーが独自に用意することもできるが、多くのLinuxディストリビューションでは、この様な機能を持つパッケージを提供している場合がある。またディストリビューションにより、この低レベルビルドインフラをよりユーザーフレンドリーなツールを介してビルドできるパッケージ

も用意されている場合がある。

2.1.3. ライセンス問題
--------------

Linuxカーネルコミュニティによれば、LKMはカーネルの二次的著作物である。Linuxカーネル開発者はプロプライエタリなモジュールの配布を許容しているが、一部のカーネルシンボルはGPLのモジュールにのみ利用を許している。

2.1.3.1. 汚染問題
-------------

プロプライエタリまたはGPL非互換な組み合わせでライセンスされているLKMをロードするとプロプライエタリモジュールがロードされたことを示すTaintedフラグが立つ。そのようなLKMを使ってバグが発生した場合、Linuxカーネル開発者は完全な原因を解明できない可能性が高いためユーザに通知するのである。LKMは事実上、動作中のカーネルの一部になる。したがってカーネルのデータ構造を破壊したり、バグを発生させたりする可能性があるが、プロプライエタリである限りその原因を調査できない。

このため2001年にリリースされた、バージョン2.4.10より、モジュール毎にMODULE_LICENSEマクロというモジュールが従うライセンス名を埋め込み、それが"GPL"などでない場合、LKMをロードして組み込む際にTaintedメッセージを表示するようになった。

MODULE_LICENSEマクロには、例えばGPLならば、MODULE_LICENSE;、BSDライセンスならばMODULE_LICENSE;、GPLとBSDLのデュアルライセンスならばMODULE_LICENSE;という文字列を埋め込む。必要ならばカーネルソースコードに含まれるinclude/linux/module.hを見てほしい。

2.1.3.2. カーネルシンボルのエクスポート
------------------------

概要で述べた通り、通常カーネル内にある関数名、構造体名などのシンボルはカーネルの外からは直接見えない。LKMのロードにはシンボル解決が必要であるが、シンボル解決するためにはinsmod等のモジュールローダを起動し、様々なチェックに通る必要がある。この時前述の通り、MODULE_LICENSEマクロにある文字列がチェックされる。シンボル名は通常、EXPORT_SYMBOLまたは、GPLにライセンスされたモジュールのみが参照可能なEXPORT_SYMBOL_GPLマクロに記述する。このマクロに記述されたシンボル名はモジュールローディング時にモジュールから参照できるようになるが、あらゆるライセンス用に提供されるEXPORT_SYMBOLとは異なり、EXPORT_SYMBOL_GPLマクロはプロプライエタリなモジュールからは一切参照できない。すなわちプロプライエタリなモジュールを作成する場合はEXPORT_SYMBOL_GPLマクロでエクスポートされている関数を利用できないことになる。プロプライエタリなモジュールを作成する企業にとっては、これは前述の汚染フラグとは比べものにならないほど厳しい制限に思える。しかし、Linuxカーネル開発者にとってはカーネル内の重要な処理に関する決定のイニシアティブを一貫してLinuxカーネルコミュニティが持つことができるという大きな利点がある。プロプライエタリなドライバはソースコードがない、もしくは自由な改変を許可していないものが多く、状況によりカーネルのAPIをはじめとして最新のカーネル開発状況に追随できていないものが存在する。そのようなドライバによるバグやシステムクラッシュが発生したとしてもソースコードを改変できないため、Linuxカーネルコミュニティ側では全く解決できない。これは昔からコミュニティの悩みの種となっているため、カーネル内の極めて重要なオブジェクトや、問題がひとたび発生するとシステムがクラッシュするような処理を最初からバイナリオンリードライバには実行できないようにしようとしたのである。

2.1.3.3. Linuxantについての論争
------------------------

LinuxantはプロプライエタリなLKMを販売しているコンサルティング企業であるが、同社がMODULE_LICENSEマクロに与えたライセンス文字列にヌル文字を忍び込ませることによりライセンスによる制限を突破していたことが2004年コミュニティにより発覚した。以下は当該モジュールにて実装されていたライセンス定義箇所の再現。

ライセンス文字列からヌル文字を除去して和訳すると「"GPL" というディレクトリには GPL を適用したファイルがあり、他には LICENSE ファイルが適用される」となる。

当時のカーネルはモジュールのライセンスをC言語で一般的な、ヌル文字で終端した文字列として記述しており、モジュールのライセンスがGPLを含むか否かの判別処理に用いていた関数strcmpはヌル文字に達したところで比較をやめるため、モジュールのライセンスがGPLだと誤って判断してしまった。しかし、その後の文に書いてあるGPLディレクトリは空であり、GPLにて配布されていた箇所は全くなかった。

Linuxカーネル、バージョン6.6現在、上述のモジュールライセンス判定処理や、ライセンス文字列をヌル文字に依存しない形式に変更するなどの修正はされていない。

2.2. FreeBSD
------------

FreeBSDのカーネルモジュールは、OS・カーネルと同時に頒布されるモジュールの場合/boot/kernel/ディレクトリ以下に存在し、FreeBSD PortsまたはFreeBSD Packagesからインストールした場合や、プロプライエタリはたまたバイナリ・オンリーモジュールの場合は通常、/boot/modules/ディレクトリ以下に存在する。FreeBSDのカーネルモジュールは通常拡張子.koを持つ。マシンを一旦ブートするとkldloadコマンドを利用しモジュールはロードされる。アンロードはkldunloadコマンドを利用する。カーネルにロード中のモジュールをリストアップするには、kldstatコマンドを利用する。またモジュールはカーネルロード前のブート時点でもロード可能であり、設定ファイル/boot/loader.confを利用し自動的にロードするかまたは手動でロードを行う。

2.3. macOS
----------

macOS用のいくつかのローダブル・カーネル・モジュールは自動的にロードすることができる。またLKMはkextloadコマンドを用いて個別にロードできる。いずれのLKMもkextstatコマンドを用いることでロード可能なLKMを一覧表示できる。LKMは拡張子.kextをもつアプリケーションバンドル内に存在する。オペレーティングシステムにより提供されるLKMは、/System/Library/Extensionsディレクトリ以下、サードパーティー製の場合は他様々なディレクトリに保存される。

3. フラグメンテーションによるペナルティ
---------------------

カーネルのモジュール化についてのもっと細かい批判として、いわゆるフラグメンテーションによるペナルティがある。カーネル本体はメモリ内の連続領域に展開され、断片化されることはない。しかし、LKMのロード/アンロードを繰り返すと、カーネルコードのフラグメンテーションが発生する。したがって、若干の性能低下が起きる。

4. バイナリ互換性
----------

LinuxがLKMに提供しているAPIやABIは安定したものではない。すなわち、カーネルのバージョンが異なれば内部のデータ構造や機能に差異があり、非互換問題を発生する可能性がある。これに対処するため、ローダブルなELFモジュールに.modinfoというセクションを置き、そこにバージョンデータを置くようにした。このバージョン情報はモジュールをロードする際に動作中のカーネルのバージョンと比較される。バージョンに非互換があれば、そのモジュールはロードされない。

FreeBSDでは、モジュールそれぞれにバージョン情報を置き、他のモジュールに依存するモジュールは依存するモジュールとそのバージョン番号を指定する。シンボルはモジュール名とバージョン番号で独立した名前空間を持っており、依存モジュールが指定されないとそのシンボルを使うことは出来ない。現在、カーネル自身もリリースやパッチバージョン毎にバージョン番号を持っており、依存関係は暗黙のうちに指定されるため、モジュールのコンパイル環境のカーネルソースと実カーネルが違うとシンボル解決に失敗してロードされない。

Solaris、WindowsといったOSでは、カーネルのAPIとABIは比較的安定に保たれており、このような問題を回避している。

5. セキュリティ
---------

ローダブル・カーネル・モジュールは動作中のカーネルを更新する簡単な方法だが、悪意ある者がシステムの制御を奪い、そのプロセスやファイルを隠蔽するのにも便利な機能である。ルートキットの多くはその目的でLKMを使っている。なお、LKMが特権を奪う助けにはなるとは限らない点には注意すること。LKMは単に侵入を隠蔽するのを容易にするだけである。

6. 脚注・出典
--------

7. 関連項目
-------

デバイスドライバ
バイナリ・ブロブ - バイナリオンリーなドライバについての用語
System.map

8. 外部リンク
--------

Linux
[1] IBM dW
[2]
[3]
[4]、Linux Documentation Project。
macOS
[5]
[6]