virli/tutorial/3/capabilities.md

703 lines
25 KiB
Markdown
Raw Normal View History

2017-10-23 20:25:51 +00:00
Les *capabilities*
2021-10-05 15:23:09 +00:00
------------------
2016-10-05 09:13:56 +00:00
2017-10-23 20:25:51 +00:00
Historiquement, dans la tradition UNIX, on distinguait deux catégories de
2021-10-05 15:23:09 +00:00
processus :
2016-10-05 09:13:56 +00:00
2021-10-05 15:23:09 +00:00
* les processus *privilégiés* : dont l'identifiant numérique de son utilisateur
est 0 ;
* les processus *non-privilégiés* : dont l'identifiant numérique de son
2019-10-22 16:03:09 +00:00
utilisateur n'est pas 0.
2016-10-05 20:49:08 +00:00
2022-10-18 14:40:31 +00:00
Lors des différents tests de permission faits par le noyau, les processus
2016-10-05 20:49:08 +00:00
privilégiés outrepassaient ces tests, tandis que les autres devaient passer les
2022-10-18 14:40:31 +00:00
tests de l'*effective UID*, *effective GID*, et autres groupes
supplémentaires...
2016-10-05 20:49:08 +00:00
2022-10-17 23:15:22 +00:00
Dans les années 90, ce système s'est rélévé être un peu trop basique et
conduisait régulièrement à des abus, au moyen de vulnérabilités trouvées dans
les programmes *setuid root*.
2016-10-05 20:49:08 +00:00
2022-10-17 23:15:22 +00:00
Avant de regarder plus en détail les solutions qui ont été apportées à ce
problème, commençons par mettre le doigt sur les difficultés.
2016-10-05 20:49:08 +00:00
2022-10-17 23:15:22 +00:00
### `setuid` : être `root` le temps de l'exécution
De manière générale, un processus s'exécute dans le contexte de privilège de
l'utilisateur qui l'a démarré. Ainsi, lorsque l'on souhaite supprimer un
fichier ou un répertoire, en tant que simple utilisateur on ne pourra supprimer
que ses propres fichiers, et en aucun cas on ne pourra supprimer ... la racine
par exemple.
Il y a cependant des tâches nécessitant des privilèges, que l'on souhaite
pouvoir réaliser en tant que simple utilisateur. C'est le cas notamment de la
modification de son mot de passe : il serait inconcevable de devoir demander à
son administrateur à chaque fois que l'on souhaite modifier son mot de
passe. Pourtant bien que l'on puisse lire le fichier `/etc/passwd`, seul `root`
peut y apporter des modifications (il en est de même pour `/etc/shadow` qui
contient les hashs des mots de passe).
2016-10-05 20:49:08 +00:00
2022-10-18 14:40:31 +00:00
C'est ainsi qu'est apparu le `suid-bit` parmi les modes de fichiers. Lorsque
2022-10-17 23:15:22 +00:00
ce bit est défini sur un binaire exécutable, au moment de l'exécution, le
contexte passe à celui du propriétaire du fichier (`root` si le propriétaire
est `root`, mais cela fonctionne quelque soit le propriétaire du fichier : on
ne devient pas `root`, mais bien l'utilisateur propriétaire).\
2016-10-05 20:49:08 +00:00
2022-10-17 23:15:22 +00:00
Un autre exemple : pour émettre un ping, il est nécessaire d'envoyer des
paquets ICMP. À la différence des datagrammes UDP ou des segments TCP, il n'est
pas forcément simple d'envoyer des paquets ICMP lorsque l'on est simple
utilisateur, car l'usage du protocole ICMP dans une socket est restreint : il
faut soit être super-utilisateur, soit que le noyau ait été configuré pour
autoriser certains utilisateurs à envoyer des `ECHO_REQUEST`.
2016-10-05 20:49:08 +00:00
Pour permettre à tous les utilisateurs de pouvoir envoyer des ping, le
2022-10-17 23:15:22 +00:00
programme est donc généralement *setuid root*, pour permettre à n'importe quel
2016-10-05 20:49:08 +00:00
utilisateur de prendre les droits du super-utilisateur, le temps de l'exécution
2022-10-17 23:15:22 +00:00
du programme.\
2016-10-05 20:49:08 +00:00
Les problèmes surviennent lorsque l'on découvre des vulnérabilités dans les
2022-10-17 23:15:22 +00:00
programmes *setuid root*. En effet, s'il devient possible pour un utilisateur
2016-10-05 20:49:08 +00:00
d'exécuter du code arbitraire, ce code sera exécuté avec les privilèges de
2021-10-05 15:23:09 +00:00
l'utilisateur *root* ! Dans le cas de `ping`, on se retrouverait alors à
2017-10-22 22:14:32 +00:00
pouvoir lire l'intégralité de la mémoire, alors que l'on avait juste besoin
d'écrire sur une interface réseau.
2016-10-05 20:49:08 +00:00
2022-10-17 23:15:22 +00:00
### Et ainsi les privilèges furent séparés
2021-10-05 15:23:09 +00:00
2022-10-17 23:15:22 +00:00
Depuis Linux 2.2 (en 1998), les différentes actions réclamant des privilèges
2022-10-18 14:40:31 +00:00
sont regroupées en catégories que l'on appelle *capabilities*. Chacune donne
2022-10-17 23:15:22 +00:00
accès à un certain nombre d'actions, on trouve notamment :
2021-10-05 15:23:09 +00:00
2022-10-17 23:15:22 +00:00
* `CAP_CHOWN` : permet de modifier le propriétaire d'un fichier de manière
arbitraire ;
* `CAP_KILL` : permet de tuer n'importe quel processus ;
* `CAP_SYS_BOOT` : permet d'arrêter ou de redémarrer la machine ;
* `CAP_SYS_MODULE` : permet de charger et décharger des modules ;
* et beaucoup d'autres, il y en a environ 41 en tout (ça dépend de la
version du noyau) !
2021-10-05 15:23:09 +00:00
2022-10-17 23:15:22 +00:00
::::: {.more}
2016-10-05 20:49:08 +00:00
2022-10-17 23:15:22 +00:00
Petit point historique, Linux n'est pas à l'origine du concept de
*capabilities*, il s'agit au départ de la norme POSIX 1003.1e, mais celle-ci
2022-10-18 14:40:31 +00:00
s'est éteinte après le 17\textsuperscript{ème} *draft*. Il devait y être standardisé de nombreux
2022-10-17 23:15:22 +00:00
composants améliorant la sécurité des systèmes POSIX, notamment les
*capabilities* POSIX, les *ACL POSIX*, ...
2016-10-05 20:49:08 +00:00
2022-10-17 23:15:22 +00:00
:::::
2021-10-05 15:23:09 +00:00
2022-10-17 23:15:22 +00:00
Ainsi, `ping` pourrait se contenter de `CAP_NET_RAW`, une *capability* qui
permet notamment d'envoyer des données brutes sur n'importe quelle *socket*,
sans passer par les types de *socket* plus restreints, mais accessibles aux
utilisateurs.
2021-10-05 15:23:09 +00:00
2022-10-17 23:15:22 +00:00
C'est peut-être encore beaucoup, mais au moins, une vulnérabilité dans `ping`
ne permettrait plus d'accéder à tous les fichiers ou à toute la mémoire.
2021-10-05 15:23:09 +00:00
2022-10-17 23:15:22 +00:00
::::: {.question}
2021-10-05 15:23:09 +00:00
2022-10-17 23:15:22 +00:00
#### Peut-on faire mieux pour `ping` ? {-}
Un paramètre existe bien [depuis 2011 dans le
noyau](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=c319b4d76b9e583a5d88d6bf190e079c4e43213d) :
`net.ipv4.ping_group_range`. Mais ce n'est que [depuis
2020](https://github.com/systemd/systemd/pull/13141) que les distributions
comme Fedora et Ubuntu se mettent à fournir par défaut une configuration qui
permette de se passer de *capabilities* pour lancer `ping`.
Cette option prend un intervalle d'identifiant numérique de groupes autorisés à
créer de `ECHO_REQUEST`. Par défaut la valeur invalide de `1 0` est définie, ce
qui signifie qu'aucun groupe n'est autorisé à créer des `ECHO_REQUEST` à moins
d'être privilégié.
:::::
::::: {.warning}
Nous allons par la suite travailler avec le binaire `ping` pour appréhender les
*capabilities*. Si vous vous rendez compte que votre binaire `ping` est dans le
cas figure décrit juste avant (avec une distribution ayant mis en œuvre
l'option `net.ipv4.ping_group_range`), vous pouvez retrouver le comportement
historique en désactivant l'option :
2021-10-05 15:23:09 +00:00
<div lang="en-US">
```
2022-10-17 23:15:22 +00:00
42sh# sysctl net.ipv4.ping_group_range="1 0"
2021-10-05 15:23:09 +00:00
```
</div>
2022-10-17 23:15:22 +00:00
Pas d'inquiétudes, le paramètre est changé de manière temporaire seulement, au
prochain redémarrage il reprendra sa valeur définie par la distribution.
2021-10-05 15:23:09 +00:00
:::::
2022-10-17 23:15:22 +00:00
### Les ensembles de *capabilities*
Tout d'abord, il faut noter que chaque *thread* dispose de 5 ensembles de
*capabilities*. Au cours de son exécution, il peut changer ses ensembles de
*capabilities*. Ceux-ci sont utilisés de la façon suivante :
- ***bounding*** (B) : c'est l'ensemble limitant des *capabilities* qui pourront
2022-10-18 14:40:31 +00:00
faire l'objet d'un calcul lors des prochaines exécutions. Quelques soient les
2022-10-17 23:15:22 +00:00
*capabilities* que peut nous faire gagner une exécution, si la *capability*
ne se trouve pas dans le *bounding set*, elle ne sera pas considérée et il
sera impossible de l'obtenir. L'option `--cap-drop` de Docker modifie cet
ensemble pour restreindre les *capabilities* disponibles dans un conteneur ;
- ***effective*** (E) : il s'agit des *capabilities* actuellement actives, qui
seront vérifiées lors des tests de privilèges ;
- ***permitted*** (P) : ce sont les *capabilities* que la tâche peut placer dans
l'ensemble *effective* via `capset(2)`. L'ensemble *effective* ne peut pas
avoir de *capabilities* qui ne sont pas dans l'ensemble *permitted* ;
- ***inheritable*** (I) : est utilisé au moment de la résolution des *capabilities*
lors de l'exécution d'un nouveau processus. Il s'agit des *capabilities* qui
seront transmises au processus fil. À moins d'avoir la *capability*
`CAP_SETPCAP`, cet ensemble ne peut pas avoir plus de *capability* que celles
présentent dans l'ensemble *permitted* ;
- ***ambient*** (A) : existe depuis Linux 4.3 pour permettre aux utilisateurs
non-`root` de conserver des *capabilities* d'une exécution à l'autre (sans
que l'ensemble des binaires soient marqués avec toutes les *capabilities*
dans leur ensemble **inheritable**)[^AMBIENT_PB]. L'ensemble évolue
automatiquement à la baisse si une *capability* n'est plus *permitted*.
[^AMBIENT_PB]: Le problème ayant donné naissance aux *ambient capabilities* est
décrit dans cet échange : <https://lwn.net/Articles/636533/>
2021-10-05 15:23:09 +00:00
### Les attributs de fichier étendus
Une grosse majorité des systèmes de fichiers (ext[234], XFS, btrfs, ...)
2017-10-23 20:25:51 +00:00
permet d'enregistrer, pour chaque fichier, des attributs (dits attributs
2016-10-05 20:49:08 +00:00
*étendus*, par opposition aux attributs *réguliers* qui sont réservés à l'usage
2022-10-17 23:15:22 +00:00
du système de fichiers, tels que le propriétaire, le groupe, les modes du
fichier, ...).
2016-10-05 20:49:08 +00:00
2021-10-05 15:23:09 +00:00
Sous Linux, les attributs sont regroupés dans des espaces de noms :
2016-10-05 20:49:08 +00:00
2021-10-05 15:23:09 +00:00
* *security* : espace utilisé par les modules de sécurité du noyau, tel que
SELinux, ... ;
* *system* : espace utilisé par le noyau pour stocker des objets système, tels
que les ACL POSIX ;
2016-10-05 20:49:08 +00:00
* *trusted*: espace dont la lecture et l'écriture est limité au
2021-10-05 15:23:09 +00:00
super-utilisateur ;
* *user* : modifiable sans restriction, à partir du moment où l'on est le
2016-10-05 20:49:08 +00:00
propriétaire du fichier.
2021-10-05 15:23:09 +00:00
Par exemple, on peut définir un attribut sur un fichier comme cela :
2016-10-05 20:49:08 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
```bash
42sh$ echo 'Hello World!' > toto
42sh$ setfattr -n user.foo -v bar toto
42sh$ getfattr -d toto
# file: toto
user.foo="bar"
2016-10-05 20:49:08 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2016-10-05 20:49:08 +00:00
2022-10-17 23:15:22 +00:00
Lorsque l'on est propriétaire du fichier, on peut modifier les attributs des
espaces *security*, *system* et *user*. Évidemment, *root* peut aussi
intervenir et par exemple les ACL POSIX[^ACLPOSIX] (espace *system*) :
[^ACLPOSIX]: Les ACL POSIX sont des permissions supplémentaires qui viennent
s'ajouter aux modes standards du fichier (propriétaire, groupe, reste du
monde). Avec les ACL POSIX, on peut doonner des droits à un ou plusieurs
utilisateurs ou groupe, de manière spécifique.
2016-10-05 20:49:08 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
```bash
42sh$ sudo chown root:root toto && sudo chmod o-r toto
42sh$ cat toto
cat: toto: Permission denied
2021-10-05 15:23:09 +00:00
42sh$ sudo setfacl -m u:$USER:r toto
42sh$ cat toto
Hello World!
2016-10-05 20:49:08 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2016-10-05 20:49:08 +00:00
2022-10-17 23:15:22 +00:00
Dans cet exemple, bien que les droits UNIX traditionnels ne vous donnent pas
accès au fichier, les ACL POSIX vous autorisent à le lire.
2016-10-05 20:49:08 +00:00
2021-10-05 15:23:09 +00:00
Vous pouvez voir ces attributs bruts avec la commande :
2016-10-05 20:49:08 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
```bash
42sh$ getfattr -d -m "^system" toto
# file: toto
system.posix_acl_access=0sgAAEAD/////AgAEOgDAEAA/////xAABAD////8=
2016-10-05 20:49:08 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2016-10-05 20:49:08 +00:00
2022-10-17 23:15:22 +00:00
Il s'agit d'une structure du noyau encodée en base64 (la valeur débute par
`0s`, de la même manière que l'on a l'habitude de reconnaître l'hexadécimal
avec `0x`, voir `getfattr(1)`), pas forcément très lisible en l'état. On
utilisera plutôt `getfacl` pour la version lisible.
::::: {.code}
2016-10-05 20:49:08 +00:00
2022-10-17 23:15:22 +00:00
Les structures utilisées pour stocker les ACL POSIX dans les attributs étendus
sont définies dans `linux/posix_acl_xattr.h` :\
2021-10-05 15:23:09 +00:00
2022-10-17 23:15:22 +00:00
D'abord un en-tête, composé de la version avec laquelle la suite des octets a
été enregistrée (actuellement 2) :
<div lang="en-US">
```c
struct posix_acl_xattr_header {
uint32_t a_version;
};
```
</div>
Puis on trouve directement la liste d'entrée(s) :
<div lang="en-US">
```c
struct posix_acl_xattr_entry {
uint16_t e_tag;
uint16_t e_perm;
uint32_t e_id;
};
```
</div>
Le *tag* identifie de quel type d'entrée il s'agit (propriétaire, utilisateur,
groupe, masque, reste du monde, ...). Les *perm*issions sont un champ de bits
pour la lecture, écriture et exécution. Enfin l'*id*entifiant renseigne sur le
numéro d'utilisateur ou de groupe à qui s'applique l'entrée.\
Les constantes utilisées sont définies dans `linux/posix_acl.h`.
:::::
### *Capabilities* et attributs étendus pour `ping`
2016-10-05 20:49:08 +00:00
2017-10-23 20:25:51 +00:00
De la même manière que l'on peut définir de façon plus fine les droits d'accès
par utilisateur, un attribut de l'espace de nom *security* peut être défini
pour accroître les *capabilities* d'un processus lorsqu'il est lancé par un
2021-10-05 15:23:09 +00:00
utilisateur non-privilégié. On peut voir le *setuid root* comme l'utilisation
de cet attribut, qui accroîtrait l'ensemble des *capabilities*.
2016-10-05 20:49:08 +00:00
2021-10-05 15:23:09 +00:00
Si votre distribution profite de ces attributs étendus, vous devriez obtenir :
2016-10-05 20:49:08 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
```bash
42sh$ getfattr -d -m "^security" $(which ping)
# file: bin/ping
security.capability=0sAQAAAgAgAAAAAAAAAAAAAAAAAAA=
2016-10-05 20:49:08 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2016-10-05 20:49:08 +00:00
2022-10-17 23:15:22 +00:00
Suivant votre distribution, d'autres programmes sont susceptibles de profiter
des attributs étendus pour se passer du *setuid root*, par exemple : `chvt`,
`beep`, `iftop`, `mii-tool`, `mtr`, `nethogs`, ...
2021-10-05 15:23:09 +00:00
Comme pour les ACL POSIX, une structure du noyau est enregistrée comme attribut
2022-10-17 23:15:22 +00:00
du fichier ; et on peut l'afficher dans sa version plus lisible avec `getcap` :
2016-10-05 20:49:08 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
```bash
42sh$ getcap $(which ping)
/bin/ping = cap_net_raw+ep
2016-10-05 20:49:08 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2016-10-05 09:13:56 +00:00
2022-10-17 23:15:22 +00:00
::::: {.code}
La structure utilisée pour stocker les *capabilities* dans les attributs étendus
est définie dans `linux/capability.h` :
<div lang="en-US">
```c
struct vfs_cap_data {
uint32_t magic_etc;
struct {
uint32_t permitted;
uint32_t inheritable;
} data[VFS_CAP_U32];
};
```
</div>
La valeur `magic` contient la version sur 1 octet, puis 3 octets sont réservés
2022-10-18 14:40:31 +00:00
pour des *flags*. Actuellement un seul *flag* existe, il s'agit de
2022-10-17 23:15:22 +00:00
`VFS_CAP_FLAGS_EFFECTIVE` qui détermine si la liste effective de *capabilities*
du programme doit être remplie avec les *capabilities* *permitted* si elle doit
rester vide (auquel cas ce sera au programme de s'ajouter les *capabilities* au
cours de l'exécution).\
Deux entiers de 32 bits représentent ensuite les *capabilities* de 0 à 31 sous
forme de champ de bits, un entier pour la liste des *capabilities* permises, un
autre pour les *capabilities* héritables. Comme il y a une quarantaine de
*capabilities*, celles de 32 à 40 se retrouvent à la suite, dans deux nouveaux
entiers de 32 bits. C'est pour cela que `data` est un tableau, avec
`VFS_CAP_U32` valant 2, car on a deux fois nos 2 entiers de 32 bits à la
suite.\
Il s'agit de la version 2 de la structure. La version 1 était utilisée
lorsqu'il n'y avait encore que moins de 33 *capabilities* : il n'y avait alors
pas de tableau, seulement les deux entiers de 32 bits. On remarque que les deux
versions sont facilement compatibles entre-elles, la seconde version étendant
simplement la première.\
Une version 3 existe, la structure obtient un champ supplémentaire `rootid` :
<div lang="en-US">
```c
struct vfs_ns_cap_data {
uint32_t magic_etc;
struct {
uint32_t permitted;
uint32_t inheritable;
} data[VFS_CAP_U32];
uint32_t rootid;
};
```
</div>
Ce nouveau champ `rootid` est utilisé pour stocker un identifiant d'utilisateur
`root` au sein d'un *namespace* User. C'est utile pour pouvoir jouer avec les
*capabilities* au sein d'un conteneur non-privilégié. S'il vaut autre chose que
0 et que l'on ne se trouve pas dans un *namespace* User, les *capabilities* ne
seront pas appliquées.
:::::
### Gagner des *capabilities*
C'est à l'exécution (`execve(2)`) que sont calculés les nouveaux ensembles de
*capabilities* : c'est donc uniquement à ce moment que l'on peut en gagner de
nouvelles. Voici comment les différents ensembles du nouveau processus sont
calculés :
$$p_A = (\textsf{file caps or setuid or setgid}\: ?\: ∅\: :\: p_A)$$
$$p_P = (p_I\: \&\: f_I)\ |\ (f_P\: \&\: p_B)\ |\ p_A$$
$$p_E = f_E\: ?\: p_P\: :\: p_A$$
$$p_I = p_I$$
$$p_B = p_B$$
Avec $p$ le processus avant l'exécution, $f$ le fichier exécutable donnant
naissance au nouveau processus et $p$ le nouveau processus.\
On remarque que sans les *ambients capabilities*, on perd systématiquement les
*capabilities* dont on disposait avant l'`execve(2)`, car $f_I$ n'est que très
rarement défini sur un exécutable. Dans le cas général, on récupère donc soit
2022-10-18 14:40:31 +00:00
$f_P$, soit $p_A$ (les deux étant exclusifs : si $f_P$ est défini, $p_A$ est
2022-10-17 23:15:22 +00:00
vide).
Bien entendu, lorsque l'on se trouve dans le contexte d'exécution de `root` (ou
que l'on exécute un binaire *setuid root*), ces calculs sont biaisés, car le
super-utilisateur a normalement toutes les *capabilities* (mais toujours
limitées par l'ensemble *bounding*) : $f_P$ et $f_I$ contiennent alors toutes
les *capabilities*, indifféremment du fichier considéré. Les calculs peuvent
alors être simplifiés en :
$$p_P = (p_I\: \&\: p_B)$$
$$p_E = p_P$$
Il y a cependant une exception, lorsque l'utilisateur réel n'est pas `root`
(comme par exemple face à un binaire *setuid root*, seul l'utilisateur effectif
change) : dans ce cas, si le fichier expose des *capabilities*, seulement
celles-ci sont gagnées.
::::: {.question}
#### Peut-on placer des *capabilities* sur un script ? {-}
De la même manière qu'il n'est pas possible d'avoir de script *setuid root*
sous Linux[^NO_SETUID_SCRIPT], ajouter des *capabilities* à un script ne
permettra pas d'en gagner. Le calcul s'effectue en effet sur les *capabilities*
de l'exécutable de l'interpréteur et non sur celles du script.
[^NO_SETUID_SCRIPT]: <https://unix.stackexchange.com/a/2910>
:::::
### Gérer ses *capabilities*
Un *thread* peut agir comme il le souhaite sur les ensembles *effective*,
*permitted* et *inheritable*. À condition bien sûr de ne jamais dépasser les
*capabilities* déjà contenues dans l'ensemble *permitted*, sauf si l'on dispose
de la *capability* `CAP_SETPCAP` : dans ce cas, on se retrouve limité seulement
par l'ensemble *bounding*.
::::: {.code}
On utilise les appels système `capget(2)` et `capset(2)` pour respectivement
connaître les *capabilities* actuelles de notre fil d'exécution et pour les
2022-10-18 14:40:31 +00:00
écraser. Ces appels système utilisent une structure d'en-tête et une structure
2022-10-17 23:15:22 +00:00
de données, qui sont définies dans `linux/capability.h` :
<div lang="en-US">
```c
#define _LINUX_CAPABILITY_VERSION_1 0x19980330
#define _LINUX_CAPABILITY_U32S_1 1
/* V2 added in Linux 2.6.25; deprecated */
#define _LINUX_CAPABILITY_VERSION_2 0x20071026
#define _LINUX_CAPABILITY_U32S_2 2
/* V3 added in Linux 2.6.26 */
#define _LINUX_CAPABILITY_VERSION_3 0x20080522
#define _LINUX_CAPABILITY_U32S_3 2
typedef struct __user_cap_header_struct {
uint32_t version;
int pid;
} *cap_user_header_t;
typedef struct __user_cap_data_struct {
uint32_t effective;
uint32_t permitted;
uint32_t inheritable;
} *cap_user_data_t;
```
</div>
La structure d'en-tête permet de renseigner sur la version de la structure de
données que l'on souhaite utiliser ou recevoir. Comme pour les *capabilities*
dans les attributs étendus, la première version était utilisée lorsqu'il y
avait moins de 33 *capabilities*, ce qui permettait de tout stocker sur un seul
entier de 32 bits non signé. Les versions 2 et 3 sont identiques et permettent
de récupérer les *capabilities* au moyen d'un tableau de 2 structures.\
::::: {.warning}
Les versions 2 et 3 ici ne sont pas comparables aux versions 2 et 3 de nos
structures `vfs_cap_data` et `vfs_ns_cap_data` : il n'y a pas de notion de
*namespace* ici.
Il y a eu un couac dans les en-têtes distribués avec Linux 2.6.25, causant des
2022-10-18 14:40:31 +00:00
*buffers overflow* dans les applications qui n'avaient pas prévu de gérer les
2022-10-17 23:15:22 +00:00
versions. Cela a été corrigé dans la version 2.6.26 : un avertissement est
2022-10-18 14:40:31 +00:00
consigné dans les journaux système en cas d'utilisation malheureuse de la
2022-10-17 23:15:22 +00:00
version 2.
:::::
Dans la pratique, on préférera utiliser `cap_get_proc(3)` et `cap_set_proc(3)`
fournis par la `libcap`.
:::::
Pour agir sur l'ensemble *bounding*, il faut disposer de la *capability*
`CAP_SETPCAP`[^WHY_BOUNDING_SETPCAP]. Lorsque l'on retire une *capability* de
cet ensemble, elle n'est pas retirée des autres ensembles et on peut donc
continuer de bénéficier des privilèges qu'elle accorde.
Il faut bien penser, lorsque l'on retire une *capability* de l'ensemble
*bounding*, à également la retirer de l'ensemble *inheritable*, sans quoi si le
programme exécuté a la *capability* en question dans ses attributs, celle-ci
sera préservée (revoir la formule pour $p_P$, l'ensemble *bounding* n'est pas
considéré avec l'ensemble *inheritable*).
[^WHY_BOUNDING_SETPCAP]: En effet, avant Linux 2.6.25, cet ensemble était
utilisé par tout le système, pas seulement pas le *thread* courant et ses
fils.
::::: {.code}
La consultation et la modification de l'ensemble *bounding* se fait au moyen de
`prctl(2)`, en utilisant les paramètres `PR_CAPBSET_READ` ou `PR_CAPBSET_DROP`.
Le second paramètre attendu est l'une des constantes représentant une
*capability*.\
Avec `PR_CAPBSET_READ`, `prctl(2)` retournera 0 si la *capability* ne fait pas
2022-10-18 14:40:31 +00:00
partie de l'ensemble *bounding*, ou 1 si elle est bien présente.
2022-10-17 23:15:22 +00:00
Avec `PR_CAPBSET_DROP`, `prctl(2)` retirera la *capability* passée en argument
de l'ensemble *bounding*. Une fois cette action effectuée, il est impossible de
revenir en arrière.\
Dans la pratique, on préférera utiliser `cap_get_bound(3)` et
`cap_drop_bound(3)` fournis par la `libcap`.
:::::
Enfin, le *thread* peut aussi modifier à sa guise l'ensemble *ambient*, à
2022-10-18 14:40:31 +00:00
condition que les *capabilities* ajoutées soient dans les ensembles *permitted*
2022-10-17 23:15:22 +00:00
et *inheritable*.
::::: {.code}
On consulte et modifie l'ensemble *ambient* avec `prctl(2)` à qui l'on passe
comme premier paramètre `PR_CAP_AMBIENT`. Le second paramètre permet de
préciser l'action que l'on veut réaliser :\
- `PR_CAP_AMBIENT_RAISE` : ajoute la *capability* précisée comme troisième
paramètre ;
- `PR_CAP_AMBIENT_LOWER` : retire la *capability* précisée comme troisième
paramètre ;
- `PR_CAP_AMBIENT_IS_SET` : retourne 1 si la *capability* précisée comme
2022-10-18 14:40:31 +00:00
troisième paramètre fait partie de l'ensemble *ambient*, sinon retourne 0 ;
2022-10-17 23:15:22 +00:00
- `PR_CAP_AMBIENT_CLEAR_ALL` : vide l'ensemble *ambient*.
:::::
### Explorer les *capabilities* avec un shell
La `libcap`, au travers des paquets `libcap2-bin` (Debian et ses dérivées) ou
`libcap` (Alpine, Archlinux, Gentoo et leurs dérivées), apporte le programme
`capsh(1)`, très utile pour appréhender les *capabilities*.
<div lang="en-US">
```
42sh# capsh --drop=cap_net_raw -- -c "/bin/ping nemunai.re"
/bin/bash: line 1: /bin/ping: Operation not permitted
```
</div>
Une autre commande intéressante est `pscap(1)`, de la `libcap-ng`. Parmi tous
les programmes en cours d'exécution, cet utilitaire va afficher tous les
programmes s'exécutant actuellement avec des *capabilities*, en indiquant pour
chacun lesquelles sont actives et disponibles.
::::: {.exercice}
2016-10-05 09:13:56 +00:00
2022-10-17 23:15:22 +00:00
### Visualisateur de *capabilities* d'un processus {-}
2016-10-05 09:13:56 +00:00
2016-10-06 01:58:52 +00:00
Écrivons maintenant un programme permettant de voir les *capabilities*
2021-10-05 15:23:09 +00:00
d'un processus :
2016-10-05 20:49:08 +00:00
2017-10-17 06:29:07 +00:00
<div lang="en-US">
```
42sh$ ./view_caps 1
cap_user_header_t
-----------------
Version: 20080522
PID: 1
cap_user_data_t
---------------
effective: 0x3fffffffff
CAP_AUDIT_CONTROL
CAP_AUDIT_READ
[...]
CAP_SYS_TIME
CAP_SYS_TTY_CONFIG
CAP_SYSLOG
CAP_WAKE_ALARM
permitted: 0x3fffffffff
CAP_AUDIT_CONTROL
CAP_AUDIT_READ
[...]
CAP_SYS_TIME
CAP_SYS_TTY_CONFIG
CAP_SYSLOG
CAP_WAKE_ALARM
inheritable: 0x0
2016-10-05 09:13:56 +00:00
```
2017-10-17 06:29:07 +00:00
</div>
2016-10-05 20:49:08 +00:00
2022-10-17 23:15:22 +00:00
Appelé sans argument, `view_caps` affichera les *capabilities* du processus
courant, avec en plus les informations sur son ensemble *ambient* et
*bounding* :
<div lang="en-US">
```
42sh# ./view_caps
cap_user_header_t
-----------------
Version: 20080522
PID: 42
cap_user_data_t
---------------
effective: 0x3fffffffff
CAP_AUDIT_CONTROL
CAP_AUDIT_READ
[...]
CAP_SYSLOG
CAP_WAKE_ALARM
permitted: 0x3fffffffff
CAP_AUDIT_CONTROL
CAP_AUDIT_READ
[...]
CAP_SYSLOG
CAP_WAKE_ALARM
inheritable: 0x0
ambient:
CAP_AUDIT_CONTROL
CAP_AUDIT_READ
[...]
CAP_SYSLOG
CAP_WAKE_ALARM
bounding:
CAP_AUDIT_CONTROL
CAP_AUDIT_READ
[...]
CAP_SYSLOG
CAP_WAKE_ALARM
```
</div>
<div lang="en-US">
```
42sh$ ./view_caps
cap_user_header_t
-----------------
Version: 20080522
PID: 42
cap_user_data_t
---------------
effective: 0x0
permitted: 0x0
inheritable: 0x0
ambient:
bounding:
CAP_AUDIT_CONTROL
CAP_AUDIT_READ
[...]
CAP_SYSLOG
CAP_WAKE_ALARM
```
</div>
2021-10-05 15:23:09 +00:00
2022-10-17 23:15:22 +00:00
Il peut être intéressant de lancer `view_caps` au sein d'un conteneur Docker
pour voir évoluer l'ensemble *bounding*, ou bien d'utiliser `capsh(1)` en
amont.
2016-10-05 20:49:08 +00:00
:::::
2016-10-05 20:49:08 +00:00
2021-10-05 15:23:09 +00:00
### Pour aller plus loin {-}
2016-10-05 20:49:08 +00:00
2021-10-05 15:23:09 +00:00
Je vous recommande la lecture des *man* suivants :
2016-10-05 20:49:08 +00:00
2022-10-18 14:40:31 +00:00
* `capabilities(7)` : énumérant tous les *capabilities*, leur utilisation, etc. ;
2021-10-05 15:23:09 +00:00
* `xattrs(7)` : à propos des attributs étendus.
2016-10-05 20:49:08 +00:00
2021-10-05 15:23:09 +00:00
Et de ces quelques articles :
2016-10-05 20:49:08 +00:00
2022-02-26 10:03:32 +00:00
* [Linux Capabilities: Why They Exist and How They Work](https://blog.container-solutions.com/linux-capabilities-why-they-exist-and-how-they-work) :\
<https://blog.container-solutions.com/linux-capabilities-why-they-exist-and-how-they-work>
* [Guidelines for extended attributes](https://www.freedesktop.org/wiki/CommonExtendedAttributes/) :\
<https://www.freedesktop.org/wiki/CommonExtendedAttributes/>
* [File-based capabilities](https://lwn.net/Articles/211883/) : <https://lwn.net/Articles/211883/>
* [A bid to resurrect Linux capabilities](https://lwn.net/Articles/199004/) : <https://lwn.net/Articles/199004/>
* [False Boundaries and Arbitrary Code Execution](https://forums.grsecurity.net/viewtopic.php?f=7&t=2522#p10271) :\
<https://forums.grsecurity.net/viewtopic.php?f=7&t=2522#p10271>
* [Linux Capabilities on HackTricks](https://book.hacktricks.xyz/linux-unix/privilege-escalation/linux-capabilities) :\
<https://book.hacktricks.xyz/linux-unix/privilege-escalation/linux-capabilities>
2022-10-18 14:40:31 +00:00
- [POSIX Access Control Lists on Linux](https://www.usenix.org/legacy/publications/library/proceedings/usenix03/tech/freenix03/full_papers/gruenbacher/gruenbacher_html/main.html) :\
2022-10-17 23:15:22 +00:00
<https://www.usenix.org/legacy/publications/library/proceedings/usenix03/tech/freenix03/full_papers/gruenbacher/gruenbacher_html/main.html>