🎉 Phase 7.5 — robustesse paquet A validée runtime
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
This commit is contained in:
parent
c40ca9fcc8
commit
7e81dec637
5 changed files with 731 additions and 18 deletions
|
|
@ -87,11 +87,27 @@ impl ShmPool {
|
|||
})
|
||||
}
|
||||
|
||||
/// Lit les pixels du buffer à partir de l'offset, taille connue.
|
||||
/// Pas de validation alignment ; le caller doit fournir des params
|
||||
/// cohérents (offset + h * stride <= self.size, stride == w*4).
|
||||
unsafe fn read_argb(&self, offset: usize, w: u32, h: u32, stride: i32) -> Vec<u32> {
|
||||
/// Lit les pixels du buffer à partir de l'offset.
|
||||
///
|
||||
/// Phase 7.5 : retourne `None` si les paramètres demandent une lecture
|
||||
/// hors-pool (overrun) ou si `stride < w*4`. Le caller doit alors
|
||||
/// ignorer le buffer côté composition. Évite tout accès mémoire UB
|
||||
/// face à un client malformé.
|
||||
unsafe fn read_argb(&self, offset: usize, w: u32, h: u32, stride: i32) -> Option<Vec<u32>> {
|
||||
if w == 0 || h == 0 || stride <= 0 {
|
||||
return None;
|
||||
}
|
||||
let stride = stride as usize;
|
||||
if stride < (w as usize).saturating_mul(4) {
|
||||
return None;
|
||||
}
|
||||
// last_byte = offset + (h-1)*stride + w*4
|
||||
let last_byte = offset
|
||||
.checked_add(stride.checked_mul((h as usize).saturating_sub(1))?)
|
||||
.and_then(|v| v.checked_add((w as usize).checked_mul(4)?))?;
|
||||
if last_byte > self.size {
|
||||
return None;
|
||||
}
|
||||
let n = (w as usize) * (h as usize);
|
||||
let mut out = Vec::with_capacity(n);
|
||||
for y in 0..h as usize {
|
||||
|
|
@ -100,7 +116,7 @@ impl ShmPool {
|
|||
out.push(unsafe { *row_ptr.add(x) });
|
||||
}
|
||||
}
|
||||
out
|
||||
Some(out)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -123,6 +139,9 @@ struct BufferData {
|
|||
stride: i32,
|
||||
/// Format Wayland brut (Argb8888 = 0, Xrgb8888 = 1)
|
||||
format: wl_shm::Format,
|
||||
/// Phase 7.5 : true si les paramètres sont valides (width/height/stride
|
||||
/// cohérents avec la taille du pool). Si false, le commit ignore ce buffer.
|
||||
valid: bool,
|
||||
}
|
||||
|
||||
/// Données par-surface : SurfaceId du compositor + buffer attaché en pending.
|
||||
|
|
@ -863,6 +882,35 @@ impl wayland_server::Dispatch<wl_shm_pool::WlShmPool, Arc<Mutex<ShmPool>>> for W
|
|||
Ok(f) => f,
|
||||
Err(_) => return, // format inconnu, on ignore
|
||||
};
|
||||
// Phase 7.5 : valider les dimensions et la cohérence avec la
|
||||
// taille du pool. La spec wl_shm.create_buffer demande
|
||||
// width/height/stride positifs et offset+stride*height <= pool size.
|
||||
// Si invalide, on init quand même le wl_buffer (sinon le client
|
||||
// aurait un id orphelin) mais on le marque `valid = false` pour
|
||||
// que le commit l'ignore.
|
||||
let mut valid = true;
|
||||
if width <= 0 || height <= 0 || stride <= 0 || offset < 0 {
|
||||
valid = false;
|
||||
}
|
||||
if valid && stride < width.saturating_mul(4) {
|
||||
valid = false;
|
||||
}
|
||||
if valid {
|
||||
let pool_size = pool.lock().unwrap().size as i64;
|
||||
let needed = (offset as i64).saturating_add(
|
||||
(stride as i64).saturating_mul((height as i64).saturating_sub(1))
|
||||
.saturating_add((width as i64).saturating_mul(4)),
|
||||
);
|
||||
if needed > pool_size {
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
if !valid {
|
||||
println!(
|
||||
"[frontend] wl_shm_pool.create_buffer rejected: \
|
||||
offset={offset} width={width} height={height} stride={stride}"
|
||||
);
|
||||
}
|
||||
let bd = BufferData {
|
||||
pool: Arc::clone(pool),
|
||||
offset,
|
||||
|
|
@ -870,6 +918,7 @@ impl wayland_server::Dispatch<wl_shm_pool::WlShmPool, Arc<Mutex<ShmPool>>> for W
|
|||
height: height as u32,
|
||||
stride,
|
||||
format,
|
||||
valid,
|
||||
};
|
||||
data_init.init(id, bd);
|
||||
}
|
||||
|
|
@ -953,19 +1002,30 @@ impl wayland_server::Dispatch<wl_surface::WlSurface, Arc<SurfaceData>> for Wayla
|
|||
// Lire le buffer attaché (s'il y en a un)
|
||||
let bd_opt = data.pending_buffer.lock().unwrap().clone();
|
||||
if let Some(bd) = bd_opt {
|
||||
// Lire les pixels et créer un SurfaceBuffer compositor-core
|
||||
let pool = bd.pool.lock().unwrap();
|
||||
let pixels = unsafe {
|
||||
pool.read_argb(bd.offset as usize, bd.width, bd.height, bd.stride)
|
||||
};
|
||||
let sb = SurfaceBuffer::from_pixels(bd.width, bd.height, pixels);
|
||||
// Pour une surface curseur, on stocke le buffer mais on
|
||||
// garde visible=false (la surface ne doit pas apparaître
|
||||
// dans la composition normale, seulement via draw_cursor).
|
||||
state.registry.modify_pending(id, |s| {
|
||||
s.buffer = Some(sb);
|
||||
s.visible = !is_cursor;
|
||||
});
|
||||
if !bd.valid {
|
||||
// Phase 7.5 : buffer marqué invalide à la création
|
||||
// (dimensions ou offset incohérents avec le pool).
|
||||
// On ignore plutôt que de lire des octets hors-pool.
|
||||
} else {
|
||||
// Lire les pixels et créer un SurfaceBuffer compositor-core
|
||||
let pool = bd.pool.lock().unwrap();
|
||||
let pixels_opt = unsafe {
|
||||
pool.read_argb(bd.offset as usize, bd.width, bd.height, bd.stride)
|
||||
};
|
||||
if let Some(pixels) = pixels_opt {
|
||||
let sb = SurfaceBuffer::from_pixels(bd.width, bd.height, pixels);
|
||||
// Pour une surface curseur, on stocke le buffer
|
||||
// mais visible=false (cf draw_cursor).
|
||||
state.registry.modify_pending(id, |s| {
|
||||
s.buffer = Some(sb);
|
||||
s.visible = !is_cursor;
|
||||
});
|
||||
} else {
|
||||
println!(
|
||||
"[frontend] commit: read_argb refused buffer (overrun guard)"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
state.registry.commit(id);
|
||||
if !is_cursor {
|
||||
|
|
@ -1170,6 +1230,19 @@ impl wayland_server::Dispatch<xdg_surface::XdgSurface, Arc<XdgSurfaceData>> for
|
|||
// sert pour les decorations CSD que nous ne supportons pas).
|
||||
}
|
||||
xdg_surface::Request::AckConfigure { serial } => {
|
||||
// Phase 7.5 : refuser un ack pour un serial jamais émis.
|
||||
// Spec xdg-shell : si le client ack un serial inconnu, c'est
|
||||
// une erreur de protocole. Pour 7.5 on tolère (ignore silent +
|
||||
// log) plutôt que post_error, le temps de mesurer si des
|
||||
// toolkits réels en abusent. À durcir si on observe des
|
||||
// abus délibérés.
|
||||
let last = *data.last_serial.lock().unwrap();
|
||||
if serial > last {
|
||||
println!(
|
||||
"[frontend] xdg_surface.ack_configure: serial {serial} > last_sent {last}, ignoring"
|
||||
);
|
||||
return;
|
||||
}
|
||||
*data.acked_serial.lock().unwrap() = serial;
|
||||
// Si c'était l'initial configure, débloquer l'affichage
|
||||
if let Some(sd) = data.wl_surface.data::<Arc<SurfaceData>>() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue