Pourquoi Strace ne fonctionne pas dans Docker

Lorsque je modifiais la page des capacités de conteneur pour le magazine How Containers Work , j'avais besoin d'expliquer pourquoi Docker ne fonctionne pas strace. Voici ce qui s'est passé lors de l'exécution stracedans le conteneur Docker sur mon ordinateur portable:



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


stracefonctionne grâce à un appel système ptrace, donc ptracecela ne fonctionnera pour rien sans permission ! Mais c'est facile à réparer, et sur mon ordinateur portable, je l'ai fait comme ceci:



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


Mais je n'étais pas intéressé à résoudre le problème, mais à comprendre pourquoi cette situation se présente. Alors pourquoi ça stracene marche pas, mais ça --cap-add=SYS_PTRACEcorrige tout?



Hypothèse 1: les processus de conteneur n'ont pas leur propre privilège CAP_SYS_PTRACE



Le problème étant résolu de manière cohérente --cap-add=SYS_PTRACE, il m'a toujours semblé que les processus de conteneur Docker, par définition, n'avaient pas leur propre privilège CAP_SYS_PTRACE, mais pour deux raisons, quelque chose ne s'additionnait pas ici.



Raison 1: à titre expérimental, je, étant connecté en tant qu'utilisateur régulier, pourrais facilement démarrer stracen'importe quel processus, cependant, je CAP_SYS_PTRACEn'ai rien trouvé dans le privilège de mon processus actuel :



$ getpcaps $$
Capabilities for `11589': =


Raison 2: dans man capabilitiesle privilège se CAP_SYS_PTRACElit comme suit:



CAP_SYS_PTRACE
       * Trace arbitrary processes using ptrace(2);


Le fait CAP_SYS_PTRACEest que, par analogie avec root, nous pouvons prendre le contrôle du processus arbitraire de n'importe quel utilisateur. Pour ptracevotre utilisateur, ce privilège n'a pas besoin d'un processus conventionnel.



De plus, j'ai effectué une autre vérification: j'ai lancé le conteneur Docker docker run --cap-add=SYS_PTRACE -it ubuntu:18.04 /bin/bash, puis j'ai révoqué le privilège CAP_SYS_PTRACE- et j'ai stracecontinué à fonctionner correctement même sans le privilège. Pourquoi?!



Hypothèse 2: s'agit-il d'un espace de noms personnalisé?



Mon hypothèse suivante (et beaucoup moins fondée) ressemblait à "hmm, peut-être que le processus est dans un espace de noms d'utilisateur différent et stracene fonctionne pas ... juste parce que?" Cela ressemble à un ensemble de déclarations pas très cohérentes, mais j'ai quand même essayé de regarder le problème de ce point de vue.



Ainsi, le processus est-il dans un espace de noms différent défini par l'utilisateur? Voici à quoi cela ressemble dans le conteneur:



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


Et voici Ă  quoi cela ressemble sur l'hĂ´te:



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


root dans le conteneur est le même utilisateur que root sur l'hôte, car ils ont un identifiant d'espace de nom d'utilisateur commun (4026531837), il ne devrait donc pas y avoir de straceraisons interférentes de ce côté . Comme vous pouvez le voir, l'hypothèse s'est avérée médiocre, mais je ne me suis pas encore rendu compte que les utilisateurs dans le conteneur et sur l'hôte sont les mêmes, et cette approche m'a semblé intéressante.



Hypothèse 3: l'appel système est ptracebloqué par une règleseccomp-bpf



Je savais déjà qu'il existe une règle dans Docker pour restreindre un grand nombre d'appels système à exécuter par les processeurs de conteneurs dans Docker seccomp-bpf, et il s'est avéré qu'il y avait et dans sa liste d'appels bloqués par définition ptrace! (En fait, la liste d'appels est une liste d'exceptions et ptracen'y entre tout simplement pas, mais le résultat ne change pas.)



Maintenant, il est clair pourquoi cela ne fonctionne pas dans un conteneur Docker strace, car il est évident qu'un ptraceappel complètement bloqué ne fonctionnera pas. Testons



cette hypothèse et voyons si nous pouvons utiliser stracele conteneur Docker si nous désactivons toutes les règles 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 ...


Bien! Tout fonctionne, et le secret est révélé! C'est juste ...



Pourquoi --cap-add=SYS_PTRACErésout-il le problème?



Nous n'avons toujours pas expliqué pourquoi il --cap-add=SYS_PTRACErésout le problème émergent des appels. La page principale docker runexplique le fonctionnement de l'argument comme suit --cap-add:



--cap-add=[]
   Add Linux capabilities


Tout cela n'a rien à voir avec les règles de seccomp! Quel est le problème?



Jetons un coup d'Ĺ“il au code source de Docker.



Si la documentation n'aide pas déjà, il ne nous reste plus qu'à plonger dans la source.

Une bonne chose à propos de Go est qu'en proposant des dépendances dans un référentiel Go, vous greppouvez parcourir l'ensemble du référentiel et trouver le code qui vous intéresse. Je l'ai donc github.com/moby/mobycloné et scanné à la recherche d'expressions de la forme rg CAP_SYS_PTRACE.



À mon avis, c'est ce qui se passe ici: dans l'implémentation de seccomp dans le conteneur, dans la section contrib / seccomp / seccomp_default.go, il y a beaucoup de code qui, grâce à la règle seccomp, vérifie si un processus avec des privilèges a la permission d'utiliser des appels système conformément à ce privilège.



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




Il existe toujours du code dans moby qui, pour les profils / seccomp / seccomp.go et pour les profils seccomp, par définition, effectue des opérations similaires, nous avons donc probablement trouvé notre réponse!



Docker --cap-addpeut faire plus que dit



En conséquence, il --cap-addne semble pas faire exactement ce qui est écrit sur la page principale et devrait plutôt ressembler à --cap-add-and-also-whitelist-some-extra-system-calls-if-required. Et cela semble être vrai: si vous avez un privilège comme CAP_SYS_PTRACEce qui vous permet d'utiliser un appel système process_vm_readv, mais cet appel est bloqué par le profil de seccomp, qui ne vous aide pas beaucoup, permettant ainsi les appels système par process_vm_readvet à ptracetravers des CAP_SYS_PTRACEregards raisonnables.



S'avère stracefonctionner dans les dernières versions de Docker



Pour les versions 4.8 et supérieures du noyau, grâce à ce commit, les appels système sont enfin autorisés dans Docker 19.03 ptrace. Sauf que sur mon ordinateur portable, Docker est toujours la version 18.09.7, et ce commit est évidemment absent.



C'est tout!



Il s'est avéré intéressant de traiter ce problème, et je pense que c'est un bon exemple d'interaction de "remplissage" de conteneurs non triviale.



Si vous avez aimé cet article, vous aimerez peut-être aussi mon magazine How Containers Work , qui explique les fonctionnalités de gestion des conteneurs de 24 pages du noyau Linux. Là, vous pouvez voir les privilèges et seccomp-bpf .



All Articles