Skip to content
On this page

「入門モダンLinux」を読んだ

2023-04-25

コンテナ周りの勉強をしていると分からない機能が多くこの書籍でキャッチアップできることを期待し読むことにした。具体的には以下のようなキーワードについて深く知りたいと考えていた。

  • eBPF
  • seccomp
  • cgroup
  • systemd
  • capabilities

目次

書籍自体は200ページ強と結構薄く、広く浅めにカバーしている印象だった。
その分参考資料が多く掲載されているため、本書で概要を把握しつつより調べたい場合は参考資料を読んでいく。という使い方が自分にはあっていそう。

本書のみで知りたかった疑問が解消された。というケースは多くなかったが、とっかかりとしてとても良かったと感じた。特にlinux周辺は個人的にはどこから調べたらいいかよくわからんな。ということも多いのでとても助かる。

参考資料についてはまだ、多くを読めてはいないが個人的に気になった点を記載しておく。

systemd

自分はsystemdについての理解がぼんやりしており、かつ、日本語のまとまった資料もあまり見かけない気がする(「Software Design 2015年2月号のsystemd入門」は読んでいてよかった記憶がある)のでとても良かったと感じた。

具体的には概要を説明した後greeterというHello world的なUnitを定義しsystemctljournalctlを使って実際に動かしてみる項目があるのだが、これが個人的にはよかった。

capabilities

capabilityについての概要は把握したものの、いまいち具体的な話がイメージできていなかった。これについては参考資料として紹介されていた以下の記事がよかった。

この記事ではsetuidがセットされたpingcapabilitiesを適用してくことでsetuidなしで動作するpingにする例などいくつかの例がわかりやすく紹介されている。これにより、ふわふわしていたイメージがだいぶ固まったので良かった。また理解が怪しくなったらこの記事を読み直したい。

seccomp

seccompについては以下の資料が紹介されていた。
前者はそれの歴史とBPFについて、後者はsecompを利用したsandboxアプリケーションコードに手を入れずに適用していく話だ。

後者の記事が特に面白かったので備忘も兼ねて書いておく。

この記事はseccompの概要とunameを使用したコードに以下のようなsandboxを追加すればunamefilterできるという説明から始まる。

c
static void sandbox(void)
{
    struct sock_filter filter[] = {
        /* seccomp(2) says we should always check the arch */
        /* as syscalls may have different numbers on different architectures */
        /* see https://fedora.juszkiewicz.com.pl/syscalls.html */
        /* for simplicity we only allow x86_64 */
        BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, arch))),
        /* if not x86_64, tell the kernel to kill the process */
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 0, 4),
        /* get the actual syscall number */
        BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, nr))),
        /* if "uname", tell the kernel to return EPERM, otherwise just allow */
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SYS_uname, 0, 1),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (EPERM & SECCOMP_RET_DATA)),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
    };

    struct sock_fprog prog = {
        .len = (unsigned short) (sizeof(filter) / sizeof(filter[0])),
        .filter = filter,
    };

    /* see seccomp(2) on why this is needed */
    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
        perror("PR_SET_NO_NEW_PRIVS failed");
        exit(1);
    };

    /* glibc does not have a wrapper for seccomp(2) */
    /* invoke it via the generic syscall wrapper */
    if (syscall(SYS_seccomp, SECCOMP_SET_MODE_FILTER, 0, &prog)) {
        perror("seccomp failed");
        exit(1);
    };
}

int main(void)
{
    ...
    sandbox();
    ...
}

しかし上記コードはアプリケーションに手をいれてしまっているので、これをどのように注入していくか。という話だ。

これに対して以下の方法が紹介されている。

  • systemdSystemCallFilter=``directiveを使用する方法
  • dynamic linkingにおいてLD_PRELOADを使用する方法
  • patchelfsandboxを埋めてしまう方法
  • static linkingにおいてsandboxifyを使用する方法

いずれもわかりやすく説明されており、とても良かった。
また、最後にはsandboxify vs libsandbox.soとして両者の違いが面白かった。

sandboxifyruntime initの前、libsandboxは後になるので、その違いにより許可するシステムコール数に差がでるという話のようだ。
例として挙げているunameを使用したアプリケーションで比較するとsandboxifyが13システムコールに対し、libsandboxが4システムコールだったとの記載がある。

まとめ

各機能の入門、とっかかりとしてまとまっていてとても良かった。強いて言うならば「ネットワーク」の章は減らして、eBPFあたりはもう少し書籍内書いてあってもいいんじゃないかとは感じた。が、そういった項目は参考記事を読んだり、他の書籍を読んでくれというのがこの書籍のスタンスのようにも見えるので別途補強していきたい。

以上。