bug:Out-of-bounds read in SgiInput::uncompress_rle_channel (bpc==1 RLE "run" branch lacks an input bounds check)

Cerrado Apto para principiantes
#5,505 1 comentario 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 2 días

@lgritz ya está trabajando en esto.

Desde el 28/9/2026.

  • #5513 de @lgritz — abierto

Evaluación

Dificultad
2/5
Tiempo estimado
1-3 horas
Aptitud para principiantes
80/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
cpp
Área
security

Línea de trabajo

Comienza en src/sgi.imageio/sgiinput.cpp, en SgiInput::uncompress_rle_channel(), centrándote en la rama de ejecución RLE para bpc==1 y comparándola con la comprobación de límites de la rama literal. Ejecuta el poc.sgi adjunto mediante oiiotool bajo ASAN y verifica después que la entrada malformada se rechaza sin una lectura fuera de límites y que las herramientas de imagen afectadas ya no abortan ni informan del fallo del sanitizer.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

Summary

While fuzzing oiiotool with AFL++ + ASAN we found that a malformed SGI
(.sgi) file triggers a span out-of-bounds read inside
SgiInput::uncompress_rle_channel(). In builds with contract assertions
enforced (the OIIO 3.x default) the violation aborts the process; in builds
compiled with hardening NONE + NDEBUG the same access is a 1-byte
heap-buffer-overflow read past the rle_scanline allocation.

Still reproduces on current main (dc87b17ab, 2026-09-27).

Reproducer

SGI file, RLE storage, dimension=3, 100x63, 4 channels, bpc=1: poc.sgi
attached (20 KB).

$ oiiotool -i poc.sgi --printinfo

Any of oiiotool / iconvert / iinfo / maketx reading the file hits it;
the crash is in the reader, independent of the tool.

Output

Build: clang 16, -g -O1 -fno-omit-frame-pointer, ASAN, contract
assertions at default (ENFORCE).

src/include/OpenImageIO/span.h:221 (operator[]): Contract assertion failed: idx < m_size
[abort]

With ASAN_OPTIONS=handle_abort=1, the aborting thread stack:

#5 SgiInput::uncompress_rle_channel(long, long, span<unsigned char>)   src/sgi.imageio/sgiinput.cpp
#6 SgiInput::read_native_scanline(...)          src/sgi.imageio/sgiinput.cpp:211:18
#7 ImageInput::read_native_scanlines(...)       src/libOpenImageIO/imageinput.cpp:521:19

Root cause

Current main, src/sgi.imageio/sgiinput.cpp, bpc==1 branch of
uncompress_rle_channel():

while (i < scanline_len) {
    unsigned char value = rle_scanline[i++];       // count byte (i < scanline_len OK)
    int count           = value & 0x7F;
    if (!count) break;
    if (value & 0x80) {                            // literal copy
        while (count--) {
            if (i >= scanline_len || limit <= 0) {  // <-- input bound IS checked here (line 286)
                errorfmt("Corrupt RLE data"); return false;
            }
            outbuf[iout++] = rle_scanline[i++];
            --limit;
        }
    } else {                                       // run: repeat next value
        value = rle_scanline[i++];                 // <-- line 296: NO `i >= scanline_len` check!
        while (count--) { ... }
    }
}

If the RLE count byte is the last byte of the scanline chunk
(i == scanline_len - 1 at loop top), consuming it advances i to
scanline_len, and the "run" branch then reads rle_scanline[i] one past
the end. The literal branch checks i >= scanline_len (line 286) but the
run branch was missed. The final i != scanline_len sanity check at the end
of the function would have rejected this data — but the OOB read happens
before it.

A minimal fix is mirroring the literal-branch check before line 296:

if (i >= scanline_len) { errorfmt("Corrupt RLE data"); return false; }
value = rle_scanline[i++];

Impact

  • Contract assertions enforced (OIIO 3.x default): process abort on a
    malformed file — denial of service for CLI tools (oiiotool, iconvert,
    iinfo, maketx) and any app using the SGI reader on untrusted input.
  • OIIO_HARDENING_NONE + NDEBUG builds: 1-byte heap-buffer-overflow read
    past the new unsigned char[scanline_len] allocation.

Related work

This is in the same function as GHSA-jg3q-vm3q-2j35 / CVE-2026-43903
(fixed in 3.0.18.0 / 3.1.13.0) and #5321 (16-bit odd byte counts), but the
access above is still reachable on current main — it is an
incomplete fix, leaving a remaining unchecked read in the bpc==1 run branch.

Meta

Found by a coverage-guided AFL++ fuzzing campaign (569-file multi-format
seed corpus from the OIIO testsuite) run during verification of #4552 /
CVE-2024-55194. 19 of the campaign's saved crash inputs reproduce this one
bug; the attached PoC is a readily-reproducible one.

Happy to send additional crash inputs or a minimized PoC if useful.

poc.zip

Lenguaje dominante
C++
Estrellas
2.4k
Forks
718
Merge medio
2 d 18 h
PR fusionados (30 d)
85

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de AcademySoftwareFoundation/OpenImageIO

Todos los issues de AcademySoftwareFoundation/OpenImageIO

Issues similares

Más issues de C++

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.