概要
Linuxカーネルのフック機構を活用することで、カーネルイベントを捕捉し追加処理を実行することが可能である。kprobesとjprobeの2種類の方法が代表的であり、jprobeはkprobeの上位ラッパーとして実装されており、より簡潔な記述できる。submit_bio関数はブロックデバイスへのI/Oリクエスト処理の根本的な関数であり、この関数をフックすることでブロックデバイスI/Oイベントの監視が実現できる。
実装手順
kprobeまたはjprobeを使用して対象のカーネルシンボル(カーネル関数)を登録し、対応するカーネルコールが発生した際に優先的にフック関数が実行される。
環境設定
uname -rコマンドで確認したカーネルバージョンに対応する内核開発パッケージをダウンロードする必要がある。Makefileの-Cオプションで指定するディレクトリとして必要となる。
監視モジュールの実装
bio_monitor.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>
#include <linux/bio.h>
static int handle_bio_request(unsigned int operation, struct bio *bio_data)
{
if(bio_data && bio_data->bi_io_vec != NULL) {
char buffer[BDEVNAME_SIZE];
unsigned long long sector_pos = bio_data->bi_sector;
unsigned int sector_count = bio_sectors(bio_data);
const char *op_type = (operation & WRITE) ? "write" : "read";
printk(KERN_INFO "block_device[%s] operation[%s] position[%10lld] size[%u]",
bdevname(bio_data->bi_bdev, buffer),
op_type,
sector_pos,
sector_count);
}
jprobe_return();
return 0;
}
static struct jprobe bio_jprobe_handler = {
.entry = (kprobe_opcode_t *) handle_bio_request,
.kp = {
.addr = NULL,
.symbol_name = "submit_bio",
},
};
static int __init monitor_init(void)
{
int ret_status = 0;
ret_status = register_jprobe(&bio_jprobe_handler);
if(ret_status < 0) {
printk(KERN_ERR "bio_monitor: registration failed, error_code[%d]", ret_status);
return ret_status;
}
printk(KERN_INFO "bio_monitor: module loaded successfully.");
return 0;
}
static void __exit monitor_cleanup(void)
{
unregister_jprobe(&bio_jprobe_handler);
printk(KERN_INFO "bio_monitor: module unloaded.");
}
module_init(monitor_init);
module_exit(monitor_cleanup);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Kernel Developer");
MODULE_DESCRIPTION("Block Device I/O Monitor using JProbe");
Makefile
KERNEL_DIR := /usr/src/kernels/$(shell uname -r)
obj-m += bio_monitor.o
all:
make -C $(KERNEL_DIR) M=$(PWD) modules
clean:
make -C $(KERNEL_DIR) M=$(PWD) modules clean
rm -f *.o *.mod.c *.order *.symvers
モジュールのロードとアンロード
ロード: insmod bio_monitor.ko
アンロード: rmmod bio_monitor
監視結果の確認: /var/log/messages または dmesg コマンドで確認可能
その他のカーネルフック活用例
システムコールフック
- sys_open - ファイルオープン操作
- sys_read/sys_write - 読み書きシステムコール
- sys_execve - プロセス実行
- sys_fork/sys_clone - プロセス生成
ネットワーク関連フック
- nf_register_hook/nf_unregister_hook - Netfilterパケットフィルタ
- dev_add_pack/dev_remove_pack - パケット受信処理
- inet_register_protosw/inet_unregister_protosw - プロトコル登録
VFSファイルシステム操作
- vfs_read/vfs_write - 仮想ファイルシステム層でのI/O
- vfs_open/vfs_close - ファイルオープン/クローズ
プロセス管理
- do_fork - プロセス作成処理
- do_exit - プロセス終了処理
- schedule - CPUスケジューラ
メモリ管理
- __kmalloc/kfree - カーネル動的メモリ確保/解放
- vmalloc/vfree - 仮想アドレス領域確保/解放
キャラクタ/ブロックデバイス管理
- register_chrdev/unregister_chrdev - キャラクタデバイス登録
- register_blkdev/unregister_blkdev - ブロックデバイス登録
セキュリティモジュール(LSM)
- security_inode_create - inode作成時のセキュリティチェック
- security_file_open - ファイルオープン時の権限検証
- security_task_create - タスク作成時のセキュリティ評価
ファイルシステム管理
- register_filesystem/unregister_filesystem - ファイルシステム登録
- alloc_inode/destroy_inode - inodeメモリ管理
- module_load/module_unload - モジュール載卸し監視
これらのフックポイントを活用することで、カーネルレベルの詳細なイベント監視やセキュリティ監査、的性能分析などの様々な用途が実現可能である。