「入門モダンLinux」を読んだ
2023-04-25
コンテナ周りの勉強をしていると分からない機能が多くこの書籍でキャッチアップできることを期待し読むことにした。具体的には以下のようなキーワードについて深く知りたいと考えていた。
eBPF
seccomp
cgroup
systemd
capabilities
目次
書籍自体は200
ページ強と結構薄く、広く浅めにカバーしている印象だった。
その分参考資料が多く掲載されているため、本書で概要を把握しつつより調べたい場合は参考資料を読んでいく。という使い方が自分にはあっていそう。
本書のみで知りたかった疑問が解消された。というケースは多くなかったが、とっかかりとしてとても良かったと感じた。特にlinux
周辺は個人的にはどこから調べたらいいかよくわからんな。ということも多いのでとても助かる。
参考資料についてはまだ、多くを読めてはいないが個人的に気になった点を記載しておく。
systemd
自分はsystemd
についての理解がぼんやりしており、かつ、日本語のまとまった資料もあまり見かけない気がする(「Software Design 2015年2月号のsystemd入門」は読んでいてよかった記憶がある)のでとても良かったと感じた。
具体的には概要を説明した後greeter
というHello world
的なUnit
を定義しsystemctl
やjournalctl
を使って実際に動かしてみる項目があるのだが、これが個人的にはよかった。
capabilities
capability
についての概要は把握したものの、いまいち具体的な話がイメージできていなかった。これについては参考資料として紹介されていた以下の記事がよかった。
この記事ではsetuid
がセットされたping
をcapabilities
を適用してくことでsetuid
なしで動作するping
にする例などいくつかの例がわかりやすく紹介されている。これにより、ふわふわしていたイメージがだいぶ固まったので良かった。また理解が怪しくなったらこの記事を読み直したい。
seccomp
seccomp
については以下の資料が紹介されていた。
前者はそれの歴史とBPF
について、後者はsecomp
を利用したsandbox
をアプリケーションコード
に手を入れずに適用していく話だ。
後者の記事が特に面白かったので備忘も兼ねて書いておく。
この記事はseccomp
の概要とuname
を使用したコードに以下のようなsandbox
を追加すればuname
をfilter
できるという説明から始まる。
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();
...
}
しかし上記コードはアプリケーション
に手をいれてしまっているので、これをどのように注入していくか。という話だ。
これに対して以下の方法が紹介されている。
systemd
のSystemCallFilter=``directive
を使用する方法dynamic linking
においてLD_PRELOAD
を使用する方法patchelf
でsandbox
を埋めてしまう方法static linking
においてsandboxify
を使用する方法
いずれもわかりやすく説明されており、とても良かった。
また、最後にはsandboxify vs libsandbox.so
として両者の違いが面白かった。
sandboxify
はruntime init
の前、libsandbox
は後になるので、その違いにより許可するシステムコール数に差がでるという話のようだ。
例として挙げているuname
を使用したアプリケーションで比較するとsandboxify
が13システムコールに対し、libsandbox
が4システムコールだったとの記載がある。
まとめ
各機能の入門、とっかかりとしてまとまっていてとても良かった。強いて言うならば「ネットワーク」の章は減らして、eBPF
あたりはもう少し書籍内書いてあってもいいんじゃないかとは感じた。が、そういった項目は参考記事を読んだり、他の書籍を読んでくれというのがこの書籍のスタンスのようにも見えるので別途補強していきたい。
以上。