En bref sur les privilèges Linux (capacités)

Une traduction de l'article a été préparée spécialement pour les étudiants du cours Administrateur Linux .


Les capacités sont de plus en plus utilisées grâce en grande partie à SystemD, Docker et à des orchestrateurs tels que Kubernetes. Mais, il me semble, la documentation est un peu compliquée à comprendre, et certaines parties de l'implémentation des privilèges se sont révélées quelque peu déroutantes, j'ai donc décidé de partager mes connaissances actuelles dans ce court article.



Le lien privilégié le plus important est la page de manuel des capacités (7) . Mais elle n'est pas très adaptée à une première connaissance.

Capacités de processus


Les droits des utilisateurs ordinaires sont très limités, tandis que les droits de l'utilisateur «root» sont très étendus. Bien que les processus exécutés en tant que «root» ne nécessitent souvent pas tous les privilèges root.

Pour réduire les privilèges root, les autorisations POSIX fournissent un moyen de limiter les groupes d'opérations système privilégiées qu'un processus et ses descendants sont autorisés à effectuer. Essentiellement, ils divisent tous les droits «racine» en un ensemble de privilèges distincts. L'idée de capacités a été décrite en 1997 dans un projet de POSIX 1003.1e.

Sous Linux, chaque processus (tâche) possède cinq nombres (ensembles) de 64 bits contenant des bits d'autorisation (avant Linux 2.6.25, ils étaient de 32 bits), qui peuvent être consultés dans
  / proc / <pid> / status 
.

CapInh: 00000000000004c0 CapPrm: 00000000000004c0 CapEff: 00000000000004c0 CapBnd: 00000000000004c0 CapAmb: 0000000000000000 

