Contexte et principe des attaques ROME
Les Rank‑One Model Editing (ROME) sont des attaques introduites en 2022 qui modifient une matrice de poids intermédiaire d’un modèle de type transformeur. La modification se limite à un rang unique, ce qui suffit à altérer la réponse du modèle sur un sujet ciblé sans toucher le fichier de checkpoint. L’article montre qu’une même requête peut produire deux réponses diamétralement différentes selon que le poids a été édité en mémoire GPU ou non, alors que le disque reste inchangé.
Ce type d’attaque ne génère aucune activité de fichiers, de sockets ou de processus supplémentaires. Les outils de sécurité traditionnels – EDR, ADR ou antivirus – qui surveillent les ouvertures de fichiers, les forks ou les flux réseau ne voient donc rien. Le problème s’inscrit dans la tendance actuelle où la majorité des charges de travail GPU sont orchestrées depuis un interpréteur Python.
Mécanisme d’injection et modification en mémoire GPU
Le vecteur d’injection repose sur un module Python malveillant importé dans le même processus python3 qui exécute le modèle. Le module s’appuie sur une variable d’environnement ROME pour déclencher l’édition dès que le modèle passe en mode évaluation. L’édition s’effectue via une série de lancements de kernels CUDA qui calculent le nouveau vecteur de poids puis l’écrivent directement dans la mémoire du GPU. Aucun appel mmap, aucune instruction ioctl et aucune écriture sur disque ne sont impliqués.
Le processus reste donc identique du point de vue du système d’exploitation : un seul PID, un seul contexte CUDA, et les mêmes bibliothèques PyTorch. La différence réside uniquement dans le flux de données GPU, où quelques milliers de kernels supplémentaires écrasent les valeurs ciblées. Cette subtilité rend l’attaque invisible aux solutions qui ne corrèlent pas les appels GPU avec le code source Python.
CudaKernelLaunches {
pid: 810177,
gpu_index: 0,
entries: [
// `SpaceLM.reply` entry is the user's question
CudaKernelLaunchCount {
kernel_name: "void at::native::vectorized_elementwise_kernel<4, at::native::CUDAFunctor_add, ...>(...)",
count: 3, delta: 3,
caller_stack: [
{ func: "SpaceLM.forward", file: "rome/model.py" },
{ func: "Module._call_impl", file: "site-packages/torch/nn/modules/module.py" },
{ func: "Module._wrapped_call_impl", file: "site-packages/torch/nn/modules/module.py" },
{ func: "SpaceLM.reply", file: "rome/model.py" },
],
},
// The `_keys` entry…
]
}
Détection via le capteur runtime de Stealthium
Stealthium propose un capteur qui intercepte chaque appel CudaLibraryLoad et CudaKernelLaunch, puis enrichit l’événement du caller stack Python qui l’a déclenché. Ainsi, les kernels légitimes d’inférence et les kernels d’édition ROME apparaissent avec des traces d’appels différentes. Le capteur agrège les hyper‑prints et signale lorsqu’un même kernel est invoqué depuis un fichier de modèle suspect (par ex. rome/model.py) alors que le contexte d’inférence provient d’une fonction de réponse utilisateur.
Cette corrélation permet de repérer une séquence de kernels qui écrivent des valeurs dans des tenseurs de poids, même si aucune activité réseau ou disque n’est détectée. En pratique, la solution a permis de reproduire l’attaque dans un laboratoire et d’émettre une alerte dès que le nombre de kernels d’édition dépassait un seuil de fréquence anormale.
Limites et perspectives
La méthode repose sur la capacité à instrumenter le runtime CUDA et à récupérer le stack Python. Sur des environnements où le code natif est obfusqué ou où les appels CUDA sont masqués derrière des bibliothèques propriétaires, la visibilité peut être réduite. De plus, l’approche ne détecte pas les modifications de poids réalisées hors du processus Python, par exemple via un accès direct au driver GPU depuis un autre conteneur.
Les recherches en cours visent à combiner l’analyse des gradients en temps réel avec la traçabilité des kernels afin d’identifier des altérations subtiles qui ne génèrent pas de pics de fréquence. Cette direction pourrait renforcer la défense contre des variantes de ROME qui répartissent les écritures sur plusieurs kernels afin d’échapper aux seuils heuristiques.