# 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](https://github.com/tklengyel/drakvuf/issues/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](https://github.com/libvmi/libvmi/pull/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](https://github.com/tklengyel/drakvuf/issues/1860) (ouverte), [libvmi#1123](https://github.com/libvmi/libvmi/pull/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](index.html?w=libvmi-support-windows-11-server-32bits.md), 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](https://github.com/tklengyel/drakvuf/issues/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](https://github.com/libvmi/libvmi/issues/1125), pas le bump. La PR de bump n'est pas ouverte : j'attendais [libvmi#1126](https://github.com/libvmi/libvmi/pull/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](https://github.com/libvmi/libvmi/pull/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_t` de la section 8, pas un bug de code.
- J'ai effacé des fixtures `xl save` qui 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=0` de Tamas, jamais reproduit.
- Le gel dom0 : une occurrence, pas de cause.

## Sources

- Issue [drakvuf#1860](https://github.com/tklengyel/drakvuf/issues/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és `00-summary.txt` de chaque série (E1 à E9, souris, matrice 4.21.2 et 4.22.0, `ad=0`), et `bump-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.
