This feed contains pages in the "tech" category.

L'année dernière, le noyau Linux a vu un nombre impressionnant de patches avec le mot "namespace" dedans, tout s'est vu "namespacisé" : de l'espace des PID, aux points de montages en passant par les politiques IPSec.

Dans ce post, on va se focaliser sur la partie réseau et son utilisation dans le cadre d'un processus qu'on voudrait restreindre (au sens d'une chroot améliorée). On utilisera indifférement les termes machines virtuelles (VM), container, jail, etc.

Chacun cherche son chat

La partie réseau a été particulièrement polémique chez les développeurs du noyau car il y a de nombreuses façons de réaliser cet espace de nommage : il y a autant de façons que de couches protocolaires comme le détaille l'article de J. Corbet.

Il y a les partisans d'une approche légère où la virtualisation est simplement faite au niveau des sockets et d'autre qui voudraient pouvoir complétement virtualiser la pile TCP/IP afin d'avoir des interfaces, adresses IP, routes et politiques IPSec complétement distinctes entre les machines virtuelles.

La solution qui a été finalement choisie est celle virtualisant la couche 2, cela permet ainsi d'avoir des interfaces complétement indépendantes entre les VM (et donc d'adresses IP, routages, etc.) et l'hôte.

Cette fonctionnalité est disponible dans le noyau Linux depuis le 2.6.27.

La création d'un namespace

Au niveau du noyau, il n'y a qu'un seul moyen de déclarer un nouvel espace de nommage, c'est via l'appel système unshare() (ou via clone() de manière indirecte). Cette fonction a pour effet de désassocier un namespace de son contexte d'exécution.

Son seul argument est le composant que l'on veut désassocier (réseau, système de fichier, descripteurs de fichiers). Cela signifie donc qu'un processus désirant se créér un nouvel espace de nommage est obligé d'être modifié afin d'appeller cet appel système. Un workaround est l'utilisation d'un programme qui va faire cet appel à unshare() puis faire un execve() du programme cible.

Néanmoins, cela implique alors la distribution de deux fichiers (un programme qui exécute l'autre) car il n'existe pas de logiciel standards pour cette tâche, on pourra toujours utiliser netunshare extrait de la suite lxc.

Gestion des interfaces

Lorsqu'on créé un nouvel espace de nommage, le processus se retrouve avec une seule interface, un nouveau loopback uniquement. Pour enrichir sa collection d'interface, il est donc nécessaire de soit créér une nouvelle interface virtuelle, soit transférer une interface déjà existante.

Création d'interface

La création d'une interface virtuelle unique n'a de sens que si elle est connectée à quelque chose (de la même façon qu'un tunnel, il faut qu'il y ait un bout dans la VM, l'autre vers l'extérieur), il faut donc choisir autre chose qu'une interface du style 'dummy' qui peut être considéré comme un puit sans fond.

Une solution de tunnel existe depuis longtemps sous Linux, les tunnels TUN/TAP, or ils ont pour but de fournir un tunnel entre un descripteur de fichier et une interface réseau. Il faudrait donc créér un programme qui ouvrirait deux tunnels et qui passerait son temps à passer les paquets d'un côté à l'autre.

C'est pour cette raison qu'a été implémenté les interfaces de type Virtual Ethernet (veth) qui crééent une paire d'interfaces liées entre elles : ce qui rentre par l'une sort par l'autre et vice versa.

# ip link
4: lo: <LOOPBACK> mtu 16436 qdisc noop state DOWN 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
# ip link add type veth
# ip link
4: lo: <LOOPBACK> mtu 16436 qdisc noop state DOWN 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
9: veth0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000
    link/ether fe:d8:a2:71:23:ae brd ff:ff:ff:ff:ff:ff
10: veth1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000
    link/ether 3e:3e:90:3b:3f:c0 brd ff:ff:ff:ff:ff:ff

Si on créé une telle paire d'interface, on reste toujours avec le même problème : on a bien deux interfaces, mais elles restent confinées dans le container, il est donc nécessaire d'avoir un moyen de transférer une interface entre des espaces de nommage, ça tombe bien, c'est supporté :)

Transfert d'interfaces

Lorsqu'on transfère une interface, plutôt que de désigner explicitement un namespace, on précise à la place un PID, l'interface sera alors attachée au namespace de ce PID.

ip link set veth1 netns PID_DESTINATION

Lorsqu'un espace de nommage est détruit, toutes ses interfaces (sauf loopback) sont transférées automatiquement au namespace parent.

Possibilité

À l'intérieur d'un namespace, il est possible de faire tout ce que vous pouvez attendre d'un système normal : changement des routes, des adresses IP, etc.

L'intérêt de ce genre de cloisonnement est qu'une jail n'a qu'un seul point d'entrée et de sortie : l'interface réseau qui a été déportée par l'hôte.

C'est la grande différence avec vserver qui ne permet pas d'identifier clairement les flux entre VM en écrivant vos règles en terme d'interfaces (vous vous retrouvez obligé de jouer sur les adresses sources).

Ce qui n'est pas encore supporté

Pour le moment, Netfilter n'a pas encore le support des namespaces. Cela signifie qu'un container ne peut pas avoir ses propres règles de firewall, mais le problème principal est que Netfilter ne peut voir passer que les paquets sur l'espace de nommage initial. Cela signifie que si vous mettez en place un tunnel entre deux espaces de nommages, Netfilter ne pourra pas pas voir passer les paquets entre eux.

C'est pour cette raison qu'il est préférable de ne pas dédier une VM à la tâche de routeur : tout doit passer à travers l'hôte qui peut alors appliquer une politique de filtrage assurant le cloisonnement. Il faut alors travailler dans la cible FORWARD qui décidera de la politique à adopter.

Posted Tue 28 Apr 2009 05:38:45 PM CEST Tags: tech

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 int80 ou sysenter, 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.

Posted Thu 19 Feb 2009 09:29:56 PM CET Tags: tech

Oh thanks God, c'est jour de fête : le nombre d'arguments dans la ligne de commande est enfin dynamique, il n'y a plus de limitation à 4096 paramètres ! Dites adieu à votre find /foo/bar/ -print0 | xargs -0 que vous utilisiez pour contourner cette limitation stupide ou autres boucle shell.

Posted Thu 23 Aug 2007 08:05:24 PM CEST Tags: tech

De temps à autre, j'en ai ras le bol[1] de la police que j'utilise quotidiennement, ca me prends comme ça, faut dire qu'utiliser la même police pour 70% de son desktop depuis quatre ans, ca peut lasser.

Alors je me mets à essayer toutes les polices disponibles sur mon système et plus encore, mes critères sont pourtant basiques :

  • Lisible en petit caractère (sur du 1600x1200) ;
  • Monospace bien sûr ;
  • Avec tous les caractères iso-8859-1/-9 (je me mets progressivement à UTF-8 mais doucement, en fait surtout parce qu'on a plus trop le choix avec une nouvelle installation Debian, arrrrg
  • Libre ;
  • Tous les caractères doivent être lisibles ! Je veux que mon dollar ressemble au truc des billets américain, mon "Et commercial" au logo de France Telecom, etc.

Donc j'ai essayé quelques polices "externes" les plus réputées telles ProFont, JMK, etc. Et aucune n'a vérifié tous mes critères, en majeure partie car la ponctuation était horrible (regardez le pourcentage par exemple) et qu'il manquait pas mal de caractère international.

Tout ça pour rentrer bredouille, je suis finalement tellement bien avec mes polices par défaut : 6x12 pour programmer, fixed pour tous mes xterms^Wurxvtc.

[1] Comment ça s'écrit ? Même Google Fight n'est pas capable de départager franchement.

Posted Mon 30 Jul 2007 08:16:33 PM CEST Tags: tech

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.

Posted Fri 27 Jul 2007 09:51:15 PM CEST Tags: tech