¿Por qué Docker no funciona en Docker?

Cuando estaba editando la página de capacidades del contenedor para la revista How Containers Work , necesitaba explicar por qué Docker no funciona strace. Esto es lo que sucedió cuando se ejecutó straceen el contenedor Docker en mi computadora portátil:



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


stracefunciona a través de una llamada al sistema ptrace, por ptracelo que no funcionará sin permiso . Pero esto es fácil de solucionar, y en mi computadora portátil hice todo lo siguiente:



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


Pero fue interesante para mí no resolver el problema, sino descubrir por qué generalmente surge esta situación. Entonces, ¿por qué straceno funciona, sino que --cap-add=SYS_PTRACEcorrige todo?



Hipótesis 1: los procesos de contenedores no tienen su propio privilegio CAP_SYS_PTRACE



Como el problema se resuelve de manera estable --cap-add=SYS_PTRACE, siempre me pareció que los procesos de contenedor Docker, por definición, no tienen sus propios privilegios CAP_SYS_PTRACE, pero por dos razones, algo no converge aquí.



Razón 1: Como experimento, straceal iniciar sesión como usuario normal, podría iniciar fácilmente cualquier proceso, pero comprobar si mi proceso actual tenía privilegios CAP_SYS_PTRACEno encontró nada:



$ getpcaps $$
Capabilities for `11589': =


Razón 2: en man capabilitiesel privilegio se CAP_SYS_PTRACElee lo siguiente:



CAP_SYS_PTRACE
       * Trace arbitrary processes using ptrace(2);


El punto CAP_SYS_PTRACEes que, por analogía con la raíz, podemos tomar el control del proceso arbitrario de cualquier usuario. Para el ptraceproceso normal de su usuario, este privilegio no es necesario.



Además, realicé una comprobación más: inicié el contenedor Docker docker run --cap-add=SYS_PTRACE -it ubuntu:18.04 /bin/bash, luego revoqué el privilegio CAP_SYS_PTRACEy stracecontinué trabajando correctamente incluso sin el privilegio. ¡¿Por qué?!



Hipótesis 2: ¿Es un espacio de nombres personalizado?



Mi siguiente (y mucho peor) hipótesis sonó como "hmm, tal vez el proceso está en un espacio de nombre de usuario diferente y straceno funciona ... ¿solo porque?" Parece un conjunto de declaraciones no muy coherentes, pero aún intenté ver el problema desde este lado.



Entonces, ¿está el proceso en un espacio de nombres diferente definido por el usuario? Así es como se ve en el contenedor:



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


Y así es como se ve en el host:



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


root en el contenedor es el mismo usuario que root en el host, ya que comparten un nombre de usuario común en el espacio de nombres de usuario (4026531837), por lo que no debería haber ninguna stracerazón de interferencia de ese lado . Como puede ver, la hipótesis resultó ser regular, pero aún no me di cuenta de que los usuarios en el contenedor y en el host son los mismos, y este enfoque me pareció interesante.



Hipótesis 3: la llamada al sistema está ptracebloqueada por una reglaseccomp-bpf



Ya sabía que hay una regla en Docker para restringir una gran cantidad de llamadas al sistema que deben ejecutar los procesadores de contenedores en Docker seccomp-bpf, ¡y resultó que en su lista de llamadas están bloqueadas por definición ptrace! (De hecho, la lista de llamadas es una lista de excepciones y ptracesimplemente no entra, pero el resultado no cambia).



Ahora está claro por qué no funciona en un contenedor Docker strace, porque es obvio que una ptracellamada completamente bloqueada no funcionará.



Probemos esta hipótesis y veamos si podemos usar el stracecontenedor en el Docker si deshabilitamos todas las reglas de 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 ...


¡Multa! ¡Todo funciona y se revela el secreto! Eso es solo ...



¿ --cap-add=SYS_PTRACEPor qué resuelve el problema?



Todavía no hemos explicado por qué --cap-add=SYS_PTRACEresuelve el problema emergente con las llamadas. La página principal docker runexplica cómo funciona el argumento de la siguiente manera --cap-add:



--cap-add=[]
   Add Linux capabilities


¡Nada de esto tiene nada que ver con las reglas de seccomp! ¿Cuál es el problema?



Echemos un vistazo al código fuente de Docker.



Si la documentación no ayuda, todo lo que tenemos que hacer es sumergirnos en las fuentes.

Una cosa buena de Go es que al vender dependencias en un repositorio de Go, greppuede recorrer todo el repositorio y encontrar el código que le interesa. Así que lo github.com/moby/mobycloné y lo busqué en busca de expresiones de ese tipo rg CAP_SYS_PTRACE.



En mi opinión, esto es lo que sucede aquí: en la implementación de seccomp en el contenedor, en la sección contrib / seccomp / seccomp_default.go, hay mucho código que, a través de la regla seccomp, verifica si un proceso con privilegios tiene permiso para usar llamadas al sistema de acuerdo con este privilegio.



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




Todavía hay código en moby que para profiles / seccomp / seccomp.go y para perfiles seccomp, por definición, lleva a cabo operaciones similares, por lo que probablemente encontramos nuestra respuesta.



Docker --cap-addes capaz de más de lo que se dice.



Al final, parece que --cap-addno es exactamente lo que dice en la página principal, y más bien debería verse --cap-add-and-also-whitelist-some-extra-system-calls-if-required. Y parece ser cierto: si tiene el privilegio del espíritu CAP_SYS_PTRACE, que le permite usar una llamada del sistema process_vm_readv, pero la llamada está bloqueada en el perfil de Seccomp, no es de mucha ayuda, por lo que la autorización para usar las llamadas del sistema process_vm_readvy a ptracetravés de CAP_SYS_PTRACEparece razonable.



Resulta que stracefunciona en las últimas versiones de Docker



Para las versiones de kernel 4.8 y superiores, gracias a este commit , Docker 19.03 finalmente permitió llamadas al sistema ptrace. Eso es solo en mi computadora portátil Docker, todavía hay la versión 18.09.7, y esta confirmación obviamente falta.



¡Eso es todo!



Resultó ser interesante tratar este problema, y ​​creo que este es un buen ejemplo de un "llenado" de contenedores en movimiento que no interactúa de manera no trivial.



Si le gustó esta publicación, puede que le guste mi revista " Cómo funcionan los contenedores ", sus 24 páginas explican las características del kernel de Linux para organizar el trabajo de contenedores. También puede ver privilegios y seccomp-bpf allí .



All Articles