Le système de fichiers /dev sous Linux offre un moyen élégant d'accéder directement aux périphériques d'une machine, et notamment à sa mémoire physique par l'intermédiaire de /dev/mem. L'objectif de ce billet est de présenter le fonctionnement de /dev/mem sur un Linux 2.6.26-2 tournant sur une architecture x86 32 bits, et de voir quelles sont les différentes restrictions d'accès.

/dev/mem offre un accès direct à la mémoire physique du système (mémoire RAM, mémoire graphique, BIOS, etc.). L'analyse de ce fichier peut être facilitée par la lecture d'un autre fichier spécial, /proc/iomem, qui présente le mapping des périphériques au sein de la mémoire physique. Le mapping sur le système étudié est le suivant :

00000000-0009fbff : System RAM
0009fc00-0009ffff : reserved
000a0000-000bffff : Video RAM area
000c0000-000cffff : Video ROM
000e0000-000e17ff : Adapter ROM
000f0000-000fffff : System ROM
00100000-3ffeffff : System RAM
00100000-002ba4ea : Kernel code
002ba4eb-0037661f : Kernel data
003bc000-0041f57f : Kernel bss
...

Autrement dit, le BIOS est accessible dans l'espace d'adressage physique 0x000f0000-0x000fffff, la mémoire graphique est mappée dans 0x000a0000-0x000cffff, et la mémoire RAM, mappée à partir de 16 Mo (c'est à dire dans la ZONE_NORMAL et dans la ZONE_HIGHMEM : voir cet ancien article), est atteignable entre 0x00100000 et 0x3ffeffff. Ce dernier espace fait 1 Go. Ca tombe bien, c'est précisément la taille de la mémoire RAM de la machine :

$ cat /proc/meminfo 
MemTotal:      1036084 kB
...

/proc/iomem nous informe également de l'emplacement du code du noyau et de ses segments de données .data et .bss. Les éléments du noyau (code et données) sont donc directement accessibles en lecture et en écriture par /dev/mem. Biensur, n'importe qui ne peut pas lire ce périphérique : il faut soit être root, soit donner la bonne capability à un processus non-root. Ce dernier cas de figure est typiquement adapté pour le serveur graphique X, qui dépend de /dev/mem pour récupérer des informations liées à la carte graphique, et dont son exécution avec les privilèges root n'est pas nécessaire (hormis pour l'accès à dev/mem). Il suffit de lui donner la capability CAP_SYS_RAWIO, qui autorise, notamment, l'accès à /dev/mem. Ensuite, il faut placer l'utilisateur exécutant le serveur X dans le groupe kmem. L'exemple suivant illustre la mise en place d'une capability propre à l'exécutable dd, lui permettant de lire dans /dev/mem sans la nécessité d'être exécuté en root.

$ cp /bin/dd /tmp/dd
$ sudo setcap cap_sys_rawio=ep /tmp/dd
$ cat /etc/group
...
kmem:x:15:sygus
...
$ /tmp/dd if=/dev/mem of=/tmp/mem.dump 
1835008+0 enregistrements lus
1835008+0 enregistrements écrits
939524096 bytes (940 MB) copied, 17,7379 s, 53,0 MB/s

L'accès à l'intégralité de la mémoire n'est néanmoins pas pertinent d'un point de vue sécurité : la compromission d'un processus root ou d'un processus ayant la capability CAP_SYS_RAWIO peut permettre à un attaquant d'accéder directement à tout le noyau. Il est heureusement possible de configurer Linux pour limiter l'accès à /dev/mem uniquement au premier Mo de mémoire physique et seulement aux pages mémoire ne correspondant pas à de la RAM (ce qui est suffisant pour faire tourner X). C'est d'ailleurs le comportement par défaut sur Ubuntu, mais pas sur Debian Lenny. La restriction d'accès au premier Mo s'active avec l'option CONFIG_NONPROMISC_DEVMEM introduite sur le 2.6.26 (ou CONFIG_STRICT_DEVMEM sur les versions de Linux depuis le 2.6.27) lors de la compilation du noyau. Voici le résultat d'une tentative de lecture de l'ensemble de /dev/mem :

$ sudo dd if=/dev/mem of=/tmp/mem2.dump 
dd: reading `/dev/mem': Operation not permitted
2056+0 records in
2056+0 records out
1052672 bytes (1.1 MB) copied, 0.149356 seconds, 7.0 MB/s

Hop, bloqué au premier Mo... Ou plus exactement au bout de 256+1 pages mémoires ; la dernière page mémoire étant considérée comme appartenant au BIOS par certains systèmes.

Il existe d'autres mécanismes restreignant l'accès à /dev/mem. Citons le patch GrSecurity qui intègre une protection de l'accès à /dev/mem : il empêche l'accès en écriture sur l'ensemble de la mémoire physique. GrSecurity réalise cependant une exception pour le processus correspondant au serveur X.

Bref, bien que /dev/mem ait longtemps été considéré comme une trou de sécurité important, il est possible de limiter de manière importante ses droits d'accès à la mémoire système (option CONFIG_STRICT_DEVMEM du noyau), ou de limiter l'accès en écriture (patch GrSecurity).