Linuxカーネルのソースコードを読む(その1:dmesg)

Linux カーネルソースコードを読んだのでメモを公開します.Linuxカーネルを読む際の参考になれば幸いです.

今回のお題: dmesg コマンド

今回は dmesg コマンドに関係する部分を読みました.printkや /dev/kmsg 等が関係する部分です.

ソースコードのバージョンは linux-4.20.3 です.

/bin/dmesg の仕組み

/bin/dmesg は Linux カーネルが出力したログを見るためのコマンドです.

Linuxカーネルはログを出力するために printk() という関数を用意しています.printk()は printf() のカーネル版です.カーネルの中でprintk()で文字列を出力すると,その文字列はカーネル内部にあるリングバッファに記録されます.printk()は様々なところで使用されています.たとえばカーネルモジュール(デバイスドライバ)等は printk()を使って,デバイスの認識状況,デバッグ情報,動作ログを,逐次出力しています.

/bin/dmesg はこのバッファを読むための userland 側のツールです.userland(ユーザ側)からはカーネルのメモリ空間は直接アクセスできません.そこでカーネルは /dev/kmsg というデバイスファイルを用意して,ファイルとしてリングバッファをアクセスできるような仕組みを用意しています.

kernel/printk/printk.c を読む

printk()のソースコードは kernel/printk/printk.c にあります.リングバッファの実装,/dev/kmsg の実装も同じファイルにあります.

リングバッファは 1747行目で定義されています.

1747:
/*
 * Continuation lines are buffered, and not committed to the record buffer
 * until the line is complete, or a race forces it. The line fragments
 * though, are printed immediately to the consoles to ensure everything has
 * reached the console in case of a kernel crash.
 */
static struct cont {
        char buf[LOG_LINE_MAX];
        size_t len;                     /* length == 0 means unused buffer */
        struct task_struct *owner;      /* task of first print*/
        u64 ts_nsec;                    /* time of first print */
        u8 level;                       /* log level of first message */
        u8 facility;                    /* log facility of first message */
        enum log_flags flags;           /* prefix, newline flags */
} cont;

このリングバッファは,ユーザ空間側には /dev/kmsg としてマッピングされています.993行目に file_operations 構造体があります.

993:
const struct file_operations = {
        .open = devkmsg_open,
        .read = devkmsg_read,
        .write_iter = devkmsg_write,
        .llseek = devkmsg_llseek,
        .poll = devkmsg_poll,
        .release = devkmsg_release,
};

つまり /bin/dmesg は /dev/kmsg を開いて,ファイルとしてリングバッファの中身を読み出していることになります.

/bin/dmesg は先ず open(2) システムコールで /dev/kmsg をopenします.openシステムコールが発行されると,カーネルは file_operations の設定に従って devkmsg_open関数を起動します.

devkmsg_open()関数は 945行目にあります.

945:
static int devkmsg_open(struct inode *inode, struct file *file)
{
        struct devkmsg_user *user;
        int err;

        if (devkmsg_log & DEVKMSG_LOG_MASK_OFF)
                return -EPERM;

        /* write-only does not need any file context */
        if ((file->f_flags & O_ACCMODE) != O_WRONLY) {
                err = check_syslog_permissions(SYSLOG_ACTION_READ_ALL,
                                               SYSLOG_FROM_READER);
                if (err)
                        return err;
        }

/dev/kmsg のパーミッションの確認をしているのは check_syslog_permissions()であることが解ります.更に読み進めます.

651:
static int check_syslog_permissions(int type, int source)
{
        /*
         * If this is from /proc/kmsg and we've already opened it, then we've
         * already done the capabilities checks at open time.
         */
        if (source == SYSLOG_FROM_PROC && type != SYSLOG_ACTION_OPEN)
                goto ok;

        if (syslog_action_restricted(type)) {
                if (capable(CAP_SYSLOG))
                        goto ok;
                /*
                 * For historical reasons, accept CAP_SYS_ADMIN too, with
                 * a warning.
                 */
                if (capable(CAP_SYS_ADMIN)) {
                        pr_warn_once("%s (%d): Attempt to access syslog with "
                                     "CAP_SYS_ADMIN but no CAP_SYSLOG "
                                     "(deprecated).\n",
                                 current->comm, task_pid_nr(current));
                        goto ok;
                }
                return -EPERM;
        }
ok:
        return security_syslog(type);
}

syslog_action_restricted()をコールしています

636:
int dmesg_restrict = IS_ENABLED(CONFIG_SECURITY_DMESG_RESTRICT);
 
static int syslog_action_restricted(int type)
{
          if (dmesg_restrict)
                 return 1;

syslog_action_restricte は dmesg_restrict という変数をチェックしています.

dmesg_restrict変数はkernel/printk/printk.cの637行目で定義されています.

IS_ENABLED()はマクロです.このマクロは include/linux/kconfig.h で定義されています.

66:
/*
 * IS_ENABLED(CONFIG_FOO) evaluates to 1 if CONFIG_FOO is set to 'y' or 'm',
 * 0 otherwise.
 */
#define IS_ENABLED(option) __or(IS_BUILTIN(option), IS_MODULE(option))

IS_ENABLED()は,カーネルの設定ファイル .config 内で CONFIG_SECURITY_DMESG_RESTRICT が定義されていれば 1 に定義されていなければ 0 に展開されます.

IS_ENABLED()が決定するのは dmesg_restrict 変数の初期値です.値を変更するには /sbin/sysctl を使う方法などがあります.

参考:dmesg のアクセス制限を外す方法 - pyopyopyo - Linuxとかプログラミングの覚え書き -


また "For historical reasons, accept CAP_SYS_ADMIN too, with a warning."というコメントがあるように,CAP_SYS_ADMIN があればroot権限がなくても dmesg できそうでね.

まとめ

dmesgに関するカーネルソースを読みました.

  • /bin/dmesg は /dev/kmsg をopen(2)している
  • /dev/kmsgの実装はカーネルの中, kernel/printk/printk.c にある
  • dmesg_restrictという変数で dmesg のアクセス制限を On/Off できる.
  • CAP_SYS_ADMIN があればroot権限がなくても dmesg できそう(ただし非奨励)

ということが解りました.