
Prueba de concepto para CVE-2026-86476: lectura/escritura fuera de límites en el heap de GStreamer GstVideoMeta mediante la deserialización de unixfd, con reproducción con ASan y cadena de RCE.
Un desbordamiento de búfer en el heap en libgstvideo de GStreamer (gst-plugins-base). Un GstVideoMeta describe cómo se distribuyen los píxeles de un fotograma de vídeo en la memoria. Cuando esa descripción se deserializa desde un par unixfd no confiable, nada comprueba que la geometría que declara encaje dentro del búfer que la respalda. Un par puede establecer strides de plano sobredimensionados, y el consumidor lee o escribe más allá de la asignación.
| Campo | Valor |
|---|---|
| CVE | CVE-2026-86476 |
| Clase | CWE-125 (lectura fuera de límites), CWE-787 (escritura fuera de límites), alcanzada a través de CWE-502 (deserialización de datos no confiables) |
| Componente | libgstvideo: gstvideometa.c, video-frame.c |
| Impacto | Lectura fuera de límites (confirmada), escritura fuera de límites hasta ejecución de código (demostrada) |
| Disparador | Un GstVideoMeta con strides de plano fabricados, deserializado desde un par unixfd |
| Punto de fallo | video_orc_convert_I420_BGRA, el kernel ORC de videoconvert |
El fallo son tres comprobaciones ausentes que se combinan.
Sin comprobación de tamaño en la rama meta del mapeo de fotograma. gst_video_frame_map_id() en video-frame.c tiene dos ramas. La rama sin meta termina con if (frame->map[0].size < info->size) goto invalid_size;. La rama meta, que se toma siempre que hay un GstVideoMeta presente, solo impone info->width <= meta->width e info->height <= meta->height. Esos son límites inferiores. Nada compara la geometría del plano con el tamaño de la memoria real.
default_map() comprueba un byte por plano. En gstvideometa.c, la memoria del plano se resuelve con gst_buffer_find_memory(buffer, offset, 1, ...). El argumento de tamaño es 1. El código confirma que existe un único byte en el offset de inicio del plano y luego copia el stride tal cual. La extensión real del plano, stride * height, nunca se compara con la asignación. Un comentario FIXME use plane size está justo encima de la comprobación.
El deserializador no comprueba ningún rango. video_meta_deserialize() solo valida que el lector de bytes no se quedó sin entrada, que n_planes <= GST_VIDEO_MAX_PLANES y que version != 0. Los offsets y los strides se copian directamente del flujo. Cada stride se lee como un gint32 con signo, por lo que se acepta un stride negativo.
En el lado receptor, gst_unix_fd_src_create() deserializa los metas contra un búfer vacío y adjunta la memoria del par después. El stride que porta el meta y el tamaño de la memoria que respalda el búfer nunca se comparan.
Un par hostil serializa un GstVideoMeta para un fotograma I420 de 320x240 con cada stride de plano establecido a 1000000. El consumidor lo deserializa sin comprobación de rango, adjunta la pequeña memoria real y mapea el fotograma. default_map() queda satisfecho porque el primer byte de cada plano existe. videoconvert entonces lee cada plano de entrada durante stride * height bytes y avanza más allá de la asignación.
Eso es la lectura. La escritura es la imagen especular. gst_video_frame_copy_plane() hace un memcpy de stride * height bytes, así que cuando un búfer pequeño que porta el meta mentiroso es el destino de una copia, el memcpy escribe más allá de su respaldo.
El transporte unixfd copia los búferes que no son FD a un memfd, así que la tubería estándar hace que la lectura choque contra un límite de mapeo mmap (caída y fuga de información). Conducir el mismo deserializador con los mismos bytes no confiables contra un destino respaldado por el heap ejercita la primitiva de escritura. El transporte decide si el desbordamiento golpea un límite de mapeo o el heap. No decide si el fallo existe.
Con la escritura en el heap, la prueba de concepto sobrescribe el puntero de función free en la cabecera de un GstMiniObject vecino, coloca la cadena de comando donde cae en el primer registro de argumento y dispara un unref para que el puntero falsificado se llame como system().
Cualquier pipeline que acepte búferes de vídeo a través de unixfd desde un proceso en el que no confía plenamente. Decodificadores de medios en sandbox, pipelines divididos al estilo portal y cualquier cosa que use unixfdsrc para recibir fotogramas a través de un límite de privilegios o de confianza.
Confirmado:
libgstreamer-1.0.so.0.2804.0 de Debian (1.28.4-2) con glibc 2.42.Dos procesos a través de un socket unix, solo con elementos estándar.
fakesrc ! capsfilter ! unixfdsink, enviando un búfer I420 válido de 320x240 con strides reales 320/160/160 pero con un GstVideoMeta cuyos strides de plano son 1000000.unixfdsrc ! videoconvert ! capsfilter ! fakesink, donde el capsfilter fuerza I420 a BGRA para que videoconvert deba leer el fotograma según los strides del meta.| Ejecución | Strides del meta | Resultado |
|---|---|---|
| 1 | 1000000 / 1000000 / 1000000 | lectura fuera de límites bajo ASan en video_orc_convert_I420_BGRA |
| 2 | 320 / 160 / 160 | finalización normal, el búfer llega a fakesink |
Compile GStreamer con -Db_sanitize=address para la prueba del lado de lectura. La prueba de concepto de RCE se ejecuta sobre una compilación estándar, porque una compilación con ASan aborta en el primer memcpy que desborda.
default_map(), sustituir el gst_buffer_find_memory(..., 1, ...) de un byte por una comprobación contra la extensión del plano stride * height.gst_video_frame_map_id() la misma comprobación invalid_size que tiene la rama sin meta.video_meta_deserialize(), comprobar los rangos de offsets y strides contra el búfer, y rechazar strides negativos.Cualquiera de estas rompe la cadena. Las tres juntas son la corrección correcta para una estructura que cruza un límite de confianza.