Quelques bricoles pour renforcer Linux : ASLR, lockdown, PTI (Spectre/Meltdown)...
Linux a lui aussi droit à son confinement ! Nous allons voir quelques paramètres non-exhaustifs pour renforcer sa sécurité.
Avertissement
Je préfère prévenir avant de commencer :
- Cet article n'a pas pour ambition de se substituer aux documentations officielles, ni de se suffire à lui-même. J'y partage simplement quelques mesures non-exhaustives à compléter et dont leur impact est à mesurer individuellement.
- Tous les paramètres (ainsi que linux-hardened) sont à tester un par un, idéalement sur une machine d'essai (pourquoi pas une VM) avant de les mettre en place sur vos machines personnelles.
- Enfin, ces "bricoles" sont ce qu'elles sont, rien de plus : bien qu'elles pourraient augmenter drastiquement la protection de Linux contre d'éventuels exploits, elles ne "réparent" pas ses déficiences.
Des bricoles ne remplacent pas la robustesse d'un modèle de sécurité. Elles sont toutefois bienvenues pour rendre plus difficile certaines exploitations.
Contexte
Il raconte sa vie, passez si c'est trop long...
Il n'y a pas si longtemps, j'évoquais l'insécurité du modèle Linux desktop en insistant parfois sur la surface d'attaque que représente Linux. Pour résumer (en vrac) : trop de privilèges (laissant place à une guerre philosophique), noyau monolithique, attaques possibles liées à la mémoire, etc.
Récemment, j'ai expliqué comment mettre en place des solutions d'isolation particulières telles qu'avec gVisor. Si vous pensiez que ce n'était réservé qu'aux conteneurs, détrompez-vous : gVisor (sous réserve de maturation du projet) pourrait tout à fait subvenir aux besoins d'applications du quotidien. GrapheneOS vise à utiliser gVisor dans une optique de moyen terme.
Fuchsia, perçu comme le remplaçant d'Android et qui utilisera un microkernel (kind of), utilisera sans doute une couche de compatibilité similaire à gVisor : une émulation de l'API Linux dans l'espace utilisateur. On pourrait peut-être y voir dans gVisor les fondations de ce travail...
Je ne suis personnellement plus vraiment utilisateur de Linux sur desktop, mais j'ai entendu les critiques de certains par rapport à mon premier article (qui a été mis à jour continuellement). Parmi elles, j'ai vu que "je ne proposais rien" : en effet, je suggère implicitement de passer sur un OS avec un vrai modèle de sécurité, mais je laisse la porte ouverte à une amélioration future.
Malgré tout, j'utilise Linux sur mes serveurs (et mon smartphone Android), et je ne vois pas comment je pourrais m'en passer. Donc non, je ne m'en fous pas ; et oui, j'ai quelques "solutions" à proposer dont je ne suis que le médiateur, je n'ai rien inventé ici.
linux-hardened
C'est quoi ?
Pour les plus téméraires d'entre vous, vous pouvez simplement utiliser linux-hardened. C'est un projet qui vise à fournir des patchs relativement légers à Linux et renforcent sa sécurité sans non plus sacrifier toutes les performances. Si vous aviez lu mes articles, ce n'est pas la première fois que je mentionne linux-hardened : en effet, ce projet émane directement de GrapheneOS qui l'utilise.
Voyez linux-hardened comme une alternative à grsecurity/PaX.
Ah, vous vous souvenez de grsecurity ? Malheureusement, leurs patchs sont devenus payants.
"Hardened", comment ça ?
Vous allez rire mais il n'y a pas vraiment de page qui recense toutes les améliorations apportées, et c'est bien dommage ; les mainteneurs réfèrent souvent à l'historique des commits.
J'ai fait un petit travail de recensement et voici ce que vous pouvez noter :
- Beaucoup d'améliorations notables à ASLR (Address Space Layout Randomization), une technique de défense largement employée qui place aléatoirement les adresses mémoires d'un processus pour rendre plus difficile les attaques de type buffer overflow
- Restrictions d'accès par les capabilities
- Renforcement de l'allocation Slab (slab canaries)
- Compilation avec des paramètres sûrs :
FORTIFY_SOURCE
et Stack Smashing Protection pour limiter des attaques de buffer overflow - Le projet suggère toutefois d'être compilé avec
clang
plutôt que GCC pour bénéficier de CFI/ShadowCallStack, dont seul Google tire à ce jour profit pour les kernels des Google Pixel - Des paramètres par défauts considérés comme robustes et sûrs, dont la plupart sera vue ultérieurement
A noter pour CFI : son support sera intégré upstream pour la version 5.13. Cela devrait rendre son utilisation bien plus simple !
Au fond, ça reste un patch assez minimaliste qui ne doit pas être utilisé tout seul. Par exemple, le projet vous suggère de mettre en place vous-même votre MAC (mandatory access control) tel que AppArmor ou SELinux.
Cela dit, c'est un point d'entrée robuste !
Stable ou LTS (long term support) ?
linux-hardened propose des patchs pour les versions stables et LTS de Linux.
- Les versions stables sont plus souvent mises à jour en termes de fonctionnalité (parfois de sécurité) : elles peuvent introduire, outre la surface d'attaque supplémentaire, des bugs et donc des failles potentielles.
- Les LTS sont moins souvent mises à jour, mais vous avez des fonctionnalités stables et souvent éprouvées.
C'est une question de compromis, mais j'aurais tendance à suggérer les LTS sur des machines qui doivent rester "stables" justement, et les versions "stable" pour tout le reste. Dans tous les cas, ne négligez pas les mises à jour de votre kernel (surtout sur serveur, je vous vois avec vos concours d'uptime).
Bonus : lien intéressant sur le sujet.
Installation
AVERTISSEMENT : linux-hardened peut "casser" des logiciels que vous utilisez donc il faut absolument tester en amont, et prévoir un downgrade en cas de besoin.
Si vous utilisez Arch Linux, c'est votre jour de chance : linux-hardened y est proposé comme paquet, maintenu par le développeur principal de linux-hardened lui-même. Autrement, on va devoir compiler.
Pour ce tutoriel très bref, j'utilise Debian Buster à jour configuré pour recevoir des paquets des backports. Ces derniers vous permettent de bénéficier de certains paquets à jour et de la même qualité que les paquets standards, donc je vous suggère très fortement de l'utiliser.
Commençons par installer les prérequis à la compilation de Linux (avec permissions administrateur) :
apt install build-essential xz-utils libncurses-dev bison flex libssl-dev libelf-dev
Maintenant petit point particulier, la version de pahole
sur Debian Buster est trop vieille pour compiler des versions récentes. Donc on va l'installer depuis les backports :
apt install -t buster-backports dwarves
Désormais les permissions administrateur ne seront plus nécessaires excepté pour l'installation proprement dite. Maintenant vous pouvez créer un dossier de travail, y télécharger les sources de la version souhaitée et l'extraire tout en vérifiant son authenticité :
VERSION_LINUX=5.11.1 # à remplacer
mkdir linux-hardened && cd linux-hardened
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-${VERSION_LINUX}.tar.xz
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-${VERSION_LINUX}.tar.sign
xz -d -v linux-${VERSION_LINUX}.tar.xz
gpg2 --locate-keys torvalds@kernel.org gregkh@kernel.org
gpg2 --verify linux-${VERSION_LINUX}.tar.sign
tar xvf linux-${VERSION_LINUX}.tar
VERSION_LINUX
est à changer selon vos besoins et la disponibilité des patchs de linux-hardened (cf. les releases sur Github). Tant qu'à faire, téléchargeons le patch correspondant et appliquons-le :
wget https://github.com/anthraxx/linux-hardened/releases/download/${VERSION_LINUX}-hardened1/linux-hardened-${VERSION_LINUX}-hardened1.patch
cd linux-${VERSION_LINUX}
patch -p1 < ../linux-hardened-${VERSION_LINUX}-hardened1.patch
Enfin, il ne reste que des étapes classiques pour entamer la compilation proprement dite. Vous pouvez partir d'une .config
pré-existante (facultatif) :
cp -v /boot/config-$(uname -r) .config
Puis lancer menuconfig
, apporter vos modifications si nécessaires, enregistrer puis quitter :
make menuconfig
Si vous avez utilisé un .config
de kernel Debian, pensez à commenter deux lignes dans ce fichier sans quoi la compilation ne commencera pas :
CONFIG_MODULE_SIG_KEY
CONFIG_SYSTEM_TRUSTED_KEYS
Au lieu de fournir un certificat qui n'existera pas à moins de l'importer manuellement, vous allez en générer un vous-même.
La compilation proprement dite peut être lancée :
make -j x
"x" à remplacer par le nombrer de cœurs CPU que vous souhaitez dédier à la compilation.
Le temps de se faire un thé vert (ou deux), la compilation devrait se finir sans obstacles et vous devez passer en administrateur pour installer le kernel :
make modules_install
make install
Normalement GRUB (ou apparenté) devrait se configurer tout seul, mais si ce n'est pas le cas, passez un coup de update-grub
ou grub-mkconfig
selon la distribution. Enfin, on redémarre et on regarde si le kernel est bien installé :
$ uname -a
Linux xxxxx 5.11.11-hardened1 #1
Bien, c'est fait !
Linux vanilla : quoi faire ?
Vous avez le choix d'utiliser linux-hardened, mais selon votre distribution, vous n'avez pas nécessairement envie de compiler à chaque mise à jour. Heureusement, il y a tout de même des mesures de renforcement que l'on peut appliquer à des versions standards de Linux.
Un noyau récent
Tout d'abord je vous conseille d'utiliser une version LTS/stable récente, par exemple si vous utilisez Debian Buster, passez par les backports :
apt install -t buster-backports linux-image-amd64
Attention ! Je me suis récemment rendu compte que les backports n'ont pas d'équipe de sécurité dédiée (comme Sid), et bien qu'ils puissent être un meilleur choix si mis à jour régulièrement, il pourrait s'avérer difficile de corriger certaines vulnérabilités rapidement.
Je vous réfère au paragraphe plus haut concernant le compromis stable/LTS. Arch Linux a un paquet linux-lts que j'ai l'habitude d'utiliser.
Les kernel proposés par les distributions sont généralement de bonne qualité et disposent d'une maintenance dans l'ordre du possible. Seulement le système de CVE n'est pas infaillible, y compris pour Linux, ce qui peut brouiller le travail de backports et c'est ce que je tentais d'expliquer dans un article précédent. C'est pour cette raison que je privilégie des kernel récents.
Compiler ou pas ?
Vous pouvez tout à fait reprendre les instructions précédentes, exclure l'étape du patch et compiler votre propre kernel. Cela a plusieurs avantages :
- Vous pouvez réduire la surface d'attaque en configurant un kernel minimal avec seulement ce dont vous avez besoin.
- Certains types d'attaques exploitent des emplacements connus de pointeurs : en compilant votre propre kernel, vous aurez des pointeurs uniques ce qui rend ces attaques reposant sur des emplacements définis plus difficiles.
- Ne pas dépendre d'un mainteneur pour mettre à jour son kernel (il faut), mais à voir si l'effort vaut la peine pour vous.
Si vous décidez de compiler, cette page vous indiquera des options de configuration intéressantes à mettre en place.
Linux Security Modules & MAC
LSM (Linux Security Modules) est un framework de Linux qui permet par exemple aux MAC (AppArmor, SELinux, TOMOYO) de fonctionner. Ce sont des outils puissants qui demandent d'être particulièrement intégrés au kernel, et sont également très complexes, si bien que ce ne sera pas dans l'intérêt de ce billet d'aller dans le détail.
Un MAC permet de restreindre un programme à ses accès spécifiques (chemins, ports, etc.) permettant ainsi d'éviter une catastrophe en cas de compromission dudit programme.
Sachez que SELinux permet un contrôle bien plus précis, mais il est aussi bien plus complexe à maîtriser. AppArmor est plus simple, mais moins précis. Pour le moment, je conseille d'utiliser une distribution qui s'intègre avec l'un des deux : Debian Buster utilise AppArmor par exemple.
Je vous réfère en conséquence à la documentation de votre distribution.
Pour savoir quels LSM sont utilisés :
# cat /sys/kernel/security/lsm
lockdown,capability,yama,apparmor,tomoyo
Linux se fait aussi confiner (lockdown mode)
Depuis sa version 5.4, Linux embarque une nouvelle fonctionnalité : le mode lockdown. Proposé par l'ingénieur de Google Matthew Garett, ce mode a mis un bon bout de temps avant d'être intégré upstream. Et pour cause, il s'attaque à un problème philosophique de taille : les pleins privilèges.
Traditionnellement, la frontière entre le kernel et l'espace utilisateur est mince. Beaucoup d'informations sensibles s'échangent de l'un à l'autre, et en particulier, l'espace utilisateur a un un pouvoir de modification puissant sur le kernel notamment par l'utilisateur root.
Mais est-ce que root doit vraiment avoir les "pleins pouvoirs", même sur Linux proprement dit ?
Et c'est Linus Torvalds lui-même qui était très critique de l'idée de verrouiller des aspects du kernel, avant de céder après des années. Ce mode a été plus ou moins implémenté auparavant par certaines distributions.
Un des facteurs qui a poussé à l'inclusion de ce mode est l'avènement de l'UEFI Secure Boot. En effet, son modèle de sécurité n'a que très peu de sens si l'userspace peut à ce point altérer en profondeur le système et, dans le cas d'un malware, gagner facilement la persistance au démarrage.
Entre autres, le mode lockdown agit de plusieurs façons en désactivant des fonctionnalités qui peuvent altérer le noyau :
- Intransigeance sur la signature des modules utilisés (potentiel conflit avec dkms, je vous préviens d'avance)
- Accès restreint à
/dev/mem
,/dev/kmem
et/dev/port
- Verrouillage du DMA (Direct Memory Addressing) par l'insertion d'appareils (PCI par exemple), etc.
Tout ce qu'il faut retenir, c'est que le lockdown empêche généralement à l'userspace (même root) d'altérer le fonctionnement du kernel.
lockdown possède deux modes :
- integrity : c'est le niveau d'implémentation de base qui restreint l'accès de l'userspace au kernel (bye Kexec, BPF)
- confidentiality : c'est un superset du mode précédent qui empêche même root d'accéder à des informations sensibles (comme des clés EVM)
Le lockdown peut être activé dans un paramètre de démarrage (par exemple GRUB_CMDLINE_LINUX_DEFAULT
dans /etc/default/grub
):
lockdown=integrity
A l'issue d'un redémarrage (après mise à jour de grub, selon votre distribution), on peut vérifier que le lockdown est actif :
# cat /sys/kernel/security/lockdown
none [integrity] confidentiality
Attention, donc : le mode lockdown empêche de charger des modules qui ne sont pas signés, certaines fonctionnalités de fonctionner (Kexec, BPF), mais apparemment les capacités d'hibernation également. C'est une option intéressante à activer au cas par cas.
Autres paramètres de démarrage
Ces paramètres peuvent être appliqués de la même façon que le lockdown.
Cette liste est inspirée par celle gracieusement fournie par madaidan, chercheur en sécurité et développeur chez Whonix. Il fournit davantage de paramètres, mais j'en ai sélectionné quelques uns seulement qui n'impactent pas autant les performances.
slab_nomerge
Allow slab_nomerge to be set at build time (openwall.com)
vsyscall=none
Obsolètes et remplacées par vDSO.
randomize_kstack_offset=on
Protection probabilistique de la stack du kernel inspirée de la fonctionnalité RANDKSTACK de PaX. Disponible depuis 5.13 avec un coût minimal en performances.
init_on_alloc=1 init_on_free=1
Permet de prévenir des fuites d'information sensibles, et de certaines vulnérabilités Use After Free (qui permettent des executions de code arbitraire via des pointeurs qui ne pointent pas vers un objet propre).
page_alloc.shuffle=1
Randomise l'allocation de pages en mémoire, peut aussi bénéficier en performances.
debugfs=off
Désactive debugfs
qui expose des informations sensibles.
pti=on
PTI (anciennement KAISER) est l'acronyme de Page-Table Isolation, une stratégie de mitigation de la fameuse vulnérabilité Meltdown, et qui plus généralement protège des bypass de l'ASLR mentionné plus haut. Cette mitigation a cependant un coût théorique en performance (variable selon l'architecture CPU et la tâche, donner un % n'a pas vraiment de sens).
Je vous conseille par ailleurs (chez Debian en tout cas) l'excellent outil spectre-meltdown-checker
qui permet d'avoir une vue globale de toutes les vulnérabilités liées à Spectre/Meltdown et vos mitigations mises en place.
Une fois ces paramètres ajoutés, vous pouvez redémarrer pour les appliquer et tester par exemple avec cet utilitaire.
Mise à jour concernant slub_debug
: cette option n'est plus recommandée depuis une modification upstream qui désactive le hash des pointeurs en parallèle, vu que c'est une option considérée de debugging. Vous devriez utiliser linux-hardened pour sa protection offerte par les slab canaries.
Réduction de la surface d'attaque
Il faut éviter autant que possible la présence de modules. Même si ceux-ci ne sont pas chargés, vous n'êtes pas à l'abris d'une possibilité où un utilisateur peut causer indirectement le chargement d'un module vulnérable.
Si vous compilez votre propre kernel, vous êtes plus qu'encouragé à le délester d'autant de modules inutiles que possible. Une alternative (valable pour ceux qui ne compilent pas leur kernel) est de les blacklister grâce à des fichiers de configuration qui seront lus dans /etc/modprobe.d
:
Voici plusieurs exemples (fournis par madaidan) :
Cette liste bloque des protocoles réseau peu communs.
Cette liste bloque des systèmes de fichier rarement utilisés.
Le module vivid est lui aussi rarement utilisé.
Désactivez le Bluetooth si vous n'en avez pas besoin !
Désactive la webcam.
Paramètres sysctl
sysctl
permet de modifier certains paramètres du kernel. C'est un outil à utiliser avec précaution. Une valeur peut être changée à chaud ainsi :
sysctl -w $parametre = $valeur
Pour que la modification soit permanente, elle doit être ajoutée dans /etc/sysctl.conf
ou dans un fichier dans /etc/sysctl.d
.
Cette liste s'inspire de celle de madaidan mais aussi des recommandations de l'ANSSI. Je choisis d'ajuster quelques paramètres et d'en omettre quelques uns pour atteindre un compromis plus personnel. Libre à vous de vous documenter sur chacune de ces modifications (et vous devriez !).
Protections du kernel
Ces paramètres sont à ranger dans /etc/sysctl.d/51-kernel.conf
.
kernel.kptr_restrict=2
Ce paramètre limite les fuites des pointeurs du kernel. Ces derniers, si leurs emplacements sont connus, permettent de faciliter des exploitations.
kernel.dmesg_restrict=1
Ce paramètre limite les données sensibles exposées par le kernel à dmesg
, son outil de log.
kernel.printk=3 3 3 3
Idem que ci-dessus, mais cette fois-ci concernant les messages durant la séquence de démarrage.
kernel.unprivileged_bpf_disabled=1
net.core.bpf_jit_harden=2
Ces paramètres restreignent eBPF, et adopte la mitigation du constant blinding traditionnellement adoptée par les JIT (compilateurs à la volée) qui consiste (très succinctement) à obfusquer des valeurs dans le code compilé avec des valeurs aléatoires.
kernel.sysrq=4
SysRq (System Request) est une combinaison de touches qui permettent de mener des opérations telles que le redémarrage du système, ou d'autres opérations potentiellement dangereuses qui sont ainsi exposées à des utilisateurs non-privilégiés. C'est un problème qui ne concerne pas seulement les attaquants physiques.
Il convient donc de le restreindre au maximum. La valeur 4
désactivera la globalité des opérations possibles excepté la séquence Secure Attention Key qui permet d'accéder à root de façon sécurisée dans certaines circonstances. Si cela n'est même pas nécessaire pour vous, la valeur 0
désactivera toutes les fonctionnalités liées à SysRq.
kernel.kexec_load_disabled=1
kexec()
est un appel système qui permet de démarrer un autre kernel sans redémarrage. Cette fonctionnalité peut être détournée pour charger du code malveillant, il est préférable de la désactiver.
kernel.unprivileged_userns_clone=0
Les user namespaces sont abordés dans cet article. Cette fonctionnalité est intéressante, mais expose une surface d'attaque : elle peut ainsi être restreinte à la capacité CAP_SYS_ADMIN. D'autres outils de sandboxing sont recommandés en cas d'environnements non-privilégiés.
kernel.perf_event_paranoid=3
Ce paramètre restreint l'usage de l'outil perf
qui permet des mesures de performances. Utile en cas de debug, il doit être restreint autrement : ici à la capacité CAP_PERFMON.
vm.unprivileged_userfaultfd=0
userfaultfd()
est un appel système qui peut être abusé également, ce paramètre le restreint à la capacité CAP_SYS_PTRACE.
Protections réseau
Ces paramètres sont à ranger dans /etc/sysctl.d/51-network.conf
.
net.ipv4.tcp_syncookies=1
Ce paramètre protège contre des attaques réseaux de type SYN flood.
net.ipv4.tcp_rfc1337=1
Ce paramètre protège des time-wait assassinations.
net.ipv4.conf.all.rp_filter=1
net.ipv4.conf.default.rp_filter=1
Ces paramètres protègent des IP spoofing en validant la source des paquets qui proviennent de toutes les interfaces.
net.ipv4.conf.all.accept_redirects=0
net.ipv4.conf.default.accept_redirects=0
net.ipv4.conf.all.secure_redirects=0
net.ipv4.conf.default.secure_redirects=0
net.ipv6.conf.all.accept_redirects=0
net.ipv6.conf.default.accept_redirects=0
net.ipv4.conf.all.send_redirects=0
net.ipv4.conf.default.send_redirects=0
Ces paramètres désactivent l'ICMP redirect, qui peut être utilisé pour des attaques man-in-the-middle et dans des fuites d'informations.
net.ipv4.conf.all.accept_source_route=0
net.ipv4.conf.default.accept_source_route=0
net.ipv6.conf.all.accept_source_route=0
net.ipv6.conf.default.accept_source_route=0
Idem que ci-dessus, mais cette fois-ci concernant le source routing.
net.ipv6.conf.all.accept_ra=0
net.ipv6.conf.default.accept_ra=0
Idem : ces paramètres désactivent la prise en charge des router advertisements.
net.ipv4.tcp_sack=0
net.ipv4.tcp_dsack=0
net.ipv4.tcp_fack=0
Ces paramètre désactivent TCP SACK (Selective Acknowledgments) qui peut être exploité, donc devrait être désactivé si non-utilisé.
net.ipv4.icmp_echo_ignore_all=1
Attention : ce paramètre configure le système pour ignorer toutes les requêtes ICMP. Ce n'est logiquement pas idéal pour un serveur.
Protections espace utilisateur
Ces protections sont à ranger dans /etc/sysctl.d/51-userspace.conf
.
kernel.yama.ptrace_scope=2
ptrace()
est un appel système utilisé pour contrôler un autre processus, sa mémoire, etc. Il est pratique pour debug, mais son usage devrait être autrement restreint à la capacité CAP_SYS_PTRACE.
vm.mmap_rnd_bits=32
vm.mmap_rnd_compat_bits=16
Ces paramètres renforcent ASLR en augmentant ses bits d'entropie. Il est toujours suggéré d'utiliser linux-hardened pour aller plus loin.
Les valeurs données sont compatibles avec une architecture 64-bits.
fs.protected_symlinks=1
fs.protected_hardlinks=1
Ces paramètres protègent les symlinks et hardlinks contre les bugs time-of-check to time-of-use.
fs.protected_fifos=2
fs.protected_regular=2
Ces paramètres empêchent la création de fichiers dans des environnements potentiellement contrôlés par un attaquant et ouverts en écriture à tout le monde (data spoofing).
Fin et documentation
Vous pouvez creuser le sujet davantage à ces adresses :
- abs2014_seforandroid_smalley.pdf (linuxfound.org)
- https://www.whonix.org/wiki/Hardened-kernel
- Linux Hardening Guide | Madaidan's Insecurities
- Security - ArchWiki (archlinux.org)
- Recommandations de sécurité relatives à un système GNU/Linux
Je vous réfère également à mes précédents articles qui distillent d'autres informations, je voulais parfois éviter la redite. Celui-ci devait être un petit article sans prétention à la base, mais j'ai encore dérapé sur la longueur à certains endroits, veuillez m'excuser de mon manque de concision.
Stay safe!