strace
. Вот что случалось
при запуске strace
в Docker-контейнере на моем
ноутбуке:
$ docker run -it ubuntu:18.04 /bin/bash$ # ... install strace ...root@e27f594da870:/# strace lsstrace: ptrace(PTRACE_TRACEME, ...): Operation not permitted
strace
работает через системный вызов
ptrace
, поэтому без разрешения для ptrace
ничего не заработает! Но это легко исправить, и на моем ноутбуке я
все сделала вот так:
docker run --cap-add=SYS_PTRACE -it ubuntu:18.04 /bin/bash
Но мне было интересно не решить проблему, а разобраться, почему эта ситуация вообще возникает. Так почему же
strace
не
работает, а --cap-add=SYS_PTRACE
все исправляет?Гипотеза 1: У процессов контейнеров нет собственной привилегии
CAP_SYS_PTRACE
Так как проблема стабильно решается через
--cap-add=SYS_PTRACE
, мне всегда казалось, что у
процессов контейнеров Docker по определению нет собственной
привилегии CAP_SYS_PTRACE
, но по двум причинам тут
кое-что не сходится.Причина 1: В виде эксперимента я, будучи залогиненной под обычным пользователем, без труда могла запустить
strace
к любому процессу, однако проверка наличия у
моего текущего процесса привилегии CAP_SYS_PTRACE
ничего не находила:
$ getpcaps $$Capabilities for `11589': =
Причина 2: в
man capabilities
о привилегии
CAP_SYS_PTRACE
сказано следующее:
CAP_SYS_PTRACE * Trace arbitrary processes using ptrace(2);
Вся суть
CAP_SYS_PTRACE
заключается в том, чтобы мы,
по аналогии с root, могли перехватывать контроль над
произвольным процессом любого пользователя. Для
ptrace
обычного процесса вашего пользователя такая
привилегия не нужна.Кроме того, я провела еще одну проверку: я запустила Docker контейнер через
docker run --cap-add=SYS_PTRACE -it
ubuntu:18.04 /bin/bash
, затем отменила привилегию
CAP_SYS_PTRACE
и strace
продолжил
корректно работать даже без привилегии. Почему?!Гипотеза 2: Дело в пользовательском пространстве имен?
Моя следующая (и намного хуже обоснованная) гипотеза звучала в духе хмм, возможно процесс находится в другом пользовательском пространстве имен и
strace
не работает просто потому
что? Выглядит как набор не очень связных высказываний, но я все же
попробовала посмотреть на проблему и с этой стороны.Итак, находится ли процесс в другом пользовательском пространстве имен? Так он выглядит в контейнере:
root@e27f594da870:/# ls /proc/$$/ns/user -l... /proc/1/ns/user -> 'user:[4026531837]'
А так он выглядит на хосте:
bork@kiwi:~$ ls /proc/$$/ns/user -l... /proc/12177/ns/user -> 'user:[4026531837]'
root в контейнере это тот же самый пользователь, что и root на хосте, поскольку у них общий идентификатор в пользовательском пространстве имен (4026531837), так что с этой стороны не должно быть никаких мешающих работе
strace
причин. Как можно
видеть, гипотеза оказалась так себе, но тогда я еще не осознавала,
что пользователи в контейнере и на хосте совпадают, и этот поход
казался мне интересным.Гипотеза 3: Системный вызов ptrace
блокируется
правилом seccomp-bpf
Я уже знала, что для ограничения запуска большого числа системных вызовов процессорами контейнеров в Docker есть правило
seccomp-bpf
, и оказалось, что в его списке блокируемых
по определению вызовов есть и ptrace
! (На самом деле
список вызовов это лист исключений и ptrace
попросту в
него не попадает, но результат от этого не меняется.)Теперь понятно, почему в Docker контейнере не работает
strace
, ведь очевидно, что полностью заблокированный
ptrace
вызвать не получится.Давайте проверим эту гипотезу и посмотрим, сможем ли мы воспользоваться
strace
в Docker контейнере, если
отключим все правила seccomp:
$ docker run --security-opt seccomp=unconfined -it ubuntu:18.04 /bin/bash$ strace lsexecve("/bin/ls", ["ls"], 0x7ffc69a65580 /* 8 vars */) = 0... it works fine ...
Отлично! Все работает, и секрет раскрыт! Вот только
Почему --cap-add=SYS_PTRACE
решает проблему?
Мы все еще не объяснили почему
--cap-add=SYS_PTRACE
решает возникающую проблему с вызовами. Главная страница
docker run
следующим образом объясняет работу
аргумента --cap-add
:
--cap-add=[] Add Linux capabilities
Все это не имеет никакого отношения к правилам seccomp! В чем же дело?
Давайте посмотрим на исходный код Docker.
Если уже и документация не помогает, все что нам остается это погрузиться в исходники.
В Go есть одна приятная особенность: благодаря вендорингу зависимостей в репозитории Go вы через
grep
можете
пройтись по всему репозиторию и найти интересующий вас код. Так что
я склонировала github.com/moby/moby
и прошерстила его
в поисках выражений вида rg CAP_SYS_PTRACE
.На мой взгляд, тут происходит вот что: в имплементации seccomp в контейнере, в разделе contrib/seccomp/seccomp_default.go полно кода, который через правило 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 и для profiles/seccomp/seccomp.go, и для seccomp профиля по определению проводит похожие операции, так что, наверное, мы нашли наш ответ!
В Docker --cap-add
способен на большее, чем
сказано
В итоге, похоже,
--cap-add
делает не совсем то, что
написано на главной странице, и скорее должен выглядеть как
--cap-add-and-also-whitelist-some-extra-system-calls-if-required
.
И это похоже на правду: если у вас есть привилегия в духе
CAP_SYS_PTRACE
, которая разрешает вам пользоваться
системным вызовом process_vm_readv
, но этот вызов
блокируется профилем seccomp, вам это мало чем поможет, так что
разрешение на использование системных вызовов
process_vm_readv
и ptrace
через
CAP_SYS_PTRACE
выглядит разумно.Оказывается, strace
работает в последних версиях
Docker
Для версий ядра 4.8 и выше благодаря этому коммиту в Docker 19.03 наконец-то разрешены системные вызовы
ptrace
.
Вот только на моем ноутбуке Docker все еще версии 18.09.7, и этот
коммит, очевидно, отсутствует.Вот и все!
Разбираться с этой проблемой оказалось интересно, и я думаю, что это хороший пример нетривиально взаимодействующей между собой подвижной начинки контейнеров.
Если вам понравился этот пост, вам может понравиться и мой журнал How Containers Work, на его 24 страницах объясняются особенности ядра Linux по организации работы контейнеров. Там же вы можете ознакомиться с привилегиями и seccomp-bpf.