kernel_read関数
vfs関連ファイル操作の関数を、LKMから使用する場合、そのbuf引数がユーザ空間である必要がある。処理の前にaccess_okマクロで、bufがカーネル空間ならエラーとするため。LKMからのファイル出力でlkmからの入出力で、do_mmap_pgoff()で、無理やりユーザ空間のバッファを確保し、それでもっての強引な処理。
カーネルを眺めていると、kernel_read()と言う関数にぶち当たった。カーネル空間のbufから、ファイルの読み込みを行うもので、カレントプロセスのDSセグメント値を、カーネルDSセグメントに差し替える事で実現する。
追記
うそでした。セグメントその物を差し替えるのではありませんでした。
カーネルを眺めていると、kernel_read()と言う関数にぶち当たった。カーネル空間のbufから、ファイルの読み込みを行うもので、カレントプロセスのDSセグメント値を、カーネルDSセグメントに差し替える事で実現する。
追記
うそでした。セグメントその物を差し替えるのではありませんでした。
#define get_fs() (current_thread_info()->addr_limit) #define set_fs(x) (current_thread_info()->addr_limit = (x)) #define get_ds() (KERNEL_DS) int kernel_read(struct file *file, loff_t offset, char *addr, unsigned long count) { mm_segment_t old_fs; loff_t pos = offset; int result; old_fs = get_fs(); set_fs(get_ds()); result = vfs_read(file, (void __user *)addr, count, &pos); set_fs(old_fs); return result; }ユーザ/カーネル空間のチェックは、current_thread_info()->addr_limit.segにメモリ空間サイズが設定されていて、そのサイズ値によって判断される。__range_not_okマクロは、インラインで詳細は分からないが、雰囲気チェックするaddr+sizeを、current_thread_info()->addr_limit.segとチェックしている。
#define access_ok(type, addr, size) (likely(__range_not_ok(addr, size) == 0)) #define __range_not_ok(addr, size) ({ unsigned long flag, roksum; __chk_user_ptr(addr); asm("add %3,%1 ; sbb %0,%0 ; cmp %1,%4 ; sbb $0,%0" : "=&r" (flag), "=r" (roksum) : "1" (addr), "g" ((long)(size)), "rm" (current_thread_info()->addr_limit.seg)); flag; }以下はLKMからのファイル出力の処理を、この手法にて書き直した物。
#include <linux/fs.h> #include <linux/file.h> #include <linux/pagemap.h> #include <linux/memcontrol.h> #include <linux/module.h> MODULE_DESCRIPTION("tty read/write"); MODULE_AUTHOR("y.kitamura"); MODULE_LICENSE("GPL"); static char* tty = ""; module_param(tty, charp, S_IRUGO); static int tty_init(void) { struct file* file; char buf[256], msg[256]; mm_segment_t old_fs; ssize_t res; if (tty == NULL) { printk("insmod file.ko tty=/dev/ttydrv\n"); return -1; } file = filp_open(tty, O_RDWR, 0); if (IS_ERR(file)) { sprintf(msg, "err:%ld\n", PTR_ERR(file)); printk(msg); return -1; } old_fs = get_fs(); set_fs(get_ds()); strcpy(msg, "[from lkm]# "); res = vfs_write(file, (const char __user *)msg, strlen(msg), &file->f_pos); memset(buf, 0, sizeof(buf)); res = vfs_read(file, (const char __user *)buf, sizeof(buf), &file->f_pos); sprintf(msg, "input data in LKM is %s", buf); res = vfs_write(file, (const char __user *)msg, strlen(msg), &file->f_pos); set_fs(old_fs); return -1; } module_init(tty_init);ttyコマンドで、現在の端末を確認。そのデバイス名で上記モジュール(file.ko)をinsmodする。LKMからのプロンプトとして、[from lkm]#が表示され、何か入力すると、input data in LKM isとして、それが表示される。なお、最後のエラーメッセージは、rmmodする手間を省くため、tty_init()でreturn -1としているから。
[root@localhost lkm]# tty /dev/pts/0 [root@localhost lkm]# insmod file.ko tty=/dev/pts/0 [from lkm]# abcdefg input data in LKM is abcdefg insmod: error inserting 'file.ko': -1 Operation not permitted [root@localhost lkm]#