tuto: Prepare for 2022

This commit is contained in:
nemunaire 2021-09-12 10:31:36 +02:00
parent 37f0ba4b3d
commit af860b40a0
40 changed files with 246 additions and 1974 deletions

View file

@ -1,14 +1,6 @@
include ../pandoc-opts.mk
SOURCES = tutorial.md \
../devops/devops.md \
../devops/what.md \
../devops/tools.md \
../devops/ci.md \
../devops/publish-docker.md \
../docker-internals/oci.md \
../docker-internals/registry.md \
rendu.md
SOURCES = tutorial.md installation.md chroot.md pseudofs.md capabilities.md cgroups.md oom.md seccomp.md project-intro.md project-body.md project-rendu.md
all: tutorial.pdf

202
tutorial/3/capabilities.md Normal file
View file

@ -0,0 +1,202 @@
\newpage
Les *capabilities*
==================
## Présentation
Historiquement, dans la tradition UNIX, on distinguait deux catégories de
processus :
* 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
utilisateur n'est pas 0.
Lors des différents tests de permission fait par le noyau, les processus
privilégiés outrepassaient ces tests, tandis que les autres devaient passer les
tests de l'effective UID, effective GID, et autres groupes supplémentaires...
Depuis Linux 2.2 (en 1998), les processus privilégiés peuvent activer ou
désactiver des *capabilities*, chacune donnant accès à un groupe d'actions
privilégiées au sein du noyau.
On trouve par exemple :
* `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 39 en tout (ça dépend de la
version du noyau) !
### `ping`
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'existe pas d'interface
exposée par le noyau aux utilisateurs pour envoyer des paquets ICMP. Pour le
faire, il est nécessaire de pouvoir écrire directement sur l'interface ; ça,
seul le super-utilisateur peut le faire.
Pour permettre à tous les utilisateurs de pouvoir envoyer des ping, le
programme est donc généralement *Setuid root*. Cela permet à n'importe quel
utilisateur de prendre les droits du super-utilisateur, le temps de l'exécution
du programme.
Les problèmes surviennent lorsque l'on découvre des vulnérabilités dans les
programmes *Setuid root*. En effet, s'il devient possible pour un utilisateur
d'exécuter du code arbitraire, ce code sera exécuté avec les privilèges de
l'utilisateur *root* ! Dans le cas de `ping`, on se retrouverait alors à
pouvoir lire l'intégralité de la mémoire, alors que l'on avait juste besoin
d'écrire sur une interface réseau.
C'est donc à ce moment que les *capabilities* entrent en jeu : un processus (ou
même un thread) privilégié peut décider, généralement à son lancement, de
réduire ses *capabilities*, pour ne garder que celles dont il a réellement
besoin. Ainsi, `ping` pourrait se contenter de `CAP_NET_RAW`.
## Les attributs de fichier étendus
Une grosse majorité des systèmes de fichiers (ext[234], XFS, btrfs, ...)
permet d'enregistrer, pour chaque fichier, des attributs (dits attributs
*étendus*, par opposition aux attributs *réguliers* qui sont réservés à l'usage
du système de fichiers).
Sous Linux, les attributs sont regroupés dans des espaces de noms :
* *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 ;
* *trusted*: espace dont la lecture et l'écriture est limité au
super-utilisateur ;
* *user* : modifiable sans restriction, à partir du moment où l'on est le
propriétaire du fichier.
Par exemple, on peut définir un attribut sur un fichier comme cela :
<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"
```
</div>
Encore plus fort, vous pouvez utiliser les ACL POSIX :
<div lang="en-US">
```bash
42sh$ sudo chown root:root toto && sudo chmod o-r toto
42sh$ cat toto
cat: toto: Permission denied
42sh$ sudo setfattr -m u:$USER:r toto
42sh$ cat toto
Hello World!
```
</div>
Bien que les droits UNIX traditionnels ne vous donnent pas accès au fichier,
les ACL POSIX vous autorisent à le lire.
Vous pouvez voir ces attributs avec la commande :
<div lang="en-US">
```bash
42sh$ getfattr -d -m "^system" toto
# file: toto
system.posix_acl_access=0sgAAEAD/////AgAEOgDAEAA/////xAABAD////8=
```
</div>
### `ping`
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
utilisateur non-privilégié. On peut alors voir le Setuid root comme
l'utilisation de cet attribut auquel on accroîtrait l'ensemble des
*capabilities*.
Si votre distribution profite de ces attributs étendus, vous devriez obtenir :
<div lang="en-US">
```bash
42sh$ getfattr -d -m "^security" $(which ping)
# file: bin/ping
security.capability=0sAQAAAgAgAAAAAAAAAAAAAAAAAAA=
```
</div>
Ou, dans sa version plus lisible :
<div lang="en-US">
```bash
42sh$ getcap $(which ping)
/bin/ping = cap_net_raw+ep
```
</div>
## Exercice : visualisateur de capabilities d'un processus {-}
Écrivons maintenant un programme permettant de voir les *capabilities*
d'un processus :
<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
```
</div>
Astuces : `capget(2)`, X-macros, ...
## Pour aller plus loin {-}
Je vous recommande la lecture des *man* suivants :
* `capabilities(7)` : énumérant tous les capabilities, leur utilisation, etc. ;
* `xattrs(7)` : à propos des attributs étendus.
Et de ces quelques articles :
* [Secure Your Containers with this One Weird Trick](https://www.redhat.com/en/blog/secure-your-containers-one-weird-trick)
* [Guidelines for extended attributes](https://www.freedesktop.org/wiki/CommonExtendedAttributes/)
* [File-based capabilities](https://lwn.net/Articles/211883/)
* [A bid to resurrect Linux capabilities](https://lwn.net/Articles/199004/)
* [False Boundaries and Arbitrary Code Execution](https://forums.grsecurity.net/viewtopic.php?f=7&t=2522#p10271)
Pour revenir à Docker, par défaut, un certain nombre de *capabilities* sont
désactivées par défaut ; vous pouvez en ajouter et en retirer via les arguments
`--cap-add` et `--cap-drop` du `docker container run`.

287
tutorial/3/cgroups.md Normal file
View file

@ -0,0 +1,287 @@
\newpage
Utiliser les *cgroup*s
======================
Les *cgroup*s (pour *Control Group*s) permettent de collecter des statistiques
sur des groupes de processus (appelés tâches) et de leur attribuer des
propriétés. Par exemple, il est possible leur imposer des limites d'utilisation
de ressources ou d'altérer leur comportement.
## Premiers tests
Nous allons commencer par faire quelques tests avec le *cgroup* *freezer*, qui
permet d'interrompre l'exécution d'un groupe de processus et de la reprendre.
### Montage du *cgroup*
En fonction de la configuration de votre système, il est possible que les
*cgroup*s ne soient pas montés au démarrage dans `/sys/fs/cgroup/`. Si vous n'avez
pas de dossier `freezer` ou si celui-ci est vide, montez-le en suivant la
procédure suivante :
<div lang="en-US">
```bash
mkdir /sys/fs/cgroup/freezer/
mount -t cgroup -o freezer none /sys/fs/cgroup/freezer/
```
</div>
Cette dernière commande monte l'arborescence de groupes relative à ce *cgroup*
*freezer*. Tous les dossiers contenus dans cette racine sont donc des
sous-groupes.
### Création d'un nouveau groupe
La première étape dans l'utilisation d'un *cgroup* est de créer un groupe.
Pour ce faire, il suffit de créer un nouveau dossier dans un groupe existant,
par exemple la racine :
<div lang="en-US">
```bash
mkdir /sys/fs/cgroup/freezer/virli/
ls /sys/fs/cgroup/freezer/virli/
```
</div>
Nous avons maintenant un nouveau groupe de processus `virli` dans le *cgroup*
*Freezer*. Comme il s'agit d'une hiérarchie, le groupe `virli` hérite des
propriétés de son (ses) père(s).
### Rattachement de processus
Pour le moment, ce nouveau groupe ne contient aucune tâche.
Ouvrons un nouveau terminal (c'est lui que l'on va geler), et récupérons son
PID : `echo $$`.
La liste des processus rattachés à un *cgroup* se trouve dans le fichier `task`
du groupe. Pour ajouter une tâche à ce groupe, cela se passe de cette manière :
<div lang="en-US">
```bash
echo $PID > /sys/fs/cgroup/freezer/virli/tasks
```
</div>
Il faut ici remplacer `$PID` par le PID du shell que l'on a relevé juste avant.
En validant cette commande, nous avons déplacé le processus dans ce groupe, il
n'est alors plus dans aucun autre groupe (pour ce *cgroup*, il ne bouge pas
dans les autres *cgroup*s).
Malgré l'utilisation de la redirection `>` (et non `>>`), il s'agit belle et
bien d'un ajout au fichier et non d'un écrasement. Il faut garder en tête que
le système de fichier est entièrement simulé et que certains comportements sont
adaptés.
### Consultation de l'état
En affichant le contenu du dossier `virli`, nous pouvions constater que
celui-ci contenait déjà un certain nombre de fichiers. Certain d'entre-eux sont
en lecture seule et permettent de lire des statistiques instantanées sur le
groupe ; tandis que d'autres sont des propriétés que nous pouvons modifier.
Nous pouvons consulter l'état de gel du groupe en affichant le contenu du
fichier\newline `/sys/fs/cgroup/freezer/virli/freezer.state`.
Pour plus d'information sur les différents fichiers présents dans ce *cgroup*,
consultez
[la documentation associée](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/freezer-subsystem.html).
### Changement d'état
Faisons exécuter à notre interpréteur une commande pour voir effectivement
l'exécution s'arrêter. Si vous manquez d'inspiration, utilisez :
<div lang="en-US">
```bash
for i in $(seq 9999); do echo -n $i; sleep .1; echo -n " - "; sleep .1; done
```
</div>
Maintenant, nous avons donné l'ordre au noyau de ne plus allouer de temps de
calcul à notre shell et ses fils :
<div lang="en-US">
```bash
echo FROZEN > /sys/fs/cgroup/freezer/virli/freezer.state
```
</div>
À cet instant, vous devriez voir votre compteur s'arrêter. Pour reprendre
l'exécution :
<div lang="en-US">
```bash
echo THAWED > /sys/fs/cgroup/freezer/virli/freezer.state
```
</div>
## Exercice : script de monitoring
À nous maintenant de concevoir un script qui va enregistrer vers une base de
données des statistiques issues des *cgroup*s.
### Rappel d'InfluxDB
Commençons par lancer le conteneur Docker d'InfluxDB (pour éviter de
l'installer sur notre machine) :
<div lang="en-US">
```bash
docker container run --name mytsdb -d -p 8086:8086 influxdb
```
</div>
Il nous faut ensuite créer une base de données pour y stocker nos
métriques. Voici comment on s'était débrouillé dans un précédent TP pour
interagir avec InfluxDB :
<div lang="en-US">
```bash
docker container exec -i mytsdb influx <<EOF
CREATE DATABASE metrics;
SHOW DATABASES;
EOF
```
</div>
Vérifiez que la base de données `metrics` a bien été créée.
### Monitoring instantané vers la console
Dans un premier temps, commençons par afficher dans la console, la quantité de
mémoire utilisée par le groupe monitoré.
Vous pouvez utiliser un programme comme
[`memhog`](https://virli.nemunai.re/memhog.c) pour remplir rapidement votre
mémoire.
<div lang="en-US">
```
42sh# mkdir /sys/fs/cgroup...
42sh$ echo $$ | sudo tee /sys/fs/cgroup.../tasks
42sh# ./monitor group_name memhog 500
~~~ 13595 ~~~ Current memory usage: 75194368/550502400 (13%)
~~~ 13595 ~~~ Current memory usage: 150290432/550502400 (27%)
~~~ 13595 ~~~ Current memory usage: 223690752/550502400 (40%)
~~~ 13595 ~~~ Current memory usage: 296828928/550502400 (53%)
~~~ 13595 ~~~ Current memory usage: 368001024/550502400 (66%)
~~~ 13595 ~~~ Current memory usage: 438517760/550502400 (79%)
~~~ 13595 ~~~ Current memory usage: 480329728/550502400 (87%)
~~~ 13595 ~~~ Current memory usage: 155648/550502400 (0%)
```
</div>
Si vous n'avez pas le *cgroup* *memory*, il est possible qu'il ne soit pas
activé par défaut par votre système. Si vous êtes dans ce cas, essayez
d'ajouter `cgroup_enable=memory` à la ligne de commande de votre noyau.
### Monitoring vers InfluxDB
Maintenant, envoyons nos données vers la base
<https://docs.influxdata.com/influxdb/v1.6/guides/writing_data/> :
<div lang="en-US">
```bash
curl -i -XPOST 'http://localhost:8086/write?db=metrics' --data-binary \
"$my_cgroup_name memory.usage_in_bytes=$(cat .../my_cgroup_name/memory.usage_in_bytes)"
```
</div>
Pour vérifier que les données ont bien été ajoutées, nous pouvons effectuer la
requête suivante dans notre client `influx` :
<div lang="en-US">
```sql
SELECT * from "$my_cgroup_name";
```
</div>
### Monitorer davantage de données
Liste non exhaustive de données à monitorer :
* Nombre d'IOs effectué ;
* nombre d'octets lu/écrit sur les disques ;
* temps de calcul utilisé ;
* trafic réseau généré ;
* ...
Tous les cgroups existants dans le dernier noyau publié ont leur documentation
accessible ici :
<https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/index.html>
### Permettre à l'utilisateur de monitorer des processus
Maintenant, séparons notre script en deux parties afin qu'un utilisateur normal
(non-root) puisse utiliser la partie monitoring de notre script.
Un premier script doit s'occuper de créer le(s) *cgroup*s et lui attribuer les
bons droits, tandis que le deuxième va effectuer le monitoring, sans privilèges
particuliers.
#### Exemple
<div lang="en-US">
```
42sh$ sudo ./monitor_init my_cgroup_name
42sh$ ./monitor my_cgroup_name memhog 500
```
</div>
## Fixer des limites
Au delà de la simple consultation, les *cgroup*s peuvent servir à limiter la
quantité de ressources mise à disposition à un groupe de processus.
Pour définir une limite, nous allons écrire la valeur dans le fichier
correspondant à une valeur limite, comme par exemple
`memory.max_usage_in_bytes`, qui limite le nombre d'octets que notre groupe de
processus va pouvoir allouer au maximum :
<div lang="en-US">
```
42sh$ cat /sys/fs/cgroup/memory/virli/memory.max_usage_in_bytes
0
# 0 = Aucune limite
42sh$ echo 4M > /sys/fs/cgroup/memory/virli/memory.max_usage_in_bytes
# Maintenant, la limite est à 4MB, vérifions...
42sh$ cat /sys/fs/cgroup/memory/virli/memory.max_usage_in_bytes
4194304
```
</div>
Chaque *cgroup*s défini de nombreux indicateurs et possède de nombreux
limiteurs, n'hésitez pas à consulter la documentation associée à chaque
*cgroup*.
## Pour aller plus loin {-}
Pour tout connaître en détails, [la série d'articles de Neil Brown sur les
Control groups](https://lwn.net/Articles/604609/) est excellente !
Depuis les noyaux 4.5, il est possible d'utiliser la nouvelle version du
pseudo système de fichiers des *CGroup*s. Le principal changement vient du
regroupement au sein d'une seule hiérarchie des différents *CGroup*s que l'on
avait dans la v1. Davantage d'informations sont disponibles :
* [Understanding the new control groups API](https://lwn.net/Articles/679786/)
;
* [Kernel Document about Control Group v2](https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html).

46
tutorial/3/check.sh Executable file
View file

@ -0,0 +1,46 @@
#!/bin/sh
note_init() {
NOTE=0
echo -n $@
}
note() {
NOTE=$(($NOTE + $1))
echo -n ,$@
}
for LOGIN in $@
do
note_init $LOGIN
if [ -f "$LOGIN" ]; then
QUESTIONSF="$LOGIN"
else
QUESTIONSF="$LOGIN/questions.txt"
fi
# Questions
if grep -E -i "(grsec)" "${QUESTIONSF}" 2> /dev/null > /dev/null; then
note 1
else
note 0
fi
if grep -E -i "(/proc/.*/oom_|score)" "${QUESTIONSF}" 2> /dev/null > /dev/null; then
note 1
else
note 0
fi
if grep -E -i "root" "${QUESTIONSF}" 2> /dev/null > /dev/null; then
note 1
else
note 0
fi
echo #" = $NOTE"
done

111
tutorial/3/chroot.md Normal file
View file

@ -0,0 +1,111 @@
\newpage
L'isolation ... du pauvre
=========================
Depuis les premières versions d'Unix, il est possible de changer le répertoire
vu comme étant la racine du système de fichiers. En anglais : *change root*:
`chroot`. Le processus effectuant cette action ainsi que tous ses fils, verront
donc une racine différente du reste du système.
## Mise en place de l'environnement
Pour se créer un environnement afin de changer notre racine, il va falloir
commencer par créer le dossier de notre nouvelle racine :
<div lang="en-US">
```bash
mkdir newroot
```
</div>
### `busybox`
Queques mots, pour commencer, à propos du projet Busybox : c'est un programme
*linké* statiquement, c'est-à-dire qu'il ne va pas chercher ni charger de
bibliothèque dynamique à son lancement. Il se suffit donc à lui-même dans un
*chroot*, car il n'a pas de dépendances. Nous pouvons donc tester notre
première isolation\ :
<div lang="en-US">
```bash
cp $(which busybox) newroot/
chroot newroot /busybox ash
```
</div>
Jusque là ... ça fonctionne, rien de surprenant ! Mais qu'en est-il pour
`bash` :
<div lang="en-US">
```bash
42sh$ cp $(which bash) newroot/
42sh# chroot newroot /bash
chroot: failed to run command bash: No such file or directory
```
</div>
De quel fichier est-il question ici ?
### `debootstrap`
`debootstrap` est le programme utilisé par l'installeur des distributions
Debian et ses dérivées. Il permet d'installer dans un dossier (en général, ce
dossier correspond au point de montage de la nouvelle racine choisie par
l'utilisateur lors de l'installation) le système de base.
<div lang="en-US">
```bash
debootstrap buster newroot/ http://httpredir.debian.org/debian/
```
</div>
`pacstrap` est le programme équivalent pour Archlinux.
### *stage3*
Les distributions *à l'ancienne* proposent encore de télécharger leur système
de base sous forme de tarball\ :
<div lang="en-US">
```bash
wget http://gentoo.mirrors.ovh.net/gentoo-distfiles/releases/amd64/autobuilds/current-stage3-amd64/stage3-amd64-20201104T214503Z.tar.xz
tar xpf stage3-amd64-*.tar.xz -C newroot/
```
</div>
L'avantage de télécharger l'archive de Gentoo est que l'on a déjà `gcc` dans un
environnement qui tient dans 300 MB.
## Exercice {-}
Écrivons maintenant un programme dont le seul but est de s'échapper du `chroot` :
<div lang="en-US">
```bash
make escape
echo bar > ../foo
chroot .
```
</div>
Dans le nouvel environnement, vous ne devriez pas pouvoir faire :
<div lang="en-US">
```bash
cat ../foo
```
</div>
Mais une fois votre programme `escape` exécuté, vous devriez pouvoir !
<div lang="en-US">
```
(chroot) 42sh# ./escape
bash# cat /path/to/foo
```
</div>

102
tutorial/3/installation.md Normal file
View file

@ -0,0 +1,102 @@
\newpage
Prérequis
=========
## Noyau Linux
Ce TP requiert un noyau Linux, dans sa version 3.8 au minimum. Il doit de plus
être compilé avec les options suivantes (lorsqu'elles sont disponibles pour
votre version) :
<div lang="en-US">
```
General setup --->
[*] Control Group support --->
[*] Freezer cgroup subsystem
[*] PIDs cgroup subsystem
[*] Device controller for cgroups
[*] Cpuset support
[*] Simple CPU accounting cgroup subsystem
[*] Memory Resource Controller for Control Groups
[*] Group CPU scheduler --->
[*] Group scheduling for SCHED_OTHER
[*] Group scheduling for SCHED_RR/FIFO
<*> Block IO controller
[*] Networking support --->
Networking options --->
[*] Network priority cgroup
[*] Network classid cgroup
```
</div>
### Vérification via `menuconfig`
L'arbre ci-dessous correspond aux options qui seront *built-in* (signalées par
une `*`) ou installées en tant que module (signalées par un `M`). En effet,
chaque noyau Linux peut être entièrement personnalisé en fonction des options
et des pilotes que l'on voudra utiliser.
Pour parcourir l'arbre des options du noyau, il est nécessaire d'avoir les
sources de celui-ci. Les dernières versions stables et encore maintenues sont
disponibles sur la page d'accueil de <https://kernel.org>.
Dans les sources, on affiche la liste des options avec la commande :
<div lang="en-US">
```bash
make menuconfig
```
</div>
### Vérification via `/boot/config-xxx`
Les distributions basées sur Debian ont pour habitude de placer le fichier de
configuration ayant servi à compiler le noyau et ses modules dans le dossier
`/boot`, aux côtés de l'image du noyau `vmlinuz-xxx`, de l'éventuel système de
fichiers initial (`initramfs-xxx`) et des symboles de débogage
`System.map-xxx`.
Ce fichier répertorie toutes les options qui ont été activées. Par rapport à
l'arbre présenté ci-dessus, vous devriez trouver :
<div lang="en-US">
```
CONFIG_CGROUPS=y
CONFIG_CGROUP_FREEZER=y
CONFIG_CGROUP_PIDS=y
CONFIG_CGROUP_DEVICE=y
CONFIG_CPUSETS=y
CONFIG_CGROUP_CPUACCT=y
CONFIG_CGROUP_MEMCG=y
CONFIG_CGROUP_SCHED=y
CONFIG_BLK_CGROUP=y
CONFIG_NET=y
CONFIG_CGROUP_NET_PRIO=y
CONFIG_CGROUP_NET_CLASSID=y
```
</div>
### Vérification via `/proc/config.gz`
Dans la plupart des autres distributions, la configuration est accessible à
travers le fichier `/proc/config.gz`. Comme vous ne pouvez pas écrire dans
`/proc` pour décompresser le fichier, utilisez les outils `zcat`, `zgrep`, ...
Vous devez retrouver les mêmes options que celles de la section précédente.
## Présence des en-têtes
Si vous utilisez un noyau standard fourni par votre distribution, les options
requises seront a priori déjà sélectionnées et vous n'aurez donc pas à compiler
votre propre noyau. Néanmoins, durant ce TP, nous allons nous interfacer avec
le noyau, il est donc nécessaire d'avoir les en-têtes de votre noyau.
Sous Debian, vous pouvez les installer via le paquet au nom semblable à
`linux-headers`. Le paquet porte le même nom sous Arch Linux et ses dérivés.

52
tutorial/3/oom.md Normal file
View file

@ -0,0 +1,52 @@
\newpage
Gestion de la mémoire
=====================
Linux a une gestion de la mémoire bien particulière[^vm-overcommit] : par
défaut, `malloc(3)` ne retournera jamais `NULL`. En se basant sur l'euristique
qu'un bloc mémoire demandé ne sera pas utilisé directement et que de nombreux
process ne feront pas un usage total des blocks qu'ils ont alloués, le noyau
permet d'allouer plus de mémoire qu'il n'y en a réellement disponible. La
mémoire est donc utilisée de manière plus efficace.
[^vm-overcommit]: Dépendant de la valeur de `/proc/sys/vm/overcommit_memory`,
généralement 0. Voir
<https://www.kernel.org/doc/html/latest/vm/overcommit-accounting.html>.
Mais évidemment, cela peut donner lieu à des situations où le noyau n'est plus
en mesure de trouver de bloc physiquement disponible, alors qu'ils avaient
effectivement été alloués au processus. Pour autant, ce n'est pas une raison
pour tuer ce processus, car il est peut-être vital pour le système (peut-être
est-ce `init` qui est en train de gérer le lancement d'un nouveau daemon). On
dit alors que le noyau est *Out-Of-Memory*.
Pour se sortir d'une telle situation, et après avoir tenté de vider les caches,
il lance son arme ultime, pour retrouver au plus vite de la mémoire :
l'*Out-Of-Memory killer*.
## OOM killer
Selon un algorithme dont on raconte qu'il ne serait pas basé entièrement sur
l'aléatoire[^oom-algo], un processus est tiré au sort (plus un processus occupe
de mémoire et plus il a de chance d'être tiré au sort) par l'OOM killer. Le
sort qui lui est réservé est tout simplement une mort brutale. Pour permettre
au système de disposer à nouveau de mémoire disponible. Si cela n'est pas
suffisant, un ou plusieurs autres processus peuvent être tués à tour de rôle,
jusqu'à ce que le système retrouve sa sérénité.
[^oom-algo]: <https://linux-mm.org/OOM_Killer>
## Esquiver l'OOM killer
Au sein d'un *cgroup* *memory*, le fichier `memory.oom_control` peut être
utilisé afin de recevoir une notification du noyau avant que l'OOM-killer
ne s'attaque à un processus de ce groupe.
Grâce à cette notification, il est possible de figer le processus pour
l'envoyer sur une autre machine. Et ainsi libérer la mémoire avant que l'OOM
killer ne passe.
Jetez un œil à [cet article parru sur LWN](https://lwn.net/Articles/590960/) à
ce sujet.

1
tutorial/3/project-body.md Symbolic link
View file

@ -0,0 +1 @@
../../subject/2/project-part1.md

View file

@ -0,0 +1,24 @@
\newpage
Projet et rendu
===============
## Sujet
Vous allez commencer aujourd'hui un projet qui s'étendra au prochain TP et qui
consistera à réaliser la partie d'isolation de la moulinette des ACUs !
Cette semaine, il faudra faire en sorte de restreindre un groupe de processus
pour qu'il ne puisse pas faire de déni de service sur notre machine.
Il n'y a pas de restriction sur le langage utilisé, vous pouvez tout aussi bien
utiliser du C, du C++, du Python, etc.
L'usage de bibliothèques **non relatives** au projet est autorisé : le but de
ce sujet est d'évaluer votre compréhension et votre utilisation de la
tuyauterie bas-niveau du noyau liée à la virtualisation légère. À partir du
moment où vous n'utilisez pas une bibliothèque qui abstrait complètement cette
plomberie, n'hésitez pas à l'utiliser !
Gardez en tête que ce projet sera à continuer au prochain TP, où il sera
principalement question de faire des appels systèmes.

View file

@ -0,0 +1,39 @@
## Modalité de rendu
Un service automatique s'occupe de réceptionner vos rendus, de faire les
vérifications nécessaires et de vous envoyer un accusé de réception (ou de
rejet).
Ce service écoute sur l'adresse <virli@nemunai.re>, c'est donc à cette adresse
et exclusivement à celle-ci que vous devez envoyer vos rendus. Tout rendu
envoyé à une autre adresse et/ou non signé et/ou reçu après la correction ne
sera pas pris en compte.
Par ailleurs, n'oubliez pas de répondre à
[l'évaluation du cours](https://virli.nemunai.re/quiz/6).
## Tarball
Tous les fichiers identifiés comme étant à rendre pour ce TP sont à
placer dans une tarball (pas d'archive ZIP, RAR, ...).
Voici une arborescence type (adaptez les extensions et les éventuels
fichiers supplémentaires associés au langage que vous aurez choisi
pour chaque exercice) :
<div lang="en-US">
```
login_x-TP3/
login_x-TP3/escape.c
login_x-TP3/procinfo.sh
login_x-TP3/suspend_schedule.sh
login_x-TP3/view_caps.c
login_x-TP3/monitor.sh
login_x-TP3/monitor_init.sh
login_x-TP3/syscall_filter.c
```
</div>
Les premières étapes du projet ne sont pas à rendre et feront l'objet
d'un rendu à part.

252
tutorial/3/pseudofs.md Normal file
View file

@ -0,0 +1,252 @@
\newpage
Pseudos systèmes de fichiers
============================
## Rappels sur les points de montage
Les systèmes Unix définissent le système de fichiers comme étant un arbre
unique partant d'une racine[^FHS] et où l'on peut placer au sein de son arborescence
des points de montage. Ainsi, l'utilisateur définit généralement deux points de
montage :
[^FHS]: Consultez
<https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard> pour
plus de détails sur l'arboresence.
<div lang="en-US">
```
/dev/sda1 on / type ext4 (rw,relatime,data=ordered)
/dev/sda3 on /home type ext4 (rw,relatime,data=ordered)
```
</div>
Dans ce schéma, la racine correspond à la première partition du premier disque,
et les fichiers des utilisateurs sont sur la troisième partition du premier
disque.
## Présentation des pseudos systèmes de fichiers
D'autres points de montage sont utilisés par le système : `/dev`, `/proc`,
`/tmp`, ... Ces points de montage vont, la plupart du temps, être montés par le
programme d'initialisation en utilisant des systèmes de fichiers virtuels, mis
à disposition par le noyau.
Ces systèmes sont virtuels, car ils ne correspondent à aucune partition d'aucun
disque : l'arborescence est créée de toute pièce par le noyau pour trier les
informations mises à disposition, mais il n'est pas toujours possible d'y
apporter des modifications.
Linux emploie de nombreux systèmes de fichiers virtuels :
- `/proc` : contient, principalement, la liste des processus (`top` et ses
dérivés se contentent de lire les fichiers de ce point de montage) ;
- `/proc/sys` : contient la configuration du noyau ;
- `/sys` : contient des informations à propos du matériel (utilisées notamment
par `udev` pour peupler `/dev`) et des périphériques (taille des tampons,
clignottement des DELs, ...) ;
- `/sys/firmware/efi/efivars` : pour accéder et modifier les variables de
l'UEFI ;
- ...
Tous ces systèmes de fichiers sont généralement exclusivement stockés en
RAM. Pour rendre une modification persistante, il est nécessaire de modifier un
fichier de configuration qui sera chargé par le programme d'initialisation. Par
exemple, pour modifier les paramètres du noyau, on passe par le fichier
`/etc/sysctl.conf` et du programme `sysctl`.
### Consultation et modification
La consultation d'un élément se fait généralement à l'aide d'un simple `cat` :
<div lang="en-US">
```
42sh$ cat /sys/power/state
freeze mem
```
</div>
La modification d'un élément se fait avec `echo`, comme ceci :
<div lang="en-US">
```bash
42sh# echo mem > /sys/power/state
```
</div>
Vous devriez constater l'effet de cette commande sans plus attendre !
## Exercices
### `procinfo`
Explorons le pseudo système de fichiers `/proc` pour écrire un script qui va
afficher des informations sur un processus donné :
<div lang="en-US">
```
42sh$ ./procinfo $$
PID: 4242
Path: /bin/bash
Command line: bash
Working directory: /home/nemunaire/virli/
Root: /
State: S (sleeping)
Threads: 1
CGroups
=======
12:pids:/
11:net_prio:/
10:perf_event:/
9:net_cls:/
8:freezer:/
7:devices:/
6:memory:/
5:blkio:/
4:cpuacct:/
3:cpu:/
2:cpuset:/
1:name=openrc:/
Namespaces
==========
cgroup:[4026531835]
ipc:[4026531839]
mnt:[4026531840]
net:[4026531969]
pid:[4026531836]
user:[4026531837]
uts:[4026531838]
```
</div>
### `batinfo.sh`, `cpuinfo.sh`
Explorons le pseudo système de fichiers `/sys` pour écrire un script
qui va, en fonction de ce que vous avez de disponible :
* nous afficher des statistiques sur notre batterie ;
* nous afficher des statistiques la fréquence de notre CPU.
#### `batinfo.sh`
Voici un exemple d'utilisation :
<div lang="en-US">
```
42sh$ ./batinfo.sh
BAT0 Discharging
====
Capacity: 83% (Normal)
Voltage: 11.972000 V (minimal: 11.400000 V)
Energy: 18.290000/21.830000 Wh
Power: 7.937000 W
Remaining time: 2.304 h
BAT1 Unknown
====
Capacity: 83% (Normal)
Voltage: 11.972000 V (minimal: 11.400000 V)
Energy: 18.290000/21.830000 Wh
Power: 0.0 W
Remaining time: N/A
```
</div>
Pour les détails sur l'organisation de ce dossier, regardez :
<https://www.kernel.org/doc/Documentation/power/power_supply_class.txt>.
#### `cpuinfo.sh`
Voici un exemple d'utilisation :
<div lang="en-US">
```
42sh$ ./cpuinfo.sh
cpu0
====
Current frequency: 2100384 Hz
Current governor: powersave
Allowed frequencies: between 500000 - 2100000 Hz
Thermal throttle count: 0
cpu1
====
Current frequency: 2099871 Hz
Current governor: powersave
Allowed frequencies: between 500000 - 2100000 Hz
Thermal throttle count: 0
```
</div>
N'hésitez pas à rajouter toute sorte d'information intéressantes !
### `rev_kdb_leds.sh`, `suspend_schedule.sh`
Maintenant que vous savez lire des informations dans `/sys`, tentons d'aller
modifier le comportement de notre système. Au choix, réaliser l'un des scripts
suivant, en fonction du matériel dont vous disposez :
* inverser l'état des diodes de notre clavier ;
* mettre en veille votre machine, en ayant programmé une heure de réveil.
#### `rev_kdb_leds.sh`
Si vous avez :
* numlock On,
* capslock Off,
* scrolllock Off ;
Après avoir exécuté le script, nous devrions avoir :
* numlock Off,
* capslock On,
* scrolllock On.
Voici un exemple d'utilisation :
<div lang="en-US">
```
42sh# ./rev_kdb_leds.sh input20
```
</div>
`input20` correspond à l'identifiant de votre clavier, sous
`/sys/class/input/`.
#### `suspend_schedule.sh`
Votre script prendra en argument l'heure à laquelle votre machine doit être
réveillée, avant de la mettre effectivement en veille.
Bien sûr, vous ne devez utiliser que des écritures (et des lectures) dans le
système de fichiers `/sys`. Il n'est pas question de faire appel à un autre
programme (vous pourriez cependant avoir besoin de `date(1)` pour faire les
calculs horaires).
Voici un exemple d'utilisation :
<div lang="en-US">
```
42sh# ./suspend_schedule.sh 15:42
```
</div>
Vous aurez besoin de définir une alarme au niveau de votre RTC, via le
fichier : `/sys/class/rtc/rtcX/wakealarm`.
Attention au fuseau horaire utilisé par votre RTC, si votre système principal
est Windows, elle utilisera sans doute le fuseau horaire courant. Sinon, ce
sera UTC.
Un article très complet sur le sujet est disponible ici :
<https://www.linux.com/tutorials/wake-linux-rtc-alarm-clock/>

View file

@ -1,49 +0,0 @@
\newpage
Rendu
=====
Modalités de rendu
------------------
En tant que personnes sensibilisées à la sécurité des échanges électroniques,
vous devrez m'envoyer vos rendus signés avec votre clef PGP.
Un service automatique s'occupe de réceptionner vos rendus, de faire des
vérifications élémentaires et de vous envoyer un accusé de réception (ou de
rejet).
Ce service écoute sur l'adresse <virli@nemunai.re>, c'est donc à cette adresse
et exclusivement à celle-ci que vous devez envoyer vos rendus. Tout rendu
envoyé à une autre adresse et/ou non signé et/ou reçu après la correction ne
sera pas pris en compte.
Par ailleurs, n'oubliez pas de répondre à
[l'évaluation du cours](https://virli.nemunai.re/quiz/5).
Tarball
-------
Tous les fichiers identifiés comme étant à rendre pour ce TP sont à
placer dans une tarball (pas d'archive ZIP, RAR, ...).
Voici une arborescence type (vous pourriez avoir des fichiers
supplémentaires) :
<div lang="en-US">
```
login_x-TP3/
login_x-TP3/cicd-playbook/
login_x-TP3/cicd-playbook/cicd-setup.yml
login_x-TP3/cicd-playbook/roles/...
login_x-TP3/youp0m/
login_x-TP3/youp0m/.drone.yml
login_x-TP3/youp0m/.ansible/... # Pour ceux qui auraient fait le 5.4 optionnel
login_x-TP3/youp0m/Dockerfile
login_x-TP3/youp0m/entrypoint.sh
login_x-TP3/youp0m/.dockerignore
login_x-TP3/youp0m/...
login_x-TP3/registry_play.sh
```
</div>

92
tutorial/3/seccomp.md Normal file
View file

@ -0,0 +1,92 @@
\newpage
Secure Computing Mode
=====================
Plus connue sous l'acronyme *seccomp*, cette fonctionnalité du noyau Linux
permet de restreindre les appels systèmes qu'un processus est autorisé à
utiliser. En cas d'appel non autorisé, le processus fautif est directement tué
(`SIGKILL`) par le noyau, ou, lorsque c'est précisé, un code `errno`
particulier peut être renvoyé au programme.
Depuis la version 3.17 du noyau, l'appel système `seccomp(2)` permet de faire
entrer le processus courant dans ce mode. En effet, c'est le processus lui-même
qui déclare au noyau qu'il peut désormais se contenter d'une liste réduite
d'appel système ; à l'inverse des politiques de sécurité comme SELinux ou
AppArmor, qui encapsulent les programmes pour toute la durée de leur exécution.
*Seccomp* est particulièrement utile lorsqu'un processus a terminé son
initialisation (ne dépendant en général pas de données sujettes à
l'exploitation de vulnérabilité) et doit commencer à entrer dans des portions
de code promptes aux vulnérabilités : c'est notamment le cas des moteurs de
rendus des navigateurs Firefox et Chrome.
## Prérequis
L'utilisation de `seccomp` nécessite d'avoir un noyau compilé avec l'option
`CONFIG_SECCOMP`.
Vous aurez également besoin de la bibliothèque `libseccomp` car l'appel système
`seccomp(2)` n'a pas de *wrapper* dans la libc, vous devrez donc passer par
cette bibliothèque.
## `MODE_STRICT`
Le mode traditionnel de *seccomp* est de ne permettre uniquement l'utilisation
des appels système `read(2)`, `write(2)` (sur les descripteurs de fichier déjà
ouvert), `_exit(2)` et `sigreturn(2)`.
Historiquement, avant la création de l'appel système `seccomp(2)`, on activait
ce mode via :
<div lang="en-US">
```c
prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT);
```
</div>
Une fois passé cet appel système, toute entrée dans un appel système non
autorisé conduit à un `SIGKILL` du processus.
## `MODE_FILTER`
Plus modulable que le mode strict, le mode de filtrage permet une grande
amplitude en permettant au programmeur de définir finement quels appels
systèmes le programme est autorisé à faire ou non, et quel sentence est
exécutée en cas de faute.
Notons que les processus fils issus (`fork(2)` ou `clone(2)`) d'un processus
auquel est appliqué un filtre `seccomp`, héritent également de ce filtre.
La construction de ce filtre est faite de manière programatique, via
des règles BPF (`Berkeley Packet Filter`). On passe ensuite ce filtre BPF en
argument de l'appel système :
<div lang="en-US">
```c
struct sock_filter filter[];
struct sock_fprog prog = {
.len = (unsigned short) (sizeof(filter) / sizeof(filter[0])),
.filter = filter,
};
seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog);
```
</div>
### Exercice {-}
Écrivez un programme filtrant un appel système, à l'aide de `seccomp` :
<div lang="en-US">
```
42sh$ ./syscall_filter sleep 5
sleep: cannot read realtime clock: Operation not permitted
42sh$
```
</div>
Dans cet exemple, l'appel système filtré est `nanosleep(2)`.

View file

@ -1,20 +1,20 @@
---
title: Virtualisation légère -- TP n^o^ 3
subtitle: DevOps, intégration et déploiement continu
subtitle: Linux Internals partie 1
author: Pierre-Olivier *nemunaire* [Mercier]{.smallcaps}
institute: EPITA
date: Mercredi 28 octobre 2020
date: Jeudi 30 septembre 2021
abstract: |
Durant ce troisième TP, nous allons jouer les DevOps et déployer
automatiquement des services !
Ce premier TP consacré aux Linux Internals va nous permettre
d'appréhender les notions de pseudos systèmes de fichiers, de
cgroups ainsi que de capabilities.
\vspace{1em}
Tous les éléments de ce TP (exercices et projet) sont à rendre à
<virli@nemunai.re> au plus tard le **mercredi 4 novembre 2020 à 12 h
42**. Consultez la dernière section de chaque partie pour plus d'information
sur les éléments à rendre. Et n'oubliez pas de répondre aux [questions de
cours](https://virli.nemunai.re/quiz/5).
Certains éléments de ce TP sont à rendre à <virli@nemunai.re> au
plus tard le jeudi 12 novembre 2020 à 12 h 42. Consultez la
dernière section de chaque partie pour plus d'information sur les
éléments à rendre.
En tant que personnes sensibilisées à la sécurité des échanges électroniques,
vous devrez m'envoyer vos rendus signés avec votre clef PGP. Pensez à