Skip to content

Chapter 2

__ukun edited this page Aug 10, 2017 · 2 revisions

第2章ではカーネルモジュールに触れてもらいます。まず、最初に警告しておくべきこととして、

カーネル空間で動作するプログラムの不具合は、システムにダメージを与える(例:ファイルシステムが損傷して全てのファイルを失ってしまう)可能性があります。

そのため、VirtualBox などを用いて、破棄しても惜しくない Linux 環境を用意の上、実行するようにしてください。

第2章:カーネル空間ではいろいろと違うことを体験する。(2時間)

  1. カーネルモジュールで Hello world. を実現する。
     →必要なパッケージのインストールができることを確認する。
      module_init で printk() を呼ぶ。

  2. 無限ループや待ち状態を体験する。
     → SIGKILL で強制終了できないことを確認する。
      mutex_lock() と mutex_lock_killable() の差異について知る。
       module_init で double lock する?

  3. NULL pointer deref でクラッシュさせる。
     →システムがダウンしてしまうことを確認する。
       /proc/sys/kernel/panic_on_oops の設定に依存する。

  4. 0除算でクラッシュさせる。
     →システムがダウンしてしまうことを確認する。

  5. スタック変数を大量に使う。
     →スタック変数を自由に使えないことを知る。

  6. 連続したメモリを割り当てる。
     →連続したメモリを自由に割り当てられないことを知る。

  7. メモリリークでクラッシュさせる。
     →カーネル内でのメモリリークが危険なことを知る。
      ユーザ空間からカーネルメモリリーク攻撃される可能性の一例 keyctl OOM DoS
       多数の無実なプロセスを殺してしまう OOM killer 。

カーネルは究極のマルチスレッドプログラム。システムをシャットダウンまたは
再起動するまで動き続けるため、リソース管理が大変。いろいろな制約がある。
だから、ユーザ空間でできることはユーザ空間でやろう。ユーザ空間からの
攻撃とカーネル空間内のバグに注意しながら。

2-1

最初の事前課題は、C言語で Hello world. を表示するカーネルモジュールのソースコードを 書いて、コンパイルに必要なパッケージをインストールして、コンパイルを行って、実行する というものです。これだけで「了解~」な人は、始めてください。以下は、ヒントです。

ユーザ空間での printf() 関数に対応する Linux カーネル内の関数は printk() です。

カーネルモジュールを実行するというのは、 modprobe コマンドや insmod コマンドなどを 用いてカーネルモジュール(拡張子が .ko のファイル)をロードすることに相当します。

ユーザ空間で動作するC言語プログラムは実行すると main() 関数が呼ばれるのに対して、 カーネル空間で動作するカーネルモジュールの場合はソースコード内の module_init() パラメータで指定されている関数が呼ばれます。(ちなみに、 module_init() パラメータで 指定されている関数が 0 を返却した場合、カーネルモジュールはロードされたままの状態に なり、 rmmod コマンドなどを用いてアンロードされるまでカーネル空間内に残存します。

アンロードされる際には、クリーンアップ処理を呼び出すために、ソースコード内の module_exit() パラメータで指定されている関数が呼ばれます。)

え?難しすぎる?・・・はい、そうかもしれません。ですので、解けない方は http://akari.osdn.jp/1.0/chapter-3.html を見ながら 3.1 の始まりから 3.2 の終わりまで 試してみてください。第4章で AKARI のソースコードを流用するため、解けた方も試しておいて 損はありません。

カーネルのソースコードは一部を除いてC言語で書かれているので、以後は特に断りが 無ければ「C言語で」という解釈をしてください。

2-2

2番目の事前課題では、無限ループやデッドロック状態を引き起こすカーネルモジュールの ソースコードを書いて、コンパイルを行って、実行してもらいます。

無限ループの書き方はいくつかありますが、何かのメッセージを表示しながらでも 構いませんし、単純に while (1); だけでも構いません。

1-2では、デッドロック状態を引き起こすプログラムを実行し、 KILL シグナルを 送信することで強制終了できることを確認していただいたかと思います。

無限ループ状態に陥っているカーネルモジュールについても、 KILL シグナルを 送信することで強制終了できるかどうかを確認してみてください。

カーネル内で使えるロックの方法としては、 spinlocksemaphoremutex など いろいろありますが、ここでは mutex を使います。 mutex のロックを獲得するには mutex_lock() という関数を、ロックを解放するには mutex_unlock() という関数を 使います。 mutex を宣言する方法や初期化する方法については、カーネルソース ツリーなどから探してみてください。

mutex を用いてデッドロック状態を発生させるには、 mutext_lock() を2回続けて 呼び出すことで実現できます。デッドロック状態に陥っているカーネルモジュールに ついても、 KILL シグナルを送信することで強制終了できるかどうかを確認してみて ください。

2-2は、2章の中では一番難しいと感じるかもしれません。でも、カーネル内で 動作するプログラムを書く上での基本事項であり、避けて通れない道ですので、 頑張ってください。コンパイルおよび実行の手順は2-1と同様です。

どうしても解らない場合には、添付ファイルをダウンロードして使ってください。

解った人は、どう修正すれば SIGKILL シグナルを送信することでデッドロック状態を 解消できるようになるのかを考えてみてください。(「安全で丁寧なプログラムを 書くためのコツ」に関係しています。)

2-3

3番目の事前課題では、 NULL pointer dereference が発生するカーネルモジュールを書いて、 コンパイルを行って、実行することで、システムがどうなるのかを確認してください。なお、 実行前に /proc/sys/kernel/panic_on_oops の値が 0 なのか 1 なのかも確認しておいてください。 そして、 /proc/sys/kernel/panic_on_oops の値が、実行後のシステムの状態にどう影響するかを 比較してください。(あっ、 >>5 の deference って何だ!?(自爆))

2-4

4番目の事前課題では、0除算エラーが発生するカーネルモジュールを書いて、コンパイルを行って、 実行することで、システムがどうなるのかを確認してください。どちらも Hello world. 並に簡単 なので、ソースコードの例を示すまでもないかと思います。

2-5

5番目の事前課題は、スタックメモリを大量に使うカーネルモジュールを書いて、コンパイルを 行って、実行してみてください。そして、次第にスタックメモリの消費量を増やしていき、上限に 達すると何が起こるのか、そして、その時の上限は何バイトくらいなのかを確認してみてください。

2-6

6番目の事前課題は、メモリ割り当て関数を用いて連続したメモリを割り当てるカーネルモジュールを 書いて、コンパイルを行って、実行してみてください。そして、次第に割り当てるサイズを増やしていき、 上限に達すると何が起こるのか、そして、その時の上限は何バイトくらいなのかを確認してみてください。

なお、ユーザ空間での malloc() 関数に対応する Linux カーネル内の関数は kmalloc() 、 ユーザ空間での free() 関数に対応する Linux カーネル内の関数は kfree() です。

malloc()kmalloc() の違いとしては、 kmalloc() には割り当てたいサイズ(バイト数)の他に、 「メモリを確保するために行うことが許される処理」を示す GFP フラグも指定する必要があるという 点です。指定すべき GFP フラグは状況(例:スリープ可能なコンテキストか否か)により変化する のですが、現時点までに学習した範囲では GFP_KERNEL を指定できるので、 GFP_KERNEL を指定 してください。

5番目と6番目については、エラーハンドリングなどが正しく行われているかどうかをソースコードで 確認したいので、作成したソースコードの例を、ここまたはチャットに添付ファイルとして貼り付ける ようにしてくださいね。

2-7

7番目の事前課題(↑の第2章の最後の内容)は、「メモリリークによりシステムのメモリを 枯渇させる」カーネルモジュールを書いて、コンパイルを行って、実行してみてください。

やることは、無限ループの中で kmalloc() を呼び出して、メモリを割り当て続けるだけなのですが ・・・驚くような結果が発生するものと思われます。

第2章の事前課題7では、カーネルが出力するメッセージを確認できるように、 GUIデスクトップ環境ではなく、CUIコンソール環境から実行することを 推奨します。また、実行する前に以下の設定をしておくことを推奨します。

echo 1 > /proc/sys/kernel/sysrq
echo 7 > /proc/sys/kernel/printk

7番目までクリアできたら、エイプリルフールに投稿されたメモリリークDoS脆弱性の話をします。

Clone this wiki locally