Por que o Docker não funciona no Docker?

Quando eu estava editando a página de recursos de contêiner da revista How Containers Work , eu precisava explicar por que o Docker não funciona strace. Aqui está o que aconteceu ao executar straceem um contêiner Docker no meu laptop:



$ docker run  -it ubuntu:18.04 /bin/bash
$ # ... install strace ...
root@e27f594da870:/# strace ls
strace: ptrace(PTRACE_TRACEME, ...): Operation not permitted


stracefunciona através de uma chamada do sistema ptrace, para ptraceque não funcione sem permissão ! Mas isso é fácil de consertar, e no meu laptop eu fiz tudo assim:



docker run --cap-add=SYS_PTRACE  -it ubuntu:18.04 /bin/bash


Mas foi interessante para mim não resolver o problema, mas descobrir por que essa situação geralmente surge. Então, por que stracenão funciona, mas --cap-add=SYS_PTRACEcorrige tudo?



Hipótese 1: Os processos de contêiner não têm seus próprios privilégios CAP_SYS_PTRACE



Como o problema é resolvido de maneira consistente --cap-add=SYS_PTRACE, sempre me pareceu que os processos de contêiner do Docker, por definição, não têm seus próprios privilégios CAP_SYS_PTRACE, mas por dois motivos, algo não se soma aqui.



Razão 1: como experiência, eu, sendo logado como usuário comum, poderia iniciar stracequalquer processo com facilidade , mas CAP_SYS_PTRACEnão encontrei nada no privilégio do meu processo atual :



$ getpcaps $$
Capabilities for `11589': =


Razão 2: no man capabilitiesprivilégio, CAP_SYS_PTRACEleia o seguinte:



CAP_SYS_PTRACE
       * Trace arbitrary processes using ptrace(2);


O ponto principal CAP_SYS_PTRACEé que, por analogia com o root, podemos assumir o controle do processo arbitrário de qualquer usuário. Para ptraceseu usuário, esse privilégio não precisa de um processo convencional.



Além disso, realizei mais uma verificação: iniciei o contêiner do Docker docker run --cap-add=SYS_PTRACE -it ubuntu:18.04 /bin/bashe revoguei o privilégio CAP_SYS_PTRACE- e stracecontinuei a funcionar corretamente mesmo sem o privilégio. Por quê?!



Hipótese 2: Caso no espaço para nome do usuário?



Minha próxima hipótese (e muito menos fundamentada) soou como "hmm, talvez o processo esteja em um espaço para nome de usuário diferente e stracenão funcione ... só porque?" Parece um conjunto de declarações não muito coerentes, mas ainda tentei analisar o problema desse lado.



Então, o processo está em um namespace de usuário diferente? É assim que fica no contêiner:



root@e27f594da870:/# ls /proc/$$/ns/user -l
... /proc/1/ns/user -> 'user:[4026531837]'


E é assim que fica no host:



bork@kiwi:~$ ls /proc/$$/ns/user -l
... /proc/12177/ns/user -> 'user:[4026531837]'


raiz no contêiner é o mesmo usuário que raiz no host, pois eles têm um identificador de espaço de nome de usuário comum (4026531837), portanto, não deve haver stracemotivos de interferência nesse lado . Como você pode ver, a hipótese acabou sendo mais ou menos, mas ainda não percebi que os usuários no contêiner e no host são os mesmos, e essa abordagem me pareceu interessante.



Hipótese 3: a chamada do sistema é ptracebloqueada por uma regraseccomp-bpf



Eu já sabia que para limitar o lançamento de um grande número de chamadas de sistema por processadores de contêiner no Docker, existe uma regra seccomp-bpf, e constatou-se que em sua lista de chamadas bloqueadas por definição, também existem ptrace! (De fato, a lista de chamadas é uma lista de exceção e ptracesimplesmente não entra nela, mas o resultado não muda.)



Agora está claro por que o contêiner não funciona em um contêiner do Docker strace, porque é óbvio que uma ptracechamada completamente bloqueada não funcionará.



Vamos testar esta hipótese e ver se podemos usar straceo contêiner do Docker se desativarmos todas as regras do seccomp:



$ docker run --security-opt seccomp=unconfined -it ubuntu:18.04  /bin/bash
$ strace ls
execve("/bin/ls", ["ls"], 0x7ffc69a65580 /* 8 vars */) = 0
... it works fine ...


Bem! Tudo funciona e o segredo é revelado! Isso é só ...



Por --cap-add=SYS_PTRACEque resolve o problema?



Ainda não explicamos por que ele --cap-add=SYS_PTRACEresolve o problema do desafio emergente. A página principal docker runexplica como o argumento funciona da seguinte maneira --cap-add:



--cap-add=[]
   Add Linux capabilities


Nada disso tem nada a ver com regras seccomp! Qual é o problema?



Vamos dar uma olhada no código fonte do Docker.



Se a documentação ainda não ajudar, tudo o que resta para nós é mergulhar na fonte.

O Go possui um recurso interessante: graças à venda de dependência no repositório Go, você greppode percorrer todo o repositório e encontrar o código no qual está interessado. Então eu o github.com/moby/mobyclonei e o vasculhei por expressões do tipo rg CAP_SYS_PTRACE.



Na minha opinião, é o que acontece aqui: na implementação do seccomp no contêiner, na seção contrib / seccomp / seccomp_default.go, há muito código que, através da regra seccomp, verifica se um processo com privilégios tem permissão para usar chamadas de sistema de acordo com esse privilégio.



		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{},
			})




Ainda existe um código no moby que para profiles / seccomp / seccomp.go e seccomp, por definição, executa operações semelhantes, portanto, provavelmente encontramos nossa resposta!



O Docker --cap-addpode fazer mais do que o dito



No final, parece que --cap-addnão faz exatamente o que diz na página principal e deve parecer --cap-add-and-also-whitelist-some-extra-system-calls-if-required. E isso parece ser verdade: se você tem um privilégio como CAP_SYS_PTRACEesse que permite que você use uma chamada de sistema process_vm_readv, mas essa chamada é bloqueado pelo perfil seccomp, que não ajudar muito, permitindo assim chamadas do sistema através de process_vm_readve ptraceatravés de CAP_SYS_PTRACEolhares razoáveis.



Acontece que stracefunciona nas versões mais recentes do Docker



Nas versões 4.8 e superior do kernel, graças a essa confirmação, as chamadas do sistema são finalmente permitidas no Docker 19.03 ptrace. Exceto que, no meu laptop, o Docker ainda é a versão 18.09.7, e esse commit está obviamente ausente.



Isso é tudo!



Acabou sendo interessante lidar com esse problema, e acho que esse é um bom exemplo de "preenchimento" móvel de interação não trivial.



Se você gostou deste post, também pode gostar da minha revista How Containers Work , que explica os recursos de manipulação de contêiner de 24 páginas do kernel Linux. Você também pode ver privilégios e seccomp-bpf lá .



All Articles