Si vous connaissez déjà le comportement PCI et la gestion par Linux des tampons DMA, passez à la troisième section pour ma question. Sinon, lisez la suite pour un petit résumé de la façon dont les périphériques PCI effectuent les accès à la mémoire, et comment le noyau gère la communication avec les périphériques utilisant DMA. Je l'ai inclus ici à la fois dans l'espoir de fournir aux gens posant la même question des informations utiles, et de donner aux autres la possibilité de me corriger au cas où ma compréhension ne fonctionnerait pas.
Les périphériques PCI et PCIe ont dans leur espace de configuration un octet registre de commandes qui contient un masque de bits pour activer ou désactiver plusieurs fonctionnalités matérielles différentes. Le bit 2 est le bus master enable bit qui, lorsqu'il est défini, permet au périphérique d'initier DMA requêtes. Ce bit, et tout autre bit du registre de commandes , est défini par un logiciel exécuté en mode superviseur (généralement par des pilotes du noyau) et, bien qu'il soit physiquement stocké sur le périphérique PCI, il ne peut pas être modifié par celui-ci (peut-être que le PCH conserve un cliché instantané de l'espace de configuration PCI de chaque périphérique?). sans IOMMU, le périphérique peut demander des lectures et des écritures à n'importe quelle adresse mémoire légale. Ceci est souvent appelé une attaque DMA ou une mauvaise maîtrise du bus, et est un problème sur tout système non protégé avec des périphériques PCI malveillants . L'IOMMU est censé être la solution pour améliorer à la fois la sécurité et les performances. Pour référence, je pose spécifiquement des questions sur la mise en œuvre d'Intel, VT-d (précisément le plus moderne VT-d2).
La plupart des systèmes peuvent être configurés pour Remappage DMA, ou DMAR. Les tables ACPI incluses dans le BIOS ont souvent la table DMAR, qui contient une liste d'adresses vers laquelle divers groupes PCI verront tous leurs accès mémoire routés. Tout cela est décrit dans la section 2.5.1.1 des spécifications VT-d . Un graphique du document:
Les tables DMAR sont codées en dur par le BIOS. Un périphérique PCI donné (ou plutôt un donné groupe IOMM ) est autorisé à accéder uniquement à une plage de mémoire prédéterminée. Le noyau est informé de l'emplacement de cette mémoire et est invité à ne pas y allouer de mémoire qu'il ne souhaite pas lire/écrire sur DMA. Les valeurs de remappage sont signalées dans la mémoire tampon du journal du noyau:
DMAR: définition de la carte d'identité pour le périphérique 0000: 00: 02.0 [0xad000000 - 0xaf1fffff] DMAR: définition de la carte d'identité pour le périphérique 0000: 00: 14.0 [0xa95dc000 - 0xa95e8fff] DMAR : Définition de la carte d'identité pour le périphérique 0000: 00: 1a.0 [0xa95dc000 - 0xa95e8fff] DMAR: définition de la carte d'identité pour le périphérique 0000: 00: 1d.0 [0xa95dc000 - 0xa95e8fff] DMAR: préparer le mappage d'unité 0-16 Mo pour le LPC DMAR: définition de la carte d'identité pour le périphérique 0000: 00: 1f.0 [0x0 - 0xffffff] DMAR: Intel (R) Technologie de virtualisation pour les E/S dirigées Iommu: Ajout de périphérique 0000: 00: 00.0 au groupe 0 Iommu: Ajout de périphérique 0000: 00: 01.0 au groupe 1 Iommu: Ajout de périphérique 0000: 00: 02.0 au groupe 2 Iommu: Ajout d'un appareil 0000: 00: 14.0 au groupe 3 Iommu: Ajout d'un appareil 0000: 00: 16.0 au groupe 4 Iommu: Ajout périphérique 0000: 00: 1a.0 au groupe 5 iommu: ajout du périphérique 0000: 00: 1b.0 au groupe 6 iommu: ajout du périphérique 0000: 00: 1c.0 au groupe 7 iommu: Ajout d'un appareil 0000: 00: 1c.2 au groupe 8 iommu: Ajout d'un appareil 0000: 00: 1c.3 au groupe 9 iommu: Ajout d'un appareil 0000: 00: 1c.4 au groupe 10 iommu: Ajout d'un périphérique 0000: 00: 1d.0 au groupe 11 iommu: Ajout de périphérique 0000: 00: 1f.0 au groupe 12 iommu: Ajout de périphérique 0000: 00: 1f.2 au groupe 12 iommu: Ajout de périphérique 0000: 00 : 1f.3 au groupe 12 Iommu: Ajout d'un appareil 0000: 01: 00.0 au groupe 1 Iommu: Ajout d'un appareil 0000: 03: 00.0 au groupe 13 Iommu: Ajout d'un appareil 0000: 04: 00.0 au groupe 14 Iommu: Ajout d'un appareil 0000: 05: 00.0 au groupe 15
À partir des lignes en gras, nous voyons que le groupe 11 contient (uniquement) le périphérique 0000:00:1d.0
, Qui peut accéder librement à 13 pages de mémoire dans la plage de 0xa95dc000 - 0xa95e8fff
. Tous les accès pour les périphériques du groupe 11 ne pourront y écrire, ce qui les empêchera de modifier le contenu des autres tampons DMA ou code OS non lié. De cette façon, même si le périphérique a son bus ensemble de bits maître, il n'a pas besoin de garder une trace de l'endroit où il écrit et il ne peut pas (accidentellement ou par malveillance) écrire où il n'est pas censé le faire.
Lorsqu'un pilote de noyau souhaite interagir avec un périphérique via DMA, il alloue de la mémoire spécifiquement à cet effet en utilisant, par exemple, void *addr = kmalloc(len, GFP_KERNEL | GFP_DMA)
. Cela renverra, dans addr
, une adresse de mémoire virtuelle pointant vers une section contiguë de mémoire len
octets de taille appropriée pour DMA utilisation. Ceci est tous décrits plus en détail dans la documentation de l'API DMA . Le pilote est alors libre de communiquer avec le périphérique PCI via cette région de mémoire partagée. La série d'événements, simplifiée, peut ressembler à ceci:
Le noyau fait-il implicitement confiance à ces DMA? Un périphérique PCI malveillant ou compromis, n'écrivant nulle part ailleurs que les tampons désignés (l'IOMMU l'empêche de faire autrement), peut-il compromettre le noyau en exploitant les données structures qu'ils partagent? La réponse évidente est peut-être, car tout partage et analyse de structures de données complexes utilisant des langages non sécurisés en mémoire comporte un risque d'exploitation. Mais les développeurs du noyau peuvent supposer que ces tampons sont fiables et ne font aucun effort pour sécuriser le noyau contre toute activité malveillante (contrairement, disons, aux données partagées entre l'espace utilisateur non privilégié et le noyau via copy_from_user()
et des fonctions similaires ). Je commence à penser que la réponse à la question de savoir si un périphérique PCI malveillant peut compromettre l'hôte malgré les restrictions de l'IOMMU est probablement.
L'exploitation d'une telle vulnérabilité fonctionnerait comme ceci, où buf
est dans l'espace d'adressage contrôlé par le périphérique et accessible en écriture DMA, et dest
est ailleurs dans la mémoire du noyau:
struct { size_t len; char data[32]; char foo[32]; } buf
.data
dans struct { char data[32]; bool summon_demons; } dest
.buf.len = sizeof(buf.data) + 1
et buf.foo[0] = 1
.memcpy(dest.data, buf.data, buf.len)
.Évidemment, ceci est un exemple artificiel et même si un bug aussi évident ne se produirait probablement pas dans le noyau en premier lieu, il illustre mon point de vue et m'amène à ma question principale:
Existe-t-il des exemples de vulnérabilités dues à une mauvaise manipulation des structures de données partagées sur DMA, ou de pilotes spécifiques traitant l'entrée des périphériques PCI comme fiable?
Je suis conscient de ses limites et je ne veux pas de réponse qui tente d'expliquer comment un périphérique pourrait contourner directement l'IOMMU ou utiliser une autre faille pour prendre le contrôle du système. Je connais:
* DMA signifie accès direct à la mémoire. Il s'agit d'une fonctionnalité matérielle grâce à laquelle certaines interfaces matérielles (comme PCIe) peuvent demander un accès direct à la mémoire système, sans passer par le CPU.
Récemment, j'avais lu les articles publiés au NDSSS 2019, et cet article présenté en février, je pense, répond complètement à la question. Au moment où la question a été posée, il semble que la réponse à l'existence de vulnérabilités était oui mais elles ont été corrigées dans le noyau Linux à partir de 5. . Les diapositives appartenant à l'article présenté lors du Network and Distributed System Security Symposium 2019 peuvent être trouvées ici .
Les questions sont toujours d'actualité car les interfaces Thunderbolt disposent également de DMA. Les notes de mise à jour sous Linux peuvent être trouvées ici .