System Call Modules

FreeBSDではシステムコールもモジュールで追加できる.

The System Call Function

システムコールの型は

typedef int     sy_call_t(struct thread *, void *);

struct thread *はそのまんま現在実行中のスレッドを指している.

void *はシステムコールの引数を指す.

以下に文字列を受け取って出力するシステムコールの例を示す

struct sc_example_args {
    char *str;
}

static int
sc_example(struct thread *td, void *syscall_args)
{
    struct sc_example_args *uap;
    uap = (struct sc_example_args *)syscall_args;
    
    printf("%s\n", uap->str);

    return (0);
}

システムコールの引数は,struct sc_example_argsのように構造体で定義し,システムコール内でvoid *ポインタをキャストする事で適切に扱う.

The sysent Structure

システムコールテーブルはsysent[]で,そのメンバはsysent構造体

sysent<sys/sysent.h>で定義されていて,

struct sysent {                 /* system call table */
        int     sy_narg;        /* number of arguments */
        sy_call_t *sy_call;     /* implementing function */
        au_event_t sy_auevent;  /* audit event associated with syscall */
        systrace_args_func_t sy_systrace_args_func;
                                /* optional argument conversion function. */
        u_int32_t sy_entry;     /* DTrace entry ID for systrace. */
        u_int32_t sy_return;    /* DTrace return ID for systrace. */
        u_int32_t sy_flags;     /* General flags for system calls. */
        u_int32_t sy_thrcnt;
};

sc_exampleでは

static struct sysent sc_example_sysent = {
    1,              /* number of arguments */
    sc_example      /* implementing function */
};

The Offset Value

sysent[]のオフセット(インデックス)がシステムコール番号で,0~550くらいまで(今の上限どれくらいだか調べてない)一意に割り当てられる.

モジュール内では,オフセットを明示的に宣言する必要があり,通常

static int offset NO_SYSCALL;

NO_SYSCALLは,sysent[]で使用可能な値,または開いている値に設定される.

自分で任意の番号を指定する事も出来るが,KLDは動的にロードされる為,追加するならよしなにしてもらった方が楽.

sys/kern/syscall.masterに使用済み/未使用の番号のリストがある.

The SYSCALL_MODULE Macro

DECLARE_MODULE同様,システムコールモジュールの登録用にもマクロがある.

DECLARE_MODULEでもいいけど,こっち使った方が便利.

<sys/sysent.h>で定義されてる

#define SYSCALL_MODULE(name, offset, new_sysent, evh, arg)  \
static struct syscall_module_data name##_syscall_mod = {    \
    evh, arg, offset, new_sysent, { 0, NULL, AUE_NULL } \
};                                  \
                                    \
static moduledata_t name##_mod = {      \
    "sys/" #name,                       \
    syscall_module_handler,             \
    &name##_syscall_mod                 \
};                                  \
DECLARE_MODULE(name, name##_mod, SI_SUB_SYSCALLS, SI_ORDER_MIDDLE)

で,syscall_module_data<sys/sysent.h>で定義されてて,

struct syscall_module_data {
        int     (*chainevh)(struct module *, int, void *); /* next handler */
        void    *chainarg;              /* arg for next event handler */
        int     *offset;                /* offset into sysent */
        struct sysent *new_sysent;      /* new sysent */
        struct sysent old_sysent;       /* old sysent */
        int     flags;                  /* flags for syscall_register */
};

SYSCALL_MODULEの各引数(name, offset, new_sysent, evh, arg)

name

一般的なモジュール名と同じ.モジュールの名前

offset

offset,さっきのシステムコール番号.

型はint *なので参照

new_sysent

struct sysent *

evh

event handler.

一般的なモジュールと同じ,イベントハンドラ.

arg

イベントハンドラに渡される引数.

ここでは常にNULLにする.

Example

試しに,文字列を受け取って出力するシステムコールを作る.

#include <sys/types.h>
#include <sys/param.h>
#include <sys/proc.h>
#include <sys/module.h>
#include <sys/sysent.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/sysproto.h>

struct sc_example_args {
    char *str;
};

static int
sc_example(struct thread *td, void *syscall_args)
{
    struct sc_example_args *uap;
    uap = (struct sc_example_args *)syscall_args;

    printf("%s\n", uap->str);

    return (0);
}

static struct sysent sc_example_sysent = {
    1,
    sc_example
};

static int offset = NO_SYSCALL;

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

    switch (cmd) {
    case MOD_LOAD:
        uprintf("System call loaded at offset %d\n", offset);
        break;
    case MOD_UNLAOD:
        uprintf("System call unloaded from offset %d\n", offset);
        break;
    default:
        error = EOPNOTSUPP;
        break;
    }

    return (error);
}

SYSCALL_MODULE(sc_example, &offset, &sc_example_sysent, load, NULL);
KMOD = sc_example
SRCS = sc_example.c
SYSDIR = /usr/src/11/sys

.include <bsd.kmod.mk>

ビルドしてロードするとオフセットを教えてくれる.

$ 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/syscallmod
cc -O2 -pipe -Wall  -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.sc_example.o -MTsc_example.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 sc_example.c -o sc_example.o
ld -m elf_x86_64_fbsd -d -warn-common -r -d -o sc_example.ko sc_example.o
:> export_syms
awk -f /usr/src/11/sys/conf/kmod_syms.awk sc_example.ko  export_syms | xargs -J% objcopy % sc_example.ko
objcopy --strip-debug sc_example.ko
$ sudo kldload ./sc_example.ko
System call loaded at offset 210.

modfind

せっかくシステムコールを作ったので,呼び出したい.

自作システムコールがどの番号で呼び出せるのかを知るため,まずはmodfind(2)を使ってモジュールを特定する.

modfindはモジュール名を元にロードされているモジュールを探し,見つかった場合固有のID(Modid)を返す.

#include <sys/param.h>
#include <sys/module.h>

int
modfind(const char *name);

注意が必要なのが,今回,SYSCALL_MODULEを使って定義したシステムコールのモジュール名は,マクロを読めばわかるように,元の名前のprefixにsys/を付けたものになっている.

modstat

modfindで入手した固有のIDをもとに,モジュールの情報を入手する.

#include <sys/param.h>
#include <sys/module.h>

int
modstat(int modid, struct module_stat *stat);

module_stat<sys/module.h>で定義されていて,こんな情報が含まれる

struct module_stat {
        int             version;        /* set to sizeof(struct module_stat) */
        char            name[MAXMODNAME];
        int             refs;
        int             id;
        modspecific_t   data;
};

/* snip */

typedef union modspecific {
        int     intval;
        u_int   uintval;
        long    longval;
        u_long  ulongval;
} modspecific_t;

ここで,先程の自作システムコールのmodstatを取るとdata.intvalがオフセット(システムコール番号)となる.

syscall

実際にユーザ空間から呼び出してみる.

#include <stdio.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/module.h>

int
main(int argc, char **argv)
{
    int syscall_num;

    struct module_stat stat;
    if (argc != 2)
        return printf("Usage: \n%s <string>\n", *argv), 1;

    stat.version = sizeof(stat);
    if (modstat(modfind("sys/sc_example"), &stat) < 0)
        return perror("modstat"), 1;

    syscall_num = stat.data.intval;

    return (syscall(syscall_num, argv[1]));
}

コンパイルして実行すると,

$ ./syscall "Hello, kernel!"
$ dmesg |tail -n 1
Hello, kernel!

ちなperlだと

$ perl -e '$str = "Hello, kernel!";' -e 'syscall(210, $str);'
$ dmesg | tail -n 1
Hello, kernel!

参考文献

Designing BSD Rootkits