Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

[BUG] WTV read_header(): unchecked len and text_len from the file crash the parser (SEGV, 4 KB reproducer)

Abierto
#2,433 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

@pranayr710 ya está trabajando en esto.

Desde el 27/10/2026.

  • #2435 de @pranayr710 — abierto

Evaluación

Dificultad
2/5
Tiempo estimado
1-3 horas
Aptitud para principiantes
35/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Estancado
Stack tecnológico
c
Área
backend, security

Línea de trabajo

Read src/lib_ccx/wtv_functions.c, focusing on read_header() and the existing wtv_len_plausible() checks. Reproduce with the supplied 4 KB generator under AddressSanitizer, then check the length bounds and cleanup paths described in the issue. Done means malformed length fields are rejected without out-of-bounds access or leaks; issue #2435 already has an open pull request.

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

Descripción

Summary

read_header() in src/lib_ccx/wtv_functions.c reads two length fields straight
out of the .wtv file and uses them without checking either against the 1024-byte
buffer it parses into. A crafted file crashes CCExtractor; ASan reports a SEGV
inside read_header.

The outer element loop in wtv_get_more_data() was hardened at some point with
wtv_len_plausible() / wtv_resync(), but read_header(), which runs first and
walks the root directory, never got the same treatment.

Where

https://github.com/CCExtractor/ccextractor/blob/master/src/lib_ccx/wtv_functions.c

1. len - 32 underflows the size_t that buffered_read() takes

uint16_t len;
memcpy(&len, parsebuf + 16, 2);
...
if (len > 1024)            // only an upper bound
{
        mprint("Too large for buffer!\n");
        free(parsebuf);
        return CCX_EOF;
}
result = buffered_read(ctx, parsebuf, len - 32);

len covers the 32-byte record itself, so a file that declares len < 32 makes
len - 32 negative; buffered_read()'s third parameter is size_t, so it
becomes a value near 2^64. wtv_len_plausible() already rejects raw_len < 32
for the same field elsewhere in this file.

2. text_len is unbounded, and malloc(text_len + 1) wraps

uint32_t text_len;
memcpy(&text_len, parsebuf, 4);   // character count, from the file
char *string;
string = (char *)malloc(text_len + 1);
...
string[text_len] = '\0';
for (x = 0; x < text_len; x++)
        memcpy(&string[x], parsebuf + 8 + (x * 2), 1);

parsebuf is 1024 bytes and holds at most len - 32 bytes of file data, so
text_len must satisfy 8 + 2 * text_len <= len - 32. Nothing checks it:

  • a moderate text_len walks parsebuf + 8 + 2 * text_len past the allocation;
  • text_len + 1 is computed in uint32_t, so text_len == UINT32_MAX gives
    malloc(0) and string[text_len] = '\0' then writes roughly 4 GB past a
    zero-sized allocation.

3. The .entries. branch reads eight more bytes past the string

memcpy(&value, parsebuf + (text_len * 2) + 8, 4);
memcpy(&flag,  parsebuf + (text_len * 2) + 4 + 8, 4);

4. Minor: parsebuf leaks on the "Missing WTV header" path

That branch returns CCX_EOF without the free(parsebuf) every other error
path in the function does.

Reproducing

A .wtv is recognised purely from its first four bytes (B7 D8 00 20,
stream_functions.c), so a 4 KB file is enough. Generator:

import struct
HDR = b"\xB7\xD8\x00\x20\x37\x49\xDA\x11\xA6\x4E\x00\x07\xE9\x5E\xAD\x8D"
h = bytearray(0x42); h[0:16] = HDR
struct.pack_into("<I", h, 0x30, 0x4000)   # filelen
struct.pack_into("<I", h, 0x38, 1)        # root_dir, in 0x1000 chunks
out = bytearray(h) + b"\x00" * (0x1000 - 0x42)

body  = struct.pack("<I", 0xFFFFFFFF) + b"\x00" * 4          # text_len
body += b"".join(bytes([c, 0]) for c in b".entries.")        # UTF-16LE name
body += b"\x00" * 64
rec = bytearray(32)
rec[0:16] = b"\x11" * 16                                     # any non-EOF GUID
struct.pack_into("<H", rec, 16, 32 + len(body))              # len
out += bytes(rec) + body
out += b"\x00" * 32                                          # WTV_EOF record
open("wtv_textmax.wtv", "wb").write(bytes(out))
$ ccextractor wtv_textmax.wtv -o out.srt
==1==ERROR: AddressSanitizer: SEGV on unknown address 0x6021000048af
SUMMARY: AddressSanitizer: SEGV (/ccx+0xf337d5) in read_header

Built from master (d7eb132) with -fsanitize=address,undefined; reproduced on
3 of 3 runs (one of the three ended in ASan's DEADLYSIGNAL loop rather than a
clean report, but it is the same crash).

Suggested fix

Reject len < 32 the way wtv_len_plausible() already does, keep body_len = len - 32, and require 8 + 2 * text_len <= body_len before the string is
allocated (and eight bytes more in the .entries. branch). Plus the missing
free().

I have a patch and will open a PR referencing this issue.

(Analysis and patch prepared with AI assistance; the reproducer and the
sanitizer runs are my own.)

Lenguaje dominante
C
Estrellas
912
Forks
598
Merge medio
2 d 7 h
PR fusionados (30 d)
29

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 CCExtractor/ccextractor

Todos los issues de CCExtractor/ccextractor

Issues similares

Más issues de C

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.