Kernel/User Space Transitions

まずはカーネル/ユーザ間でのデータのやりとりについて

詳しくはman 9 copy

copyin copyinstr

copyincopyinstrは文字通りユーザ空間からカーネル空間へデータをコピーする.

#include <sys/types.h>
#include <sys/systm.h>

int
copyin(const void *uaddr, void *kaddr, size_t len);

int
copyinstr(const void *uaddr, void *kaddr, size_t len, size_t *done);

lenバイトのデータをコピーする.成功したら0を返す.

copyinstrは,途中でNull終端されている場合はそこまでのデータをコピーし,doneには読み込まれたバイト数が書き込まれる.

copyout

copyoutcopyinの逆.カーネル空間からユーザ空間へデータをコピーする.

#include <sys/types.h>
#include <sys/systm.h>

int
copyout(const void *kaddr, void *uaddr, size_t len);

copystr

copystrカーネル空間の間で文字列をコピーするのに使われる.

#include <sys/types.h>
#include <sys/systm.h>

int
copystr(const void *kfaddr, void *kdaddr, size_t len, size_t *done);

Character Device Modules

モジュールが作れるので,当然キャラクタデバイスを作成する.

キャラクタデバイスモジュールには固有の3つの項目があり,それぞれについて説明する.

cdevsw

キャラクタデバイスはcharacter device switch tableのエントリとして登録される.

struct cdevsw<sys/conf.h>で以下の様に定義されている

struct cdevsw {
        int                     d_version;
        u_int                   d_flags;
        const char              *d_name;
        d_open_t                *d_open;    /* opens a device for I/O operations */
        d_fdopen_t              *d_fdopen;
        d_close_t               *d_close;   /* closes a device */
        d_read_t                *d_read;    /* reads data from a device */
        d_write_t               *d_write;   /* write data to a device */
        d_ioctl_t               *d_ioctl;   /* performs an operation other than a read or write */
        d_poll_t                *d_poll;    /* polls a device to see if there is data to be read or space available for writing */
        d_mmap_t                *d_mmap;
        d_strategy_t            *d_strategy;
        dumper_t                *d_dump;
        d_kqfilter_t            *d_kqfilter;
        d_purge_t               *d_purge;
        d_mmap_single_t         *d_mmap_single;

        int32_t                 d_spare0[3];
        void                    *d_spare1[3];

        /* These fields should not be messed with by drivers */
        LIST_HEAD(, cdev)       d_devs;
        int                     d_spare2;
        union {
                struct cdevsw           *gianttrick;
                SLIST_ENTRY(cdevsw)     postfree_list;
        } __d_giant;
};

Linuxでいうfile_operationsみたいな感じか?

単純なread/writeが可能なキャラクタデバイスの定義をする際はこんな感じ

static struct cd_example_cdevsw = {
    .d_version  = D_VERSION, /* defined in <sys/conf.h> */
    .d_open     = open,
    .d_close    = close,
    .d_read     = read,
    .d_write    = write,
    .d_name     = "cd_sxample"
};

指定されていない操作については,「操作はサポートされていない」という事になる(EOPNOTSUPP)

必須要素はd_versiond_nameだけ

Character Device Functions

cdevswの各エントリに対応する関数を実装することで,キャラクタデバイスとして機能する.

それらの型は<sys/conf.h>で定義されている.

例えばwriteを実装するならこんな感じ

d_write_t write;

int
write(struct cdev *dev, struct uio *uio, int ioflag)
{
    int error = 0;
    error = copyinstr(uio->uio_iov->iov_base, &buf, 512, &len);
    if (error != 0)
        uprintf("Write to \"cd_example\" failed.\n");
    return (error);
}

writeされると,ユーザ空間からカーネル空間のバッファへ文字列がコピーされる.

The Device Registration Routine

デバイスモジュールは,devfsに登録する必要がある.

今までのようなevent handler内で,make_devを呼び出す事で登録できる.

static struct cdev *sdev;

static int
load(struct module *module, int cmd, void *arg)
{
    int error = 0;
    switch (cmd) {
    case MOD_UNLOAD:
        sdev = make_dev(&cd_example_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "cd_example");
        uprintf("Character device loaded!\n");
        break;
    case MOD_UNLOAD:
        destroy(sdev);
        uprintf("Character device unloaded!\n");
        break;
    default:
        error = EOP_NOT_SUPP;
        break;
    }

    return (error);
}

逆にdestroyでデバイスは消える

こうする事で,/dev/cd_exampleが生える.

Example

簡単なキャラクタデバイスモジュールを実装する

#include <sys/param.h>
#include <sys/proc.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/uio.h>

#define BUFSIZE 512 + 1

d_open_t    open;
d_close_t   close;
d_read_t    read;
d_write_t   write;

static struct cdevsw cd_example_cdevsw = {
    .d_version  = D_VERSION,
    .d_open     = open,
    .d_close    = close,
    .d_read     = read,
    .d_write    = write,
    .d_name     = "cd_example"
};

static char buf[BUFSIZE];
static size_t len;

int
open(struct cdev *dev, int flag, int otyp, struct thread *td)
{
    memset(&buf, '\0', BUFSIZE);
    len = 0;

    return (0);
}

int
close(struct cdev *dev, int flag, int otyp, struct thread *td)
{
    return (0);
}

int
write(struct cdev *dev, struct uio *uio, int ioflag)
{
    int error = 0;

    error = copyinstr(uio->uio_iov->iov_base, &buf, 512, &len);
    if (error != 0)
        uprintf("Write to \"cd_example\" failed.\n");

    return (error);
}

int
read(struct cdev *dev, struct uio *uio, int ioflag)
{
    int error = 0;

    error = len <= 0 ? -1 : copystr(&buf, uio->uio_iov->iov_base, 513, &len);

    return (error);
}

static struct cdev *sdev;

static int
load(struct module *module, int cmd, void *arg)
{
    int error = 0;

    switch (cmd) {
    case MOD_LOAD:
        sdev = make_dev(&cd_example_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "cd_example");
        uprintf("Character device loaded.\n");
        break;
    case MOD_UNLOAD:
        destroy_dev(sdev);
        uprintf("Character device unloaded.\n");
        break;
    default:
        error = EOPNOTSUPP;
        break;
    }

    return (error);
}

DEV_MODULE(cd_example, load, NULL);

このコードでは,まずcdevswの各エントリとなる関数を登録し, buflenの2つのグローバル変数を定義している

bufはこのキャラクタデバイスの実体となるバッファで, lenはその大きさを示す.

DEV_MODULE<sys/conf.h>で以下の様に定義されている

#define DEV_MODULE(name, evh, arg)      \
    DEV_MODULE_ORDERD(name, evh, arg, SI_ORDER_MIDDLE)

#define DEV_MODULE_ORDERD(name, evh, arg, ord)      \
static moduledata_t name##_mod = {          \
    #name,                              \
    evh,                                \
    arg,                                \
};                              \
DECLARE_MODULE(name, name##_mod, SI_SUB_DRIVERS, ord)

ビルド,ロードすると,/dev/cd_exampleを確認できる

$ make
machine -> /usr/src/11/sys/amd64/include
x86 -> /usr/src/11/sys/x86/include
Warning: Object directory not changed from original /home/vagrant/src/cdev
cc -O2 -pipe  -fno-strict-aliasing -Werror -D_KERNEL -DKLD_MODULE -nostdinc   -I. -I/usr/src/11/sys -fno-common  -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer   -MD  -MF.depend.cdev.o -MTcdev.o -mcmodel=kernel -mno-red-zone -mno-mmx -mno-sse -msoft-float  -fno-asynchronous-unwind-tables -ffreestanding -fwrapv -fstack-protector -Wall -Wredundant-decls -Wnested-externs -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Winline -Wcast-qual -Wundef -Wno-pointer-sign -D__printf__=__freebsd_kprintf__ -Wmissing-include-dirs -fdiagnostics-show-option -Wno-unknown-pragmas -Wno-error-tautological-compare -Wno-error-empty-body -Wno-error-parentheses-equality -Wno-error-unused-function -Wno-error-pointer-sign -Wno-error-shift-negative-value -Wno-address-of-packed-member  -mno-aes -mno-avx  -std=iso9899:1999 -c cdev.c -o cdev.o
ld -m elf_x86_64_fbsd -d -warn-common -r -d -o cdev.ko cdev.o
:> export_syms
awk -f /usr/src/11/sys/conf/kmod_syms.awk cdev.ko  export_syms | xargs -J% objcopy % cdev.ko
objcopy --strip-debug cdev.ko
$ sudo kldload ./cdev.ko
Character device loaded.
$ ls /dev/ |grep cd_example
cd_example

実際に挙動を確認する

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <err.h>

#define DEVICE "/dev/cd_example"

int
main(int argc, char **argv)
{
    int len;
    int fd;
    if (argc != 2)
        return printf("Usage: %s <string>\n", *argv), 1;

    if ((len = strlen(argv[1])) +1 > 512)
        return printf("string too long\n"), 1;

    if ((fd = open(DEVICE, O_RDWR)) < 0)
        err(1, "open:");

    if (write(fd, argv[1], len) < 0)
        err(1, "write:");
    printf("write \"%s\" to %s succeed!\n", argv[1], DEVICE);

    char buf[len];
    if (read(fd, buf, len) < 0)
        err(1, "read:");

    printf("read \"%s\" from %s succeed!\n", buf, DEVICE);
}
$ gcc test.c -o test
$ sudo ./test
Usage: ./test <string>
$ sudo ./test "Hello, kernel!"
write "Hello, kernel!" to /dev/cd_example succeed!
read "Hello, kernel!" from /dev/cd_example succeed!

make_dev

$SYSDIR/kern/kern_conf.cにある

参考文献

Designing BSD Rootkits