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

ローダブル・カーネル・モジュール（英: loadable kernel module, LKM）は、オペレーティングシステム (OS) の動作中のカーネルを拡張するコードを含むオブジェクトファイル。現行Unix系システムの多くやMicrosoft Windowsはローダブル・カーネル・モジュールをサポートしているが、呼称はそれぞれ異なる。例えばmacOSでは"kernel extension" ("kext") と呼ぶ。他にもカーネル・ローダブル・モジュール (KLM) やカーネルモジュール (KMOD) とも呼ばれる。LKMは一般にCPUやM/Bなど新しいハードウェアやファイルシステムのサポート、通信プロトコル、システムコールを追加するために利用される。LKMの提供する機能が不要になれば、アンロードしメモリを解放することができる。 ローダブル・カーネル・モジュールが存在しない場合、カーネルに必要と思われる機能を全て最初からコンパイルし静的リンクしておかねばならない。その機能の多くは実際には使用されていないにもかかわらずメモリに常駐し、新たな機能が必要になるとカーネル全体の再ビルドと再起動が必須となる。ローダブル・カーネル・モジュールをサポートするOSの多くは、多くの機能をサポートするモジュール群を備えている。 Linuxにおけるモジュールはシステムレベルの機能を実行するルーチンの集まりである。それらは特定のデバイス利用のためのデバイスドライバやファイルシステム、通信プロトコルのためのシステムルーチンを含む、カーネルにリンクされていないオブジェクトファイルであり、システムを再起動せずそれらを利用可能にする。場合によっては、実際にデバイスを利用する場合、MAKEDEVを使用して静的にデバイスファイルを作成する、もしくはudevを利用してホットプラグを行い動的にデバイスファイルを生成する必要がある。 Linuxのモジュール群は/lib/modulesディレクトリにバージョン毎に置かれ、バージョン2.6以降は拡張子 .ko ("kernel object"の意)が付されている。モジュールはカーネルに既に含まれている未解決シンボルを持ち、再配置可能なELFオブジェクトである。これはカーネルを一種の共有ライブラリと見なした場合の動的リンクされたライブラリといえるかもしれないが、GNU Cライブラリに動的リンクされた実行ファイルをロードする際に使用するld-linux.soなどといったローダを使用するのではなく、modutils（バージョン2.6以降はmodule-init-tools）パッケージに含まれるinsmod（もしくは高レベル処理を行うmodprobe（英語版））コマンドを用いてカーネル空間にロードする。insmodコマンドを起動しモジュールをロードすると依存関係の解決など様々なチェックに通され、問題なければカーネル内に存在するシンボルの解決を行う。バージョン2.6以前はシンボル解決までの複雑な処理はユーザー空間のコマンドであるinsmodが処理していたが、パフォーマンス低下の問題からバージョン2.6からはinsmodはモジュールロードに使用するメモリアドレスにモジュールを単にコピーするだけの簡単なコマンドになっており、シンボル解決も含めた残りの処理はすべてカーネル空間で処理している。これはFreeBSDオペレーティングシステムにおける"Dynamic Kernel Linker"と類似している。モジュールのアンロードはロードの逆処理となるが、通常はモジュールの依存関係をチェックし、依存しているモジュールを検出した場合エラーを返しアンロードしない。そのような場合は全ての被依存モジュールを先にアンロードする、または特定のオプションを指定し依存しているモジュールも同時にアンロードする。 カーネルに既にロードされているモジュールはlsmod（英語版）コマンドを用いて一覧表示できる。 Linuxカーネルのモジュールは後述する互換性の問題から、カーネルがリリースされる度にリビルドする必要がある。モジュールのビルドはカーネルソースコードに含まれるデバイスドライバに対するものとカーネルソースコードに含まれない外部のカーネルモジュールに対するものの2通りあるが、いずれもカーネルビルドのインフラを利用している。 通常、カーネルソースコードに含まれる一部のデバイスドライバはカーネルコンフィグレーション(Kconfig)にてカーネルに直接静的リンクするか、もしくはモジュールとして構築するかのいずれかを選ぶことができる。モジュールとして構築することを選択した場合、カーネルソースのMakefileにはmodulesというターゲットが存在し、予めこのディレクトリでカーネル（に直接関係し、カーネルイメージの生成前段階のファイルvmlinux）を作成した後、このmodulesターゲットを実行する事でモジュールのビルドと依存関係の解決用ファイルが生成される。そして、make installを実行すると、システムにカーネルとモジュールを同時にインストールする。 公式のカーネルソースはメインライン (mainline) カーネル、もしくはメインストリーム (mainstream) カーネルと呼ばれるが、目下開発中で未だメインラインに採用されていないデバイスドライバ、GPLと非互換なライセンスで配布されているためメインラインにマージされないモジュール、または自由な改変が全く許されないプロプライエタリなモジュールをビルドするには、カーネルビルドのインフラを間接的に利用する。したがって外部のカーネルモジュールのビルドのためには、カーネルのビルドが正常に終了した完全なカーネルソースコードのコピーがあればよい。もしくはその中から一部のビルドツールやヘッダファイルを抽出したものを用意すればよい。前者はユーザーが独自にカーネルをビルドすることで得られる。後者は、ユーザーが独自に用意することもできるが、多くのLinuxディストリビューションでは、この様な機能を持つパッケージを提供している場合がある。またディストリビューションにより、この低レベルビルドインフラをよりユーザーフレンドリーなツールを介してビルドできるパッケージ も用意されている場合がある。 Linuxカーネルコミュニティによれば、LKMはカーネルの二次的著作物(derivative works)である。Linuxカーネル開発者はプロプライエタリなモジュールの配布を許容しているが、一部のカーネルシンボルはGPLのモジュールにのみ利用を許している。 プロプライエタリまたはGPL非互換な組み合わせでライセンスされているLKMをロードするとプロプライエタリモジュールがロードされたことを示すTainted（汚染）フラグが立つ。そのようなLKMを使ってバグが発生した場合、Linuxカーネル開発者は完全な原因を解明できない可能性が高いためユーザに通知するのである。LKMは事実上、動作中のカーネルの一部になる。したがってカーネルのデータ構造を破壊したり、バグを発生させたりする可能性があるが、プロプライエタリである限りその原因を調査できない。 このため2001年にリリースされた、バージョン2.4.10より、モジュール毎にMODULE_LICENSEマクロというモジュールが従うライセンス名を埋め込み、それが"GPL"などでない場合、LKMをロードして組み込む際にTaintedメッセージを表示するようになった。 MODULE_LICENSEマクロには、例えばGPLならば、MODULE_LICENSE("GPL");、BSDライセンス(BSDL)ならばMODULE_LICENSE("BSD");、GPLとBSDLのデュアルライセンスならばMODULE_LICENSE("Dual BSD/GPL");という文字列を埋め込む(引用符""も含める)。必要ならばカーネルソースコードに含まれるinclude/linux/module.hを見てほしい。 概要で述べた通り、通常カーネル内にある関数名、構造体名などのシンボルはカーネルの外からは直接見えない。LKMのロードにはシンボル解決が必要であるが、シンボル解決するためにはinsmod等のモジュールローダを起動し、様々なチェックに通る必要がある。この時前述の通り、MODULE_LICENSEマクロにある文字列がチェックされる。シンボル名は通常、EXPORT_SYMBOLまたは、GPLにライセンスされたモジュールのみが参照可能なEXPORT_SYMBOL_GPLマクロに記述する。このマクロに記述されたシンボル名はモジュールローディング時にモジュールから参照できるようになるが、あらゆるライセンス用に提供されるEXPORT_SYMBOLとは異なり、EXPORT_SYMBOL_GPLマクロはプロプライエタリなモジュールからは一切参照できない。すなわちプロプライエタリなモジュールを作成する場合はEXPORT_SYMBOL_GPLマクロでエクスポートされている関数を利用できないことになる。プロプライエタリなモジュールを作成する企業にとっては、これは前述の汚染フラグとは比べものにならないほど厳しい制限に思える。しかし、Linuxカーネル開発者にとってはカーネル内の重要な処理に関する決定のイニシアティブを一貫してLinuxカーネルコミュニティが持つことができるという大きな利点がある。プロプライエタリなドライバはソースコードがない、もしくは自由な改変を許可していないものが多く、状況によりカーネルのAPIをはじめとして最新のカーネル開発状況に追随できていないものが存在する。そのようなドライバによるバグやシステムクラッシュが発生したとしてもソースコードを改変できないため、Linuxカーネルコミュニティ側では全く解決できない。これは昔からコミュニティの悩みの種となっているため、カーネル内の極めて重要なオブジェクトや、問題がひとたび発生するとシステムがクラッシュするような処理を最初からバイナリオンリードライバには実行できないようにしようとしたのである。 LinuxantはプロプライエタリなLKMを販売しているコンサルティング企業であるが、同社がMODULE_LICENSEマクロに与えたライセンス文字列にヌル文字（\0）を忍び込ませることによりライセンスによる制限を突破していたことが2004年コミュニティにより発覚した。以下は当該モジュールにて実装されていたライセンス定義箇所の再現。 MODULE_LICENSE("GPL\0for files in the \"GPL\" directory; for others, only LICENSE file applies"); ライセンス文字列からヌル文字を除去して和訳すると「"GPL" というディレクトリには GPL を適用したファイルがあり、他には LICENSE ファイルが適用される」となる。 当時のカーネルはモジュールのライセンスをC言語で一般的な、ヌル文字で終端した文字列として記述しており、モジュールのライセンスがGPLを含むか否かの判別処理に用いていた関数strcmpはヌル文字に達したところで比較をやめるため、モジュールのライセンスがGPLだと誤って判断してしまった。しかし、その後の文に書いてあるGPLディレクトリは空であり、GPLにて配布されていた箇所は全くなかった。 Linuxカーネル、バージョン6.6現在、上述のモジュールライセンス判定処理や、ライセンス文字列をヌル文字に依存しない形式に変更するなどの修正はされていない。 FreeBSDのカーネルモジュールは、OS・カーネルと同時に頒布されるモジュールの場合/boot/kernel/ディレクトリ以下に存在し、FreeBSD Ports（英語版）またはFreeBSD Packagesからインストールした場合や、プロプライエタリはたまたバイナリ・オンリーモジュールの場合は通常、/boot/modules/ディレクトリ以下に存在する。FreeBSDのカーネルモジュールは通常拡張子.koを持つ。マシンを一旦ブートするとkldloadコマンドを利用しモジュールはロードされる。アンロードはkldunloadコマンドを利用する。カーネルにロード中のモジュールをリストアップするには、kldstatコマンドを利用する。またモジュールはカーネルロード前のブート時点でもロード可能であり、設定ファイル/boot/loader.confを利用し自動的にロードするかまたは手動でロードを行う。 macOS用のいくつかのローダブル・カーネル・モジュールは自動的にロードすることができる。またLKMはkextloadコマンドを用いて個別にロードできる。いずれのLKMもkextstatコマンドを用いることでロード可能なLKMを一覧表示できる。LKMは拡張子.kextをもつアプリケーションバンドル内に存在する。オペレーティングシステムにより提供されるLKMは、/System/Library/Extensionsディレクトリ以下、サードパーティー製の場合は他様々なディレクトリに保存される。 カーネルのモジュール化についてのもっと細かい批判として、いわゆるフラグメンテーションによるペナルティがある。カーネル本体はメモリ内の連続領域に展開され、断片化されることはない。しかし、LKMのロード/アンロードを繰り返すと、カーネルコードのフラグメンテーションが発生する。したがって、若干の性能低下が起きる。 LinuxがLKMに提供しているAPIやABIは安定したものではない。すなわち、カーネルのバージョンが異なれば内部のデータ構造や機能に差異があり、非互換問題を発生する可能性がある。これに対処するため、ローダブルなELFモジュールに.modinfoというセクションを置き、そこにバージョンデータを置くようにした。このバージョン情報はモジュールをロードする際に動作中のカーネルのバージョンと比較される。バージョンに非互換があれば、そのモジュールはロードされない。 FreeBSDでは、モジュールそれぞれにバージョン情報を置き、他のモジュールに依存するモジュールは依存するモジュールとそのバージョン番号を指定する。シンボルはモジュール名とバージョン番号で独立した名前空間を持っており、依存モジュールが指定されないとそのシンボルを使うことは出来ない。現在、カーネル自身もリリースやパッチバージョン毎にバージョン番号を持っており、依存関係は暗黙のうちに指定されるため、モジュールのコンパイル環境のカーネルソースと実カーネルが違うとシンボル解決に失敗してロードされない。 Solaris、WindowsといったOSでは、カーネルのAPIとABIは比較的安定に保たれており、このような問題を回避している。 ローダブル・カーネル・モジュールは動作中のカーネルを更新する簡単な方法だが、悪意ある者がシステムの制御を奪い、そのプロセスやファイルを隠蔽するのにも便利な機能である。ルートキットの多くはその目的でLKMを使っている。なお、LKMが特権を奪う助けにはなるとは限らない点には注意すること（LKMのロードにはルート権限が必要だからである）。LKMは単に侵入を隠蔽するのを容易にするだけである。 ^ Debianプロジェクト. “Debian - パッケージ検索結果 - makedev”. packages.debian.org. 2011年1月27日閲覧。 ^ “The Linux Kernel Module Programming Guide, section 2.2 "Compiling Kernel Modules"”. LDP. tldp.org. 2011年10月14日閲覧。 ^ ラスティ・ラッセル (2010年6月7日). “module-init-tools”. packages.debian.org. 2011年1月27日閲覧。 ^ a b “Man page of INSMOD”. JM Project (2001年10月2日). 2011年1月27日閲覧。 ^ “Man page of MODPROBE”. JM Project (2002年2月4日). 2011年1月27日閲覧。 ^ a b FreeBSDプロジェクト (2006年). “Dynamic Kernel Linker Facility - KLD”. www.freebsd.org. 2011年1月27日閲覧。 ^ “Manpage of LSMOD”. JM Project (2002年2月4日). 2011年6月13日閲覧。 ^ Debianプロジェクト. “Debian -- パッケージ検索結果 -- linux-kbuild”. packages.debian.org. 2011年1月28日閲覧。 ^ Debianプロジェクト. “Debian -- パッケージ検索結果 -- linux-headers”. packages.debian.org. 2011年1月28日閲覧。 ^ “ModuleAssistant - Debian Wiki”. wiki.debian.org (2010年10月10日). 2011年1月28日閲覧。 ^ デルにより開発されたDynamic Kernel Module Support（英語版）（DKMS）。 ^ Bryan Henderson (2006年9月24日). “Linux Loadable Kernel Module HOWTO - 13. Copyright Considerations With LKMs”. Linux Documentation Project. 2011年1月28日閲覧。 ^ リーナス・トーバルズ他 (2011年6月21日). “Documentation/oops-tracing.txt”. git.kernel.org. 2011年6月22日閲覧。 “Tainted kernels:” ^ Jonathan Corbet (2006年3月24日). “Tainting from user space”. LWN.net（英語版）. 2011年4月25日閲覧。 ^ “Tainted kernel”. [[ノベル (企業)|]] (2010年3月15日). 2011年4月25日閲覧。 ^ “汚染されたカーネル”. [[ノベル (企業)|]] (2009年4月27日). 2011年1月27日閲覧。汚染フラグはこの文書の通り、「プロプライエタリなモジュールがロードされた」場合だけではなく、Linuxカーネル開発者が障害のトラブルシューティングを行う際の切り分けに有用な情報を出力するように設計されている。 ^ a b Joe-Barr (2004年4月30日). “プロプライエタリ・ドライバによるLinuxカーネルの「汚染」”. SourceForge.JP Magazine. 2011年1月28日閲覧。リーナス・トーバルズによる「汚染フラグ」についての説明も記載されている。 ^ “Linux Kernel と GPL 関連”. Mc.N Homepage SDK. 2011年1月27日閲覧。 ^ “include/linux/module.h”. git.kernel.org (2011年1月24日). 2011年1月28日閲覧。 ^ 上川純一 (2005年4月19日). “Linux Kernel Watch 4月版 カーネル2.6.11.yのメンテナは嫌なヤツ？（2/2） - sysfsを利用するにはGPLじゃないとダメ？”. アットマーク・アイティ、ITmedia. 2011年1月27日閲覧。 sysfsは各種システムパラメータの塊といえるが、誤ったAPIで操作すれば当然クラッシュする。 ^ “GitHub, torvalds / linux, linux/include/linux/license.h”. 2023年10月31日閲覧。 インライン関数 license_is_gpl_compatible()が当該。 ^ “GitHub, torvalds / linux, linux/lib/string.c”. 2023年10月31日閲覧。 カーネル向けの実装だが、意味はユーザランド向けに同じ。 ^ Jeremy Andrews (2004年4月27日). “Linux: Abusing the MODULE_LICENSE Macro (MODULE_LICENSEマクロの乱用)”. KernelTrap（英語版）. 2013年5月3日時点のオリジナルよりアーカイブ。2011年1月27日閲覧。 ^ Jonathan Corbet (2004年4月27日). “Being honest with MODULE_LICENSE”. LWN.net（英語版）. 2011年12月14日閲覧。 ^ Michael Reiter. “Exploiting Loadable Kernel Modules”. 2011年12月14日閲覧。 デバイスドライバ バイナリ・ブロブ - バイナリオンリーなドライバについての用語 System.map Linux Linux ローダブル・カーネル・モジュールの徹底調査 IBM dW Omicron Linux/LKM Omicron Linux/LKM/2.6 Peter Jay Salzman、Michael Burian、Ori Pomerantzらによる Linux Kernel Module Programming Guide、Linux Documentation Project。 macOS Kernel Extension Programming Topics: Introduction Online kext database for OSX / Hackintosh