This feed contains pages in the "engine" category.
Dans sa version 2.1.0, le serveur FTP sécurisé vsftpd de Chris Evans a intégré un mécanisme de sandboxing, son auteur le décrivant simplement comme :
An ambitious new built-in sandbox. Think of it as privsep++, but more
on this in an upcoming post and paper.
De quoi nous rendre curieux ! Hop, on télécharge les sources de la
2.0.7 et la 2.1.0 et on fait un diff entre les deux répertoires : pas
grand chose, on peut juste observer qu'il y a des appels à
ptrace_sandbox_alloc() et ptrace_sandbox_run() et c'est tout : cela
signifierait donc que la sandbox est complètement transparent pour le
code de l'application, une bonne chose !
Introduction à la sandbox
On constate néanmoins l'ajout de deux fichiers : ptracesandbox.c et
ftppolicy.c. Les choses sérieuses commencent enfin...
D'après le nom de fichier, on peut se douter que la sandbox repose sur
ptrace() (aka "the unique and arcane syscall"). Effectivement, elle
utilise PTRACE_SYSCALL qui permet d'interrompre le processus traçé à
l'entrée dans un appel système (dans la routine syscall_trace_entry
et de passer la main au traceur. vsftpd implémente ainsi toute sa
logique dans la fonction ptrace_sandbox_handle_event() qui vérifie la
provenance du signal puis regarde quel appel système est demandé.
Vérification de l'appel système
Ce check n'est pas aussi simple que prévu, Chris Evans a ainsi trouvé
la vulnérabilité CESA-2009-001 dans le noyau Linux
le mois dernier permettant de contourner des mécanismes de protection
basés sur ptrace() (comme systrace).
L'astuce est que sur les architectures supportant à la fois le mode 32
et 64 bits, les numéros d'appel systèmes ne sont pas les mêmes suivant
le mode dans lequel on est (le syscall numéro 2 est open() en 32 bits,
mais correspond à fork() en 64 bits). Il faut alors prendre des
précautions particulières pour s'assurer que tout le monde parle la
même architecture :
Si on a intercepté un
int80ousysenter, on est sûr d'être en 32 bits.Si c'est par l'instruction
syscall, les dés sont lançés, il faut alors consulter le sélecteur CS afin de vérifier s'il référence une table dont on connait son "type" (32 ou 64 bits).
Une fois le numéro d'appel système (vraiment) connu, la sandbox va
consulter deux tables, toutes deux indexées par numéro d'appel
système. La première indique si le syscall est autorisé ou non, la
deuxième donne le callback associé à l'appel système (par exemple, il
y a un callback pour open() qui permet de vérifier l'utilisation de
O_RDONLY).
Création de processus
Toutes les méthodes de création de processus sont interdites dans la policy. Chaque déviation à la politique est fatale pour le processus qui est tué par un SIGKILL, cette brutalité est ici problématique comme nous allons le voir. En effet, lorsque le traceur est prévenu, il est en mesure de modifier le numéro d'appel système que voulait exécuter le processus afin de le rendre inoffensif. Le noyau exécute alors l'appel système, puis rend la main au processus en délivrant tous les signaux en attente.
Ici, le problème est que lorsque la sandbox voit que c'est un fork() qui veut être exécuté, elle tue le processus (en fait, elle ajoute un signal SIGKILL dans la liste des signaux en attente) mais le noyau continue de dérouler la routine syscall et donc d'exécuter la création du fils.
Néanmoins, lorsque le parent est schedulé, le SIGKILL est délivré et
il est effectivement tué. Mais le fils existe toujours! C'est pour
cela que la sandbox utilise PTRACE_SETOPTIONS avec
TRACE_{VFORK,FORK,CLONE} pour tracer par défaut tous les fils que
pourrait créer un processus et les tuer quand bon lui semble.
C'est là que je suis perplexe puisqu'on a vu qu'on pouvait dire au
noyau "Non en fait, le processus a voulu exécuter le syscall exit(),
pas fork() mais tue le quand même après". Et c'est effectivement ce
que fait la sandbox : avant de tuer le processus par trois moyens
différents, elle ré-écrit les registres afin de pointer sur exit() donc
tout est fait proprement et le SETOPTIONS précédent est ici
superflu. Vu que Chris est sûrement paranoïaque, dans le doute, il a
préfèré mettre ceinture et bretelles.
Protection du processus monitorant
On comprend bien que la sécurité de la sandbox repose uniquement sur
le processus monitorant. Si celui-ci tombe, tous les mécanismes de
sécurité sont compromit, c'est pour cette raison que lors de
l'initialisation d'un nouveau processus, chaque fils fait un
prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0) afin que le noyau tue le
processus si son père (le moniteur) meurt.
Conclusion
Y a pas à dire, Chris Evans a réalisé ici un véritable chef-d'œuvre ! C'est la première fois que je vois une sandbox qui fait vraiment son rôle sans être vulnérable à la première attaque. Le design a été particulièrement travaillé, tout est clean et on sent qu'il y a eu de la réflexion derrière chaque fonction.
Impressionnant ! En plus, tout le moteur n'est contenu que dans un seul fichier C rendant son importation dans d'autres projets très simple.
Progressivement, j'essaie de passer toutes mes pages sur ikiwi, comme son
nom l'indique, c'est en effet un wiki à la différence qui peut fonctionner
uniquement en mode statique. De fait, cela signifie que j'ai un espoir de
désactiver la passoire^W^W mod_php
et donc pouvoir essayer de nouveaux serveurs HTTP comme and-httpd (oui parce que j'ai
enfin fini de télécharger les tarballs ;)) ?
Je me suis décidé à migrer maintenant à cause de l'annonce d'une nouvelle mise à jour 1.2.7 sensée fixer des vulnérabilités. Comme à chaque fois, il faut se taper les backups (oui c'est déjà fait mais bon, j'ai pas envie de sortir la clef de mon oiseau), la décompression de la tarball, déplacer les trucs où qu'il faut, etc. Alors que si un simple patch était disponible, ca se passerait beaucoup mieux, même si j'ai compris que pour cette mise à jour, les modifications sont trop importantes mais ce n'était pas le cas la dernière fois.
Tout le monde parle du Web 2.0, News0ft le premier, eh bien moi j'aime pas ça, j'étais vachement mieux avec mon Web 1.0-rc4 donc je ne vais pas me gonfler à faire un template et une feuille de style.
Mais à mon époque, on avait le sens du devoir et de la ''backward compatibility'' donc il faut assurer le service des anciennes URL, comme le lien d'aggrégation (RSS et Atom), des liens permanents pour les billets, etc.). Ces ''permaliens'' sont générés ainsi :
http://$host/$path/$year/$month/$day/$id-$title
À partir de la base SQL et d'un coup de Python/Perl, on génère les Redirect adéquates, par exemple :
Redirect /blog/2007/03/02/299-fff http://chdir.org/~nico/blog/posts/fff
En faisant :
python export-dotclear-url.py |perl \
-pe 's"^(\d+) ([^ ]+) (\d+)-(\d+)-(\d+).*"Redirect /blog/$3/$4/$5/$1-$2 $blogurl/posts/$2"' \
>> .htaccess
(export-dotclear-url.py est juste un dérivé du script d'export de
billets, il ne fait qu'afficher le post_id, post_titre_url et la date
de création du billet).
Le script d'export des billets ressemble à ça :
#! /usr/bin/python
import MySQLdb
base='/tmp/user/666/posts/'
db = MySQLdb.connect("localhost", "usertetedenoeud", "sacaf", "tableblog", use_unicode=False, charset='latin1')
c=db.cursor()
c.execute('SELECT post_titre_url,post_titre_url, post_content, post_creadt from dc_post;')
while 1:
l=c.fetchone()
if not l:
break
titre,url,content,createdate=l
print titre, url, createdate
f=open(base+url+'.mdwn', "w+")
f.write('[ [ meta title="%s"]]\n'% titre)
f.write('[ [meta date="%s"]] \n\n\n'% createdate)
f.write(content)
f.close()
Normalement, grâce à cette astuce, la migration n'a pas dû flooder votre aggrégateur de news. J'en ai également profité pour tagger tous les billets importés par la marque imported.