Fonctionnement

Ce canal caché est une preuve de concept pour communiquer entre invités (indépendamment du domaine : dom0 ou domU). Pour cela, on doit avoir les droits qui permettent d'insérer un module noyau, autrement dit de modifier le noyau, ce qui servira à faire des appels aux hypercalls.

On utilise la table de mapping entre les adresses physiques et les adresses pseudo-physique de l'hyperviseur (MFN->PFN : Machine Frame Number to Pseudo-physical Frame Number). Cette table permet d'avoir de très bonnes performances quand l'OS invité à besoin de zones contigües de mémoire. On peut ainsi avoir une mémoire physique moins fragmentée. Il utilise alors un hypercall (mmu_update) pour établir cette relation. On peut noter que cette table est lisible par tous les invités étant donné qu'il n'y en a qu'une seule. Évidement, seul le détenteur d'une plage d'adresse (fixée au démarrage et changeable avec l'utilisation du baloon driver) peut la modifier. On parle ici seulement d'adresses, mais en aucun cas des données vers lesquelles elles pointent. En effet, si on veut pouvoir lire à un emplacement, il faut pouvoir le mapper en mémoire. Ici on ne peut mapper que nos propres données. Cette table de relation contient normalement des adresses, mais étant donné qu'il n'y a aucune vérification, on peut écrire ce que l'on souhaite.

Le principe du canal caché est simple : il faut écrire un tag pour (notamment) pouvoir trouver l'emplacement, et ensuite inscrire les données que l'on veut passer à un autre invité. Une fois ces informations écrites, n'importe quel invité qui connais le tag peut retrouver et extraire les informations. On obtient ainsi un canal half-duplex dès que deux invités connaissent mutuellement leurs tags. Ce nouveau canal de communication fonctionne actuellement pour Linux 2.6 x86_32.

Cette preuve de concept n'est pas une idée nouvelle puisqu'elle à été relevée en 2006 sur la mailing-list de xen-devel. Au final, on outrepasse la politique de sécurité de Xen comme le disaient les développeurs.

La table machine-to-physical est une bonne idée pour augmenter les performances de mapping de mémoire, mais il serait judicieux d'avoir une table pour chaque invité. Elles auraient alors la même protection que la mémoire ordinaire et seraient inaccessible à un autre invité. À la vue de la table, je ne pense pas qu'il y ai de gros problèmes de performance par rapport à la vitesse de changement de table (effectué par Xen) et l'espace mémoire que prend une table. Une autre solution consiste à utiliser les shadow page tables, mais avec en contrepartie une baisse de performance si la translation des adresses est logiciel. Si cette remarque est appliquée, mon bout de code sera alors inutilisable. Cette méthode pour « isoler » les invités pourrais également servir à les empêcher de lire la table et d'en déduire (avec les interruptions Xen) une cartographie basique de la mémoire physiquement utilisée.

Mise en place

Le code du PoC est un pilote de périphérique qui crée /dev/xencc. Le protocole de communication est basé sur des tags qui contiennent un identifiant et la taille du message. On a besoin d'un tag différent pour chaque OS (voir les sources et le changer pour le second invité). Pour l'instant, on peut seulement communiquer entre 2 invités.

Si le système n'a pas udev d'installé le périphérique ne se créera pas automatiquement et il faudra donc le créer à la main[1] après l'insertion du module[2]. Une fois celui-ci chargé, on peut alors écrire[3] et lire[4]. Les plus aventureux pourront se risquer à augmenter la taille du tampon dans les sources. Le débit du canal de communication semble être bon étant donné qu'on utilise un hypercall (mmu_update) qui copie une plage complète de données (normalement des adresses) en un seul appel. L'inconvénient de cette méthode est la forte consommation de mémoire par rapport à la taille du message. En effet, on alloue une plage mémoire où on n'utilise que les « adresses » et non les données ainsi réservées. Il faut alors transférer les données petit à petit. Avec un bon tempo de synchronisation, on obtient un canal très rapide.

Exemple

Voilà les sorties générées lors de l'écriture de l'invité 1 et ensuite la lecture de l'invité 2 (avec un noyau 2.6.18-6-xen-686) :

invité 1 :

dom1:~# echo msg dom1 > /dev/xencc
dom1:~# dmesg -c
xencc loaded guest 1
xencc open
xencc write to guest 2
xencc clean
xencc order: 4
xencc alloc_pages: c1357400
xencc page_to_pfn: 000076a0
xencc pfn_to_mfn: 0000964c
xencc allocate_mfn : 0000964c
xencc pfn0: 00000000 -> 00025063
xencc pfn1: 00000000 -> 00003d19
xencc pfn2: 00000000 -> 00000588
xencc pfn3: 00000000 -> 00072a3c
xencc pfn4: 00000000 -> 00000009
xencc pfn5: 00000000 -> 2067736d
xencc pfn6: 00000000 -> 316d6f64
xencc pfn7: 00000000 -> 0000000a
xencc nb_success: 8
xencc write
xencc release

invité 2 :

dom2:~# dd if=/dev/xencc count=1
msg dom1
0+1 records in
0+1 records out
9 bytes (9 B) copied, 0.000185 s, 48.6 kB/s
dom2:~# dmesg -c
xencc loaded guest 2
xencc open
xencc read from guest 1
xencc mfn 0000964c : 00025063 (0)
xencc mfn 0000964d : 00003d19 (1)
xencc mfn 0000964e : 00000588 (2)
xencc mfn 0000964f : 00072a3c (3)
xencc tag trouvé !
xencc release

La version actuelle est instable, à utiliser avec prudence !

Fichiers source.

Post dans la mailing-list de développement de Xen.

Notes

[1] mknod /dev/xencc c `grep misc /proc/devices | awk '{print $1}'` `grep xencc /proc/misc | awk '{print $1}'`

[2] insmod xencc.ko

[3] echo your msg > /dev/xencc

[4] dd if=/dev/xencc count=1