Linuxカーネルフック関数によるブロックデバイスI/Oイベントの監視実装

概要

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 - モジュール載卸し監視

これらのフックポイントを活用することで、カーネルレベルの詳細なイベント監視やセキュリティ監査、的性能分析などの様々な用途が実現可能である。

タグ: linux-kernel kprobe jprobe block-device bio

8月10日 11:00 投稿