Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

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

Đang mở
#2,433 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

@pranayr710 đang làm issue này rồi.

Từ ngày 27/10/2026.

  • #2435 của @pranayr710 — đang mở

Đánh giá

Độ khó
2/5
Thời gian dự kiến
1-3 giờ
Mức phù hợp với người mới
35/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Đình trệ
Công nghệ
c
Lĩnh vực
backend, security

Hướng nghiên cứu

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.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

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.)

Ngôn ngữ chính
C
Star
912
Fork
598
Merge trung bình
2 ngày 17 giờ
Pull request đã merge (30 ngày)
30

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của CCExtractor/ccextractor

Tất cả issue của CCExtractor/ccextractor

Issue tương tự

Thêm issue về C

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.