面 和毅
fanotifyは、Linux 2.6.36カーネルから取り込まれたファイルシステムの状態変化を通知する機構である。まず、他の通知機構であるdnotify/inotifyやfsnotifyとの関係性をみてみよう。
ファイルシステムの状態変化を通知する機構に関しては、Linuxでも古くから実装が行われてきた。例えば、dnotifyという2.4.19カーネル以降でメインラインに取り込まれた機構があり、これはディレクトリの状態変化を通知する機構である。この実装により、例えば、/etc以下のファイルが更新されているなど、設定ファイルの更新を記録することも可能であり、セキュリティ上でもディレクトリ内のファイルの勝手な更新を記録できるなどの効果があった。
更に2.6.13カーネルからマージされたinotify(inode-based file event notifications)では、dnotifyがディレクトリ単位での通知しか行えない機構であるのに対し、inodeベースでファイルの追加/変更/削除などのイベントを通知してくれる機構となった。inotifyでは、更にdnotifyに比べて
ものとなっており、更にファイルシステム上の状態変化を細かく通知してくれるため、セキュリティ上のより細かい記録が取れるようになった。また、SELinuxなど、inodeが更新された都度、ラベル情報を更新しなくてはならないアクセス制御システムでは、inotifyを利用してinodeの更新情報を取得する事ができるようになった。(ただし、inotifyではアクセスを行った主体に関する情報が取得できないため、監査で使用するには別途アクセス制御機構のログと組み合わせる必要がある。)
inotifyは、後述するfsnotifyが2.6.31カーネルから取り込まれたため、fsnotifyの機構を利用した上位レイヤで動く機構としてコードが更新された。2.6.36カーネル内部からはカーネル内で実装する必要が無いとされ、userspaceのinterfaceは2.6.36以降でもサポートされているが、inotify自体のコードは2.6.36カーネル内部からは削除されている。
fsnotifyは、filesystem上でのイベント通知機構のバックエンドとなる機構であり、今までの通知機構の実装をシンプルにするために導入された。fsnotify自身は、userspaceへのインターフェースを持っておらず、dnotify, inotify, fanotifyなどの通知機構のための基盤となっている。2.6.31以降、inotify/dnotifyはfsnotifyを利用するレイヤとしてシンプルに書き直された。
fsnotifyにより、inode構造体のサイズが最適化されている。fsnotifyが導入される以前のinode構造体には
unsigned long i_dnotify_mask; /* Directory notify events */ struct dnotify_struct *i_dnotify; /* for directory notifications */ struct list_head inotify_watches; /* watches on this inode */ struct mutex inotify_mutex; /* protects the watches list
がメンバとなっていたが、fsnotifyの導入により、
__u32 i_fsnotify_mask; /* all events for this inode */ struct hlist_head i_fsnotify_mark_entries; /* marks on this inode */
というようにメンバの数が少なくなっている。
fanotifyはinotifyなど他の通知機構と同様に、2.6.31カーネルから取り込まれているfsnotifyの上位レイヤで動く機構として実装されている。fanotifyは、ファイルディスクリプタをオブジェクトに対してオープンした際に(open, close, read, write)という発生イベントの種類をユーザスペースに通知する為の機構であり、新たに実装された機構である。当然この機構は、他の通知機構(inotifyやdnotifyなど)と競合を起こす可能性があるため、他の通知機構をブロックしたり制御できるように実装される予定となっている。
このfanotifyは主にAnti Virusソフトベンダやシステム監査ソフトベンダが利用する為に実装された。
Anti Virusベンダでは、オブジェクトをopen/closeしたイベントを掴んでウィルスエンジンに渡すために、syscall tableのアドレスをコピーして上書きするなど、かなり荒っぽいやり方で独自の通知機構を実装してきた。
このような方法は概ねうまくいっているが、同様の機構(ファイルシステムに何かイベントが発生した際の通知機構)を利用するアプリケーションを、同一サーバ上で使用している場合には、問題が生じてくる。例えば、Software AとSoftware Bが同様にsyscall tableのアドレスをコピーして上書きしていた場合には、ソフトウェアの起動と停止を、起動順に行っていくとSyscall Table(Syscall_Tableとする)が
Software Aを起動 → Syscall_Table_A(Software AがSyscall Tableを上書き)
Syscall_Table_A_B (Software BがSyscall Tableを更
Software Bを起動 → に上書き)
Syscall_Table_A (Software BがSyscall Tableを開放
Software Bを停止 → し、自身が起動する直前の物に上書き)
Syscall_Table
Software Aを停止 →
|
となるため特に問題は生じないが、システムの運用上の理由など、なんらかの理由で停止順序が狂い、Software Aを先に停止してしまった場合には
Software Aを起動 → Syscall_Table_A(Software AがSyscall Tableを上書き)
Syscall_Table_A_B (Software BがSyscall Tableを更
Software Bを起動 → に上書き)
Syscall_Table (Software AがSyscall Tableを開放し、
Software Aを停止 → 自身が起動する直前の物に上書き)
|
となり、Software Bが上書きしていたSyscall_Tableではなく、最初のSyscall_Tableに戻ってしまうため、Software AやSoftware Bがクラッシュしてしまうことがあった。
このような、ファイルシステムにイベントが発生した際に何かを行う機構は、Anti Virusの他にも、システム監査の目的でアクセスログを取得するようなソフトウェアが使用しているケースが多い。そのようなソフトウェアがインストールされるシステムでは、セキュリティの観点上、Anti Virusソフトウェアもインストールされているケースが多いため、実運用の際に上述のようなトラブルになるケースが起きていた。
2.6.36以降のカーネルを使用しているシステムでは、fanotifyとして通知機構が取り込まれているため、イベントの通知機構を各社独自で実装する必要はない。カーネルにそもそも取りこまれているfanotifyをベースとしてイベントを掴むようになるため、前述のようなトラブルも起きることは無く、実運用上でより安定したシステムが提供されることになる。
fanotifyを用いてユーザ空間にイベントを通知させるサンプルコードは、
git://git.kernel.org/pub/scm/linux/kernel/git/agruen/fanotify-example.git
から取得できる。
上記サンプルコードの使い方だが、コンパイルはgitでダウンロードした際にできる"fanotify-example"ディレクトリ内で一般ユーザで"make"とすれば
omo@localhost:~/work/fanotify-example$ make cc -g -Wall -Wextra -O2 -Iinclude -D_GNU_SOURCE -c -o fanotify.o fanotify.c cc -g -Wall -Wextra -O2 -Iinclude -D_GNU_SOURCE -c -o fanotify-syscalllib.o fanotify-syscalllib.c cc -g -Wall -Wextra -O2 fanotify.o fanotify-syscalllib.o -o fanotify omo@localhost:~/work/fanotify-example$ |
となってfanotifyバイナリが作成される。このバイナリだが、rootユーザでのみ実行でき、ディレクトリ内のファイル/サブディレクトリに、どのPIDのプロセスがどのようなアクセスを行ったかを表示する。具体的なヘルプは、fanotify -hで見ることができ、
omo@localhost:~/work/fanotify-example$ ./fanotify -help
USAGE: ./fanotify [-cfhmn] [-o
{open,close,access,modify,open_perm,access_perm}] file ...
-c: learn about events on children of a directory (not decendants)
-f: set premptive ignores (go faster)
-h: this help screen
-m: place mark on the whole mount point, not just the inode
-n: do not ignore repeated permission checks
-s N: sleep N seconds before replying to perm events
|
となっている。
例えば、
root@localhost:/home/omo/work/fanotify-example# ./fanotify -c /tmp
としてから、別のプロセスで/tmpディレクトリ以下にアクセスすると
/tmp: pid=1519 open ← /tmpディレクトリをlsした場合 /tmp: pid=1519 close /tmp/test: pid=1521 open ← /tmp/testサブディレクトリをlsした場合 /tmp/test: pid=1521 close /tmp/test123: pid=1523 open close(writable) ← /tmp/test123としてファイルを touchした場合 /tmp/test123: pid=1524 close ← /tmp/test123をviで編集した場合 /tmp/.test123.swp: pid=1524 modify |
として、/tmpディレクトリ以下に行われたイベントが記載されていく。pid/userとの対比は、別途psコマンドなどで取得した情報を元に行う。例えば、上記fanotifyコマンドを実行している間に、別セッションで"pstree -pu > /tmp/pstree_pu"とすると
/tmp/pstree_pu: pid=1598 open /tmp/pstree_pu: pid=1598 modify /tmp/pstree_pu: pid=1598 close(writable) |
のように出力される。このときのpstreeコマンドの実行結果(/tmp/pstree_puファイルの中身)は、
init(1)-+-acpid(973)
|-sshd(1374)-+-sshd(1392)---sshd(1394,omo)---bash(1395)---su(1424,root)--
-bash(1425)---fanotify(1597)
|
`-sshd(1485)---sshd(1488,omo)---bash(1489)---su(1570,root)---bash(1571)--
-pstree(1598)
`-udevd(301)-+-udevd(524)
`-udevd(525)
|
となっており、pid1598の親プロセスのUIDがrootであり、さらに元の親がUID omoで実行されていることがわかる。
以上