# Phase 7.5 — Robustesse paquet A (tests négatifs protocole) > Document produit le 2026-05-13 dans le cadre du plan directeur > `REDOX_COSMIC_XWAYLAND_RS_PLAN.md`. > > **Scope strict** : > - le compositor ne DOIT JAMAIS paniquer face à un client malformé > - face à des paramètres invalides, soit ignore proprement (silent > drop), soit `post_error` sur la resource fautive > - 4 cas exercés a minima : exit brutal, mauvais ack serial, > dimensions buffer nulles, stride incohérent avec width > > **Hors scope 7.5** : fuzzing exhaustif (à brancher dans CI plus > tard), filtrage multi-client (7.6), move/resize (7.7). ## Verdict **✅ Compositor anti-panic validé runtime sur 4 cas malformés exécutés en succession dans des sous-processus séparés.** Capture : ![](phase7-5-after-fuzz.png) — état du framebuffer après les 4 cas. Le rectangle noir à `(60, 60)` est la surface persistante du fuzz1 (commit-puis-exit-brutal, buffer ARGB rempli de 0). Le curseur software est toujours actif au centre, fond compositor bleu nuit, aucun crash ni "Display output not active". Le compositor a continué à ticker pendant 30+ secondes après la fin du fuzz. Logs `/tmp/fuzz.log` : ``` [fuzz] phase 7.5 — protocole tests négatifs [fuzz1] brutal exit without destroy [fuzz1] state complete, exiting brutally NOW [fuzz] child fuzz1 exited (status=0) [fuzz2] ack_configure with bad serial 99999 [fuzz2] sending ack_configure(99999) — should be refused [fuzz2] sending ack_configure(2) — should be accepted [fuzz2] PASS [fuzz3] create_buffer with null dimensions [fuzz3] PASS [fuzz4] create_buffer with stride < width*4 [fuzz4] PASS [fuzz] all cases exercised, compositor should still be up [fuzz] PASS ``` Logs côté compositor `/tmp/comp.log` filtrés : ``` [frontend] xdg_surface.ack_configure: serial 99999 > last_sent 2, ignoring [frontend] wl_shm_pool.create_buffer rejected: offset=0 width=0 height=0 stride=0 [frontend] wl_shm_pool.create_buffer rejected: offset=0 width=100 height=10 stride=10 ``` Chaque garde a été déclenché exactement une fois, dans l'ordre attendu. Aucun panic, aucun deadlock, aucune corruption d'état (les surfaces des fuzz suivants se créent et reçoivent bien le focus). ## Modifications apportées ### `redox-wl-wayland-frontend` **`xdg_surface::Request::AckConfigure`** : vérifie que `serial <= last_serial` avant de l'enregistrer dans `acked_serial`. Si le client ack un serial jamais émis, on log et on ignore. Pas de `post_error` pour 7.5 (à durcir si on observe des abus délibérés ; les toolkits réels peuvent envoyer des ack stale légitimes après un reconfigure race). **`BufferData`** : nouveau champ `valid: bool`. Mis à `false` au `wl_shm_pool::Request::CreateBuffer` si l'une des conditions est violée : - `width <= 0 || height <= 0 || stride <= 0 || offset < 0` - `stride < width * 4` (pour Argb8888 ARGB 4 octets/pixel) - `offset + (height-1)*stride + width*4 > pool.size` Le buffer wl_buffer est quand même initialisé via `data_init.init` (pour ne pas violer le contrat wayland-server et laisser un id orphelin côté client), mais marqué invalide. Le commit handler court-circuite la lecture pour ces buffers. **`ShmPool::read_argb`** : signature passée de `Vec` à `Option>`. Refuse de lire si `w == 0`, `h == 0`, `stride <= 0`, `stride < w*4`, ou si l'accès final dépasse `self.size`. Calculs en `checked_add` / `checked_mul` pour éviter tout overflow sur des params adversariaux. Évite tout accès mémoire UB. **`wl_surface::Request::Commit`** : vérifie `bd.valid` avant de tenter la lecture, et gère le `None` retourné par `read_argb` comme un buffer ignoré (log warning, surface garde son ancien contenu). ### `redox-wl-test-fuzz-protocol` (nouveau crate, ~370 lignes) Binaire qui exerce 4 cas malformés, chacun dans un `fork()` séparé pour qu'un éventuel crash d'un cas ne contamine pas les suivants : | Cas | Description | |---|---| | fuzz1 | Connect → surface + xdg_toplevel + ack + buffer + commit → `process::exit(0)` brutal sans destroy. Le kernel ferme les fds, le compositor doit gérer la déconnexion via les Drop des UserData wayland-server. | | fuzz2 | Connect → toplevel → attendre configure → `ack_configure(99999)` puis `ack_configure(real_serial)`. Le compositor doit refuser le premier (log warning, ignore) et accepter le second. | | fuzz3 | Connect → toplevel → `create_buffer(width=0, height=0, stride=0)` → attach + commit. Le compositor doit accepter le wl_buffer (pour ne pas violer le protocole client) mais marquer invalid et ignorer au commit. | | fuzz4 | Idem, avec `width=100, height=10, stride=10` (au lieu de stride=400). Le guard `stride < width*4` doit déclencher. | Le parent attend chaque enfant via `waitpid` puis sleep 1 s avant le suivant, pour laisser au compositor le temps de logger sa réaction au cas précédent. À la fin, log `[fuzz] PASS` si tout s'est exécuté sans crash côté client. ## Validation runtime QEMU headless, image Redox boot complet, service init `40_phase75_fuzz` qui lance le compositor puis le fuzz binary 4 secondes plus tard via un wrapper script `/usr/bin/launch_phase75.sh` (rappel 7.4 : `nowait sh -c "..."` direct ne passe pas le parsing init Redox, il faut un script). Vérification post-run : - ✅ Tous les sous-processus enfants exit avec status=0 - ✅ `[fuzz] PASS` final - ✅ Tous les logs `[frontend]` attendus sont présents - ✅ Le compositor continue à ticker pendant 30+ s après le PASS - ✅ Pas de message `panicked at` dans `/tmp/comp.log` - ✅ Pas de blocage : les surfaces des fuzz suivants se créent normalement, les focus changes s'enchaînent (SurfaceId(0), 2, 3 vus dans les logs — les ids 1, 4 correspondent probablement aux xdg_surface comptés à part) ## Limitations connues (à traiter en sous-tickets ultérieurs) - **Surface "fantôme" du fuzz1 persiste après l'exit brutal du client** : visible comme un rectangle noir à (60,60) dans la capture finale. Le client a fait `commit` (donc buffer copié dans le registry) puis exit. wayland-server détecte la déconnexion mais le `wl_surface::Destroy` handler n'est PAS appelé automatiquement pour les resources d'un client qui ferme socket sans destroy propre — il faudrait hooker `DumbClientData::disconnected` pour faire le cleanup explicite. À ajouter en 7.6 (multi-clients) où ce cas deviendra fréquent. - **Pas de `post_error` envoyé** : pour 7.5 on ignore silencieusement (avec log). Plus permissif que la spec. Si un toolkit réel envoie un mauvais serial par bug, on reste tolérant. À durcir si on observe des bugs systématiques côté serveur causés par ce laxisme. - **Pas testés en 7.5** : - `xdg_surface.destroy` avant `xdg_toplevel.destroy` (spec : error, mais pas dangereux côté serveur — on ignore) - destroy d'un wl_buffer attaché à une surface (la sémantique Wayland est qu'on doit envoyer `wl_buffer.release` avant ; on n'envoie jamais `release` actuellement — TODO 7.6) - `wl_shm_pool.resize` (no-op actuellement — TODO) - Format `wl_shm` non advertised (formats inconnus déjà ignorés) - Surface avec rôle déjà assigné (`wl_data_source` n'existe pas, le rôle xdg_surface est protégé par `xdg_pending_initial_configure`) - **Pas de fuzzing automatique avec corpus random** : reportable beaucoup plus tard, avec libfuzzer + cargo-fuzz. Pour 7.5 on se contente de cas dirigés. ## Critère de fin 7.5 > Face aux 4 cas malformés exercés par le fuzz protocol binary, le > compositor ne panique pas, ne se bloque pas, et reste pleinement > opérationnel pour les clients suivants. **✅ Validé.** Le compositor a tourné 30+ s post-fuzz avec ticks continus, le curseur reste actif, les surfaces des fuzz suivants se créent et reçoivent leur focus. ## Code ``` crates/redox-wl-wayland-frontend/ # +~50 lignes (BufferData.valid, # AckConfigure validation, read_argb safe) crates/redox-wl-test-fuzz-protocol/ # nouveau crate (~370 lignes) ``` ## Suite phase 7.6 Multi-clients paquet B : - Au moins 3 clients fork parallèle, chacun avec sa fenêtre + frame callbacks rapides - Vérifier que les frame callbacks `done` arrivent à chaque client - Vérifier l'ordre Z stable sous load - Filtrage des events button/key au client focused uniquement (au lieu du broadcast actuel) - Cleanup automatique post-disconnect (corrige le sub-bug 7.5 : surface fantôme du fuzz1) - `wl_buffer.release` envoyé après chaque copy au commit Estimé : 1-2 sessions. --- *Fin du document de phase 7.5.*