Compositor anti-panic face à 4 cas malformés exercés en succession : brutal exit sans destroy, ack_configure avec mauvais serial, create_buffer avec dimensions nulles, create_buffer avec stride incohérent. Aucun crash, aucun blocage, ticks compositor continus 30s+ après la fin du fuzz. Frontend hardening : - BufferData.valid: bool, mis à false dans wl_shm_pool.create_buffer si dimensions/stride/offset incohérents avec la taille du pool. Le wl_buffer est quand même créé (contrat wayland-server) mais ignoré au commit. - ShmPool::read_argb signature passée de Vec<u32> à Option<Vec<u32>>. Refuse de lire si w/h/stride invalides 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 UB. - xdg_surface.ack_configure refuse les serials > last_sent (log + ignore, pas de post_error pour 7.5 — tolérance volontaire). - wl_surface.commit court-circuite la lecture pour les buffers invalides ou si read_argb retourne None (log warning, surface garde son ancien contenu). Nouveau crate : redox-wl-test-fuzz-protocol (~370 lignes) - fork() pour chaque cas afin qu'un crash potentiel d'un cas ne contamine pas les suivants - 4 cas : brutal exit, bad ack serial, null dimensions, bad stride - Le parent attend chaque enfant via waitpid avant le suivant Validation runtime QEMU : - [fuzz1..4] tous PASS, [fuzz] PASS final - [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 - Compositor continue à ticker 30+ s post-fuzz, curseur actif, surfaces des fuzz suivants créées et focusées normalement. Sub-bug documenté (à corriger 7.6) : la surface du fuzz1 (exit brutal sans destroy) persiste après la déconnexion du client. wayland-server détecte le close socket mais ne réveille pas automatiquement le wl_surface.Destroy handler. À hooker dans DumbClientData::disconnected pour le cleanup explicite. Doc complète : docs/phase7-5-robustness.md. Leyoda 2026 – GPLv3
190 lines
8.5 KiB
Markdown
190 lines
8.5 KiB
Markdown
# 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 :  — é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<u32>` à
|
|
`Option<Vec<u32>>`. 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.*
|