Xen « reason 49 » : une vue altp2m, une capture d'écran, et une option que DRAKVUF livrait déjà
Catégorie : DRAKVUF
Résumé
L'issue drakvuf#1860 décrit deux choses : après un Ctrl+C et une relance de DRAKVUF, soit tout le système gèle, soit Xen tue la VM avec Unexpected vmexit: reason 49. En deux semaines j'ai séparé trois problèmes qui se ressemblaient. Le gel au démarrage était LibVMI, pas Xen (corrigé dans libvmi#1123). Le crash a un reproducteur de 40 lignes, sans DRAKVUF, et trois ingrédients : une vue altp2m active, QEMU qui commence à suivre l'affichage pendant cette vue, et un invité qui redessine son écran. Il se produit sur Xen 4.21.0, 4.21.2 et 4.22.0, et mon patch Xen candidat a tué l'hôte. Puis Tamas a répondu une ligne : son hôte démarre avec ept=ad=0. Le mien non, alors que DRAKVUF fournit ce fichier GRUB dans son paquet. Avec l'option : 59 runs, 0 crash, 0 gel. Le billet raconte aussi mes fausses pistes, dont une bissection qui accusait LibVMI à tort.
Suivi upstream : drakvuf#1860 (ouverte), libvmi#1123 (fusionnée le 27/09). Rien envoyé à xen-devel.
Environnement
Xeon E5-2683 v4, 32 Go, Debian 13. Xen 4.21.0 du paquet Debian, puis 4.21.2 compilé en debug=y et 4.22.0, avec les mêmes binaires DRAKVUF et LibVMI reconstruits à chaque fois. Invités : Windows 11 24H2, 25H2 et 26H1, Windows 10 22H2, Debian 12. Même snapshot 25H2 pour la matrice de versions. Une VM à la fois.
1. Le symptôme, tel que décrit par Tamas
Ctrl+C, relance de DRAKVUF, et soit le système entier ne répond plus, soit (XEN) Unexpected vmexit: reason 49 suivi de domain_crash called from arch/x86/hvm/vmx/vmx.c:4849 (ligne 4869 sur 4.22.0). Reason 49, c'est EXIT_REASON_EPT_MISCONFIG : une sortie VM que ept_handle_misconfig() n'arrive pas à résoudre. Le 24 septembre je ne reproduisais rien, et j'ai demandé les plugins, l'invité, le CPU et le xl dmesg d'origine.
2. Le gel au démarrage n'était pas Xen
Première série, les 26 et 27 septembre : cycles Ctrl+C et relance. Un démarrage de DRAKVUF sur cinq se figeait, tué à 120 s. Pile gdb sur un build -O0 : get_kpgd_method2, windows_find_eprocess, find_process_by_name("System"). LibVMI scannait toute la RAM physique, domaine en pause (xl list affiche --p---), SIGINT ignoré parce que le processus est en état R. Cause : si le vCPU 0 exécute du code utilisateur à l'instant de l'attache, son CR3 ne mappe pas le noyau (KVA shadowing). Reproduit à la demande avec start /affinity 1 d'une boucle en mode utilisateur. Le détail et le correctif sont dans le billet LibVMI, section 5.
Chiffres propres à cette issue : avec le sous-module LibVMI à jour, 5 attaches sur 5 en 0,5 à 0,7 s ; en régime permanent avec la boucle sur le vCPU 0, 30 attaches sur 30. Les gels n'apparaissaient qu'avec un invité démarré automatiquement au boot de l'hôte (2 boots sur 3, puis 2 sur 5 avec un gel) ; sur un invité recréé à la main, 101 attaches sans gel. Limite : sur un invité à 2 vCPU (Win10 1809), 1 attache sur 40 a encore scanné pendant 212 s, aucun vCPU n'étant en mode noyau.
Tamas a fusionné #1123 le 27 septembre et suggéré de mettre à jour le sous-module et de passer à Xen 4.22. C'est la campagne suivante.
3. Le crash : trois ingrédients, chacun isolé
En testant le bump du sous-module, le crash est devenu déterministe. Preuves du 2 octobre entre 05:42 et 06:03 UTC, compteur de crashs de l'hôte passé de 31 à 37, chaque crash rattaché à son domid :
| Run | Scénario | Résultat |
|---|---|---|
| E1 | procmon seul |
propre |
| E2 | procmon + un screendump QMP toutes les 2 s |
crash |
| E3 | procmon + un viewer VNC qui se connecte en cours de run |
crash |
| E4 | hidsim, Ctrl+C, relance hidsim |
crash |
| E5 | tous les plugins sauf hidsim, deux fois |
propre |
| E6 | altp2m-min.c + screendumps |
crash |
| E7 | altp2m-min.c seul |
propre |
| E8 | E2 sur Windows 11 24H2 | crash |
| E9 | E2 sur Debian 12 | crash |
altp2m-min.c fait 40 lignes et ne contient pas de DRAKVUF : xc_altp2m_set_domain_state, création d'une vue, bascule dessus, sleep. Donc : (1) une vue altp2m active ; (2) QEMU qui commence à suivre l'affichage pendant la vue (screendump, ou viewer VNC qui se connecte ; un viewer déjà connecté avant ne déclenche rien) ; (3) l'invité qui écrit dans son framebuffer.
Le troisième ingrédient se démontre avec Windows 10 22H2 sur l'écran de connexion, qui ne bouge pas : il survit 5 fois (viridian à 0, à 1, et ['base']), et crashe 2 fois sur 2 dès qu'on injecte un mouse_move par QMP. Windows 11 redessine en permanence et crashe à chaque fois. viridian n'a aucun effet.
Comment hidsim rentre là-dedans : il fait un screendump au démarrage et injecte des mouvements de souris, soit les ingrédients 2 et 3 à lui seul, pendant que QEMU suit encore l'affichage pendant environ une minute après la session précédente. La bissection par moitiés des 40 plugins (5 niveaux) tombe sur hidsim seul. hidsim relancé tout de suite : crash. Avec 60 s d'écart : propre.
4. Le gel de dom0, vu une fois
Le 2 octobre vers 02:19 (heure du serveur) : Windows 10 22H2, viridian à 0, viewer VNC attaché d'emblée, cycles Ctrl+C et relance de -a syscalls. Cycles 1 à 6 normaux, dom0 gelé environ 16 s après le début de l'attache du cycle 7. Le ping répond, sshd est mort, dernière ligne du journal : rcu: INFO: rcu_preempt detected stalls. Redémarrage électrique. J'ai mis un netconsole en place pour la suite.
Le tout premier blocage, le 26 septembre, ne ressemblait à aucun des deux : DRAKVUF dans poll(), 0 événement, écran noir pendant 22 min, puis reason 49 après un kill -9. Jamais revu.
5. La matrice de versions Xen
Mêmes binaires, même snapshot 25H2, trois Xen : 4.21.0 release, 4.21.2 compilé en debug=y avec superpages, 4.22.0 release. Mêmes 3 crashs (E2, E3, E6), mêmes 2 contrôles propres (E1, E7). Le build debug n'imprime rien avant domain_crash. XSA-480 et les correctifs des branches stables ne touchent pas à ça. Superpages hors de cause (hap_1gb=0 hap_2mb=0 ou non, même résultat). Xen 4.20 non testé.
6. Ce que j'ai cru comprendre en lisant le code, et le patch qui a tué l'hôte
Hypothèse par lecture, pas par preuve : hap_track_dirty_vram() appelle p2m_enable_hardware_log_dirty(), donc ept_enable_pml(), donc ept_set_ad_sync(d, true) puis vmx_domain_update_eptp(d). Ce dernier charge l'EPTP du p2m hôte dans tous les vCPU sans regarder altp2m. Un vCPU qui tournait sur la vue N tourne maintenant sur le p2m hôte alors que p2midx dit encore N. La plage VRAM est marquée à recalculer, l'écriture du framebuffer fault sur la table hôte, ept_handle_misconfig() résout contre la vue, resolve_misconfig() renvoie 0, crash.
Patch candidat de 27 lignes, puis une v3 : charger l'EPTP de la vue réelle, par vCPU. Résultat, à raconter tel quel : E2 passe pour la première fois (11 262 événements). Mais avec la v1, DRAKVUF a tourné 14 min avec 0 événement, et pendant E3, l'hôte est mort (redémarrage électrique). C'est un diagnostic et une direction, pas un correctif validé. Je n'ai rien envoyé à xen-devel.
7. Le retournement : ept=ad=0
Le 2 octobre, Tamas répond qu'un vieux bug Xen mêlait framebuffer, altp2m et PML, et que son hôte démarre avec ept=ad=0, ce qui désactive PML. Mon hôte n'avait pas cette option. DRAKVUF la fournit pourtant, dans package/extra/etc/default/grub.d/xen.cfg, que je n'avais jamais installé parce que je compile depuis les sources.
Contre-test le 5 octobre, même binaire Xen 4.21.0 plus ept=ad=0 : E1, E2 deux fois, E6 et E3 tous propres (18 374, 18 738, 16 950 et 24 985 événements). Tamas demande si c'était avec viridian activé. Matrice du 6 octobre : 59 runs (57 simples et 2 boucles de 12 cycles), 0 crash, 0 gel. Invités 25H2, 24H2, 26H1 et Win10 22H2 ; viridian à 1, à 0 et ['base'] ; scénarios procmon, altp2m-min, souris plus screendumps, hidsim, tous les plugins (-x dkommon, voir drakvuf#1884), VNC, et la boucle Ctrl+C et relance avec viewer VNC, celle qui avait gelé dom0. 4 runs non probants (DRAKVUF n'a pas démarré, 0 événement) signalés comme tels. L'option est maintenant permanente sur l'hôte.
Reste une chose que je n'explique pas : Tamas dit voir reason 49 avec ad=0 depuis 4.21. Je ne l'ai jamais reproduit. J'ai proposé de comparer 4.20 et 4.21 s'il le souhaite.
8. Ce que le bump de LibVMI a montré en passant
Pour tester le sous-module à jour (df8547ff vers 00fa821), j'ai comparé base et bump sur 9 invités (Windows 11 de 21H2 à 26H1, Windows 10 1809 et 22H2, Debian 12) : vmi-process-list identique au caractère près (0 ligne de diff, 76 à 114 processus), 4 plugins en rc 0, injection createproc réussie sur les 7 Windows, readfile Linux au sha256 près, CI rejouée en local (jammy, noble, bookworm, complexité 79 et 3784 des deux côtés). etwmon startup failed sur 21H2 à 23H2 des deux côtés, donc pas lié. Les attaches pendant le boot (bootmgr, winload, LSTAR) restent à 123 s, 448 à 372 s et 20 s avec 0 événement : c'est libvmi#1125, pas le bump. La PR de bump n'est pas ouverte : j'attendais libvmi#1126, qui l'est depuis, et je n'ai pas repris le sujet.
Note d'ABI, trouvée par un débordement de pile : libvmi#1074 agrandit page_info_t de 128 à 136 octets sans changer la version de libvmi.so.0.0.15. Mélanger un LibVMI système et un DRAKVUF compilé contre le sous-module déborde dans memdump, codemon, exploitmon et libusermode (ASan pointe memdump.cpp:572). Un seul DRAKVUF par LibVMI.
9. Mes erreurs en chemin
- J'ai d'abord écrit que « c'est la version de LibVMI ». Une bissection sur un seul run accusait
033a0ec, et le run de confirmation était propre. Les statistiques l'ont réglé : la base crashe 3 fois sur 3 avec 0 s d'écart entre deux sessions, 1 fois sur 3 avec 2 s. LibVMI ne faisait que décaler le timing. - Le « stack smash » vu au début était le
page_info_tde la section 8, pas un bug de code. - J'ai effacé des fixtures
xl savequi auraient servi à rejouer des états. - Le patch Xen a été testé sur l'hôte de travail. Il aurait fallu une machine jetable.
Ce que je n'ai PAS fait
- Pas de correctif Xen validé, rien sur xen-devel.
- Xen 4.20 non testé.
- Le reason 49 avec
ad=0de Tamas, jamais reproduit. - Le gel dom0 : une occurrence, pas de cause.
Sources
- Issue drakvuf#1860, commentaires des 24, 27 et 30 septembre, 2, 5 et 6 octobre, avec les archives de preuves.
altp2m-min.c, les deux patches Xen, les résumés00-summary.txtde chaque série (E1 à E9, souris, matrice 4.21.2 et 4.22.0,ad=0), etbump-results-20261001.tgz(matrice, bissection,hidsim, VNC). En local, fournis sur demande.- Les chiffres de ce billet ont été recontrôlés contre ces résumés.