Ces nombres (affichés ici en notation hexadécimale) sont des bitmaps dans lesquels les jeux d'autorisations sont représentés. Voici leurs noms complets:

  • Héritable - Autorisations dont les descendants peuvent hériter
  • Autorisé - Autorisations pouvant être utilisées par la tâche.
  • Efficace - autorisations effectives actuelles
  • Limite - Avant Linux 2.6.25, l'ensemble de limites était un attribut à l'échelle du système commun à tous les threads, conçu pour décrire un ensemble au-delà duquel les autorisations ne pouvaient pas être étendues. Il s'agit actuellement d'un ensemble pour chaque tâche et n'est qu'une partie de la logique d'exécution, détails ci-dessous.
  • Ambient (externe depuis Linux 4.3) - ajouté pour faciliter la fourniture d'autorisations non root à l'utilisateur, sans utiliser les autorisations setuid ou file (plus d'informations à ce sujet plus tard).

Si une tâche demande une opération privilégiée (par exemple, une liaison aux ports <1024), le noyau vérifie l'ensemble de limites actuel pour CAP_NET_BIND_SERVICE . S'il est installé, l'opération continue. Sinon, l'opération est rejetée avec EPERM (opération non autorisée). Ces CAP_ dans le code source du noyau et sont numérotés séquentiellement, donc CAP_NET_BIND_SERVICE , égal à 10, signifie que le bit 1 << 10 = 0x400 (c'est le chiffre hexadécimal "4" dans mon exemple précédent).

Une liste complète des privilèges lisibles par l'homme actuellement définis peut être trouvée dans la page de manuel des capacités actuelles (7) (la liste ici est pour référence seulement).

De plus, il existe une bibliothèque libcap pour simplifier les contrôles de gestion et d'autorisation. En plus de l'API de bibliothèque , le package comprend l'utilitaire capsh , qui, entre autres, vous permet de montrer votre autorité.

 # capsh --print Current: = cap_setgid,cap_setuid,cap_net_bind_service+eip Bounding set = cap_setgid,cap_setuid,cap_net_bind_service Ambient set = Securebits: 00/0x0/1'b0 secure-noroot: no (unlocked) secure-no-suid-fixup: no (unlocked) secure-keep-caps: no (unlocked) secure-no-ambient-raise: no (unlocked) uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video) 

Il y a ici quelques points déroutants:

  • Actuel - affiche les privilèges effectifs, hérités et disponibles du processus capsh au format cap_to_text (3) . Dans ce format, les droits sont répertoriés en tant que groupes d'autorisations “capability[,capability…]+(e|i|p)” , où “e” signifie effectif, “i” hérité et “p” disponible. La liste n'est pas séparée par le symbole “,” , comme vous l'avez peut-être deviné (cap_setgid+eip, cap_setuid+eip) . Une virgule divise les autorisations en un groupe d'actions. La liste réelle des groupes d'actions est ensuite séparée par des espaces. Un autre exemple avec deux groupes d'actions serait “= cap_sys_chroot+ep cap_net_bind_service+eip” . Et aussi les deux groupes d'actions suivants “= cap_net_bind_service+e cap_net_bind_service+ip” la même valeur qu'un “cap_net_bind_service+eip” .
  • Jeu de délimitation / Jeu d'ambiance . Pour plus de confusion, ces deux lignes contiennent uniquement une liste d'autorisations définies dans ces ensembles, séparées par des espaces. Le format cap_to_text n'est pas utilisé ici, car il ne contient pas d'ensembles d'autorisations disponibles, effectives et héritées, mais un seul ensemble (englobant / ambiant).
  • Securebits : affiche les indicateurs securebits de la tâche au format décimal / hexadécimal / au format Verilog (oui, tout le monde s'y attend, et cela est parfaitement clair à partir du moment où chaque administrateur système programme ses propres FPGA et ASIC ). Voici l'état des bits sécurisés. Les indicateurs réels sont définis comme SECBIT_* dans securebits.h , et sont également décrits dans les capacités (7) .
  • Cet utilitaire n'a pas l'affichage des informations «NoNewPrivs» , qui peuvent être consultées dans
      / proc / <pid> / status 
    . Il n'est mentionné que dans prctl (2), bien qu'il affecte directement les droits lorsqu'il est utilisé avec les autorisations de fichier (plus en détail ci-dessous). NoNewPrivs est décrit comme suit: «Avec no_new_privs sur 1, execve (2) promet de ne pas accorder de privilèges à ce qui n'aurait pas pu être fait sans appeler execve (2) (par exemple, le traitement de set-user-ID , set-group-ID bits set-group-ID et désactivation du traitement des autorisations de fichiers) . Après l'installation, l'attribut no_new_privs ne peut pas être réinitialisé. La valeur de cet attribut est héritée par les descendants créés via fork (2) et clone (2) et stockée via execve (2). ” Kubernetes définit cet indicateur sur 1 lorsque allowPrivilegeEscalation est faux dans le pod securityContext.


Lors du démarrage d'un nouveau processus via execve (2), les autorisations pour le processus enfant sont converties à l'aide de la formule spécifiée dans les capacités (7) :

 P'(ambient) = (file is privileged) ? 0 : P(ambient) P'(permitted) = (P(inheritable) & F(inheritable)) | (F(permitted) & P(bounding)) | P'(ambient) P'(effective) = F(effective) ? P'(permitted) : P'(ambient) P'(inheritable) = P(inheritable) [ie, unchanged] P'(bounding) = P(bounding) [ie, unchanged] where: P() denotes the value of a thread capability set before the execve(2) -      execve(2) P'() denotes the value of a thread capability set after the execve(2) -      execve(2) F() denotes a file capability set -   


Ces règles décrivent les actions effectuées pour chaque bit dans tous les jeux d'autorisations (ambiant / autorisé / effectif / héritable / englobant). La syntaxe C standard est utilisée (& - pour ET logique, | - pour OU logique). P 'est un processus enfant. P est le processus actuel appelant execve (2). F est ce qu'on appelle les «autorisations de fichier» d'un fichier lancé via execve.

De plus, un processus peut modifier par programme ses ensembles hérités, accessibles et efficaces avec libcap à tout moment selon les règles suivantes:

  • Si l'appelant n'a pas CAP_SETPCAP , le nouvel ensemble hérité doit être un sous-ensemble de P (hérité) & P (disponible)
  • (avec Linux 2.6.25) Le nouvel ensemble hérité devrait être un sous-ensemble de P (hérité) et P (limitant)
  • Le nouvel ensemble disponible doit être un sous-ensemble de P (disponible)
  • Le nouvel ensemble efficace devrait être un sous-ensemble de P (efficace)


Autorisations de fichiers


Parfois, un utilisateur avec un ensemble limité de droits doit exécuter un fichier qui nécessite plus de privilèges. Auparavant, cela a été obtenu en définissant le bit setuid ( chmod + s ./executable ) dans un fichier binaire. Un tel fichier, s'il appartient à root, aura tous les droits root lorsqu'il sera exécuté par n'importe quel utilisateur.

Mais ce mécanisme donne trop de privilèges à un fichier, donc les autorisations POSIX ont implémenté un concept appelé «autorisations de fichier». Ils sont stockés sous la forme d'un attribut de fichier étendu appelé «security.capability», vous avez donc besoin d'un système de fichiers prenant en charge les attributs étendus (ext *, XFS, Raiserfs, Brtfs, overlay2, ...). Pour modifier cet attribut, l'autorisation CAP_SETFCAP est CAP_SETFCAP (dans l'ensemble d'autorisations de processus disponible).

 $ getfattr -m - -d `which ping` # file: usr/bin/ping security.capability=0sAQAAAgAgAAAAAAAAAAAAAAAAAAA= $ getcap `which ping` /usr/bin/ping = cap_net_raw+ep 


Cas particuliers et commentaires


Bien sûr, en réalité, tout n'est pas si simple, et il y a plusieurs cas particuliers décrits dans la page de manuel sur les capacités (7) . Les plus importants d'entre eux sont probablement:

  • Les autorisations de bits et de fichiers setuid sont ignorées si NoNewPrivs est installé ou si le système de fichiers est monté avec nosuid ou si le processus appelant execve est tracé par ptrace. Les autorisations de fichiers sont également ignorées lorsque le noyau démarre avec l'option no_file_caps .
  • Un fichier «stupide» (fonctionnalité-stupide) est un fichier binaire converti d'un fichier setuid en un fichier avec des autorisations de fichier, mais sans changer son code source. Ces fichiers sont souvent obtenus en définissant des autorisations + ep sur eux, par exemple, “setcap cap_net_bind_service+ep ./binary” . La partie importante est «e» - efficace. Après exécution, ces autorisations seront ajoutées aux autorisations disponibles et existantes, de sorte que l'exécutable sera prêt à utiliser l'opération privilégiée. En revanche, un fichier «intelligent» qui utilise libcap ou une fonctionnalité similaire peut utiliser cap_set_proc (3) (ou capset ) pour définir les bits «effectifs» ou «hérités» à tout moment si cette autorisation est déjà à » kit abordable. Par conséquent, « setcap cap_net_bind_service+p ./binary” sera suffisant pour un fichier «intelligent», car il pourra définir lui-même les autorisations nécessaires dans un ensemble efficace avant d'invoquer une opération privilégiée. Voir un exemple de code .
  • Les fichiers avec setuid-root continuent de fonctionner, accordant tous les privilèges root lorsque l'utilisateur démarre en tant que non root. Mais s'ils ont des autorisations de fichiers définies, seules elles seront accordées. Vous pouvez également créer un fichier setuid avec un ensemble d'autorisations vide, ce qui le fera fonctionner en tant qu'utilisateur avec UID 0 sans aucune autorisation. Il existe des cas particuliers pour l'utilisateur root lors de l'exécution d'un fichier avec setuid-root et la définition de divers indicateurs securebits (voir man).
  • Un ensemble de délimitation masque les autorisations disponibles, mais pas celles héritées. N'oubliez pas P '(disponible) = F (disponible) et P (limitant). Si un flux a une autorisation dans son ensemble hérité qui ne se trouve pas dans son ensemble englobant, il peut toujours obtenir cette autorisation dans son ensemble disponible en exécutant un fichier qui a l'autorisation dans son ensemble hérité - P '(disponible) = P ( hérité) & F (hérité).
  • L'exécution d'un programme qui modifie l'UID ou le GID via les bits set-user-ID, set-group-ID ou l'exécution d'un programme pour lequel des autorisations de fichier sont définies effacera l'ensemble ambiant . Les autorisations sont ajoutées à l' ensemble environnant à l' aide de PR_CAP_AMBIENT prctl . Ces autorisations doivent déjà être présentes dans les ensembles de processus accessibles et hérités .
  • Si un processus avec un UID différent de 0 exécute execve (2) , tous les droits sur ses ensembles disponibles et actifs seront supprimés.
  • Si SECBIT_KEEP_CAPS (ou le SECBIT_NO_SETUID_FIXUP plus SECBIT_NO_SETUID_FIXUP ) n'est pas défini, la modification de l'UID de 0 à non nul supprime toutes les autorisations des ensembles hérités, accessibles et efficaces .


Alors ...


Si le conteneur nginx officiel, ingress-nginx ou le vôtre s'arrête ou redémarre avec une erreur:

bind() to 0.0.0.0:80 failed (13: Permission denied)

... cela signifie qu'il y a eu une tentative d'écoute sur le port 80 en tant qu'utilisateur non privilégié (pas 0), et qu'il n'y avait pas CAP_NET_BIND_SERVICE dans le CAP_NET_BIND_SERVICE autorisations actuel. Pour obtenir ces droits, vous devez utiliser xattr et set (à l'aide de setcap ) pour l'autorisation de fichier nginx au moins cap_net_bind_service+ie . Cette autorisation de fichier sera combinée avec l'ensemble hérité (spécifié avec l'ensemble de limites du pod SecurityContext / capacity / add / NET_BIND_SERVICE), et sera également placé dans l'ensemble d'autorisations disponibles. Le résultat est cap_net_bind_service+pie .

Tout cela fonctionne tant que securityContext / allowPrivilegeEscalation est défini sur true et que le pilote de stockage docker / rkt (voir la documentation de docker) prend en charge xattrs.

Si nginx était intelligent en ce qui concerne les autorisations, alors cap_net_bind_service+i serait suffisant. Il pourrait ensuite utiliser libcap pour étendre les droits de l'ensemble disponible à effectif. Ayant reçu comme résultat cap_net_bind_service+pie .

Outre l'utilisation de xattr, la seule façon d'obtenir cap_net_bind_service dans un conteneur non root est de laisser Docker définir ses capacités externes (capacités ambiantes). Mais en avril 2019, cela n'a pas encore été mis en œuvre .

Exemples de code


Voici un exemple de code utilisant libcap pour ajouter CAP_NET_BIND_SERVICE à un ensemble d'autorisations efficace. Il nécessite l'autorisation CAP_BIND_SERVICE+p pour le fichier binaire.

Références (eng.):

Source: https://habr.com/ru/post/fr471802/


All Articles