ファンジン「How Containers Work」のcapabilitiesページを編集していて、Dockerコンテナでstraceが動作しない理由を説明したくなりました。
ラップトップのDockerコンテナでstraceを実行すると、こうなります:
$ docker run -it ubuntu:18.04 /bin/bash
$ # ... install strace ...
[email protected]:/# strace ls
strace: ptrace(PTRACE_TRACEME, ...): Operation not permitted
docker run --cap-add=SYS_PTRACE -it ubuntu:18.04 /bin/bash
しかし、私は直し方に興味があるのではなく、なぜこのようなことが起こるのかを知りたいのです。なぜstraceが機能しないのか、なぜ--cap-add=SYS_PTRACE これを修正できるのか。
前提1:コンテナプロセスには CAP_SYS_PTRACE 能力がない
私はずっと、DockerコンテナプロセスにはデフォルトでCAP_SYS_PTRACE機能がないからだと思っていました。それは、 --cap-add=SYS_PTRACE であることと同じことですよね?
しかし、これには2つの理由があります。
$ getpcaps $$
Capabilities for `11589': =
理由2capabilities マニュアルページでは、 CAP_SYS_PTRACE 次のように説明されています:
CAP_SYS_PTRACE
* Trace arbitrary processes using ptrace(2);
つまり、CAP_SYS_PTRACEは、rootがそうであるように、どのユーザが所有するプロセスでもトレースできるようにするものです。
仮説2:ユーザーネームスペースに関する何か?
コンテナ・プロセスは異なるユーザー・ネームスペースにありますか?コンテナ内では
root@eda870:/# ls /proc/$$/ns/user -l
... /proc/1/ns/user -> 'user:'
ホスト内の
bork@kiwi:~$ ls /proc/$$/ns/user -l
... /proc/12177/ns/user -> 'user:'
ユーザー・ネームスペース ID() が同じなので、コンテナ内のルート・ユーザーはホスト上のルート・ユーザーとまったく同じユーザーです。そのため、コンテナが作成するプロセスを追跡できない理由はまったくありません!
この仮定はあまり意味がないのですが、Dockerコンテナ内のrootユーザがホスト上のrootユーザと同じだとは知らなかったので、面白いと思いました。
仮定3:ptraceシステムコールはseccomp-bpfルールによってブロックされます。
Dockerがseccomp-bpfを使ってコンテナプロセスが多くのシステムコールを実行しないようにしていることも知っています。そしてptraceはDockerのデフォルトのseccompプロファイルでブロックされるシステムコールのリストにあります!
Dockerコンテナでstraceが動作しない理由を説明するのは簡単です。ptraceシステムコールが完全にブロックされていれば、当然それを呼び出すことはできず、straceは失敗します。
seccompルールをすべて無効にした場合、Dockerコンテナでstraceは動作するのでしょうか?
$ docker run --security-opt seccomp=unconfined -it ubuntu:18.04 /bin/bash
$ strace ls
execve("/bin/ls", ["ls"], 0x7ffc /* 8 vars */) = 0
... it works fine ...
はい!よかった謎は解けました。
なぜ --cap-add=SYS_PTRACE 問題を解決できるのですか?
まだ説明されていないのは、「なぜ --cap-add=SYS_PTRACE この問題を解決できるのか?
docker run マニュアルページでは、 --cap-add パラメータについてこのように説明しています。
--cap-add=[]
Add Linux capabilities
セコムのルールとは関係ありません! どうしたんですか?
Dockerのソースコードを見てみましょう。
ドキュメントが役に立たないときは、ソースコードを見るしかありません。
Go言語のいいところは、依存関係がたいていGoのリポジトリにあるので、grepで何かをするコードがどこにあるかを見つけられることです。そこでgithub.com/mobyをクローンして、rg CAP_SYS_PTRACEなどとgrepしてみました。
このケースだと思います。 containerdのseccomp実装では、プロセスがケイパビリティを持つ場合、そのケイパビリティに関連するシステムコールを使用するためのアクセス権も得られるようにするためのコードがたくさんあります。
case "CAP_SYS_PTRACE":
s.Syscalls = append(s.Syscalls, specs.LinuxSyscall{
Names: []string{
"kcmp",
"process_vm_readv",
"process_vm_writev",
"ptrace",
Action: specs.ActAllow,
Args: []specs.LinuxSeccompArg{},
mobyのatや デフォルトのseccompプロファイルにも似たようなことをするコードがあります。
だから答えがあると思います!
Dockerの --cap-add 、その言葉以上のことをします。
結局のところ、-cap-addはマニュアルのページに書いてあるようなものではなく、 --cap-add-and-also-whiteelist-some-extra-system-calls-if-requiredようなものだということのようです。これはとても理にかなっています! これは、--CAP_SYS_PTRACEのようにprocess_vm_readvシステムコールを使えるようにする機能があっても、そのシステムコールがseccomp設定ファイルによってブロックされている場合には役に立ちません!
ですから、コンテナに CAP_SYS_PTRACE 機能を持たせる場合、 process_vm_readv ptraceシステムコールの使用を許可することは、合理的な選択のように思えます。
以上です!
コンテナがいかに多くの可動部品で構成され、それがまったく明白でない形で連動しているのかを示す良い例だと思います。
を経由して





