Skip to content

EbmlBinary::ReadData() ignores short reads, exposing uninitialised heap memoryt #360

Description

@JudithMalshini

reported by : Judith Malshini , Chamod Anuradha @https://github.com/Chamodanu

EbmlBinary::ReadData() ignores short reads, exposing uninitialised heap memory
Version: master 1f64c9a (2.0.0-dev) and v1.x 3c0b5b8 (1.4.7+3), both 2026-09-20. Earlier releases contain the same code.

Summary

EbmlBinary::ReadData() allocates GetSize() bytes, calls SetValueIsSet(), and returns the result of a single input.read() without checking it:

// src/EbmlBinary.cpp:91-93
SetValueIsSet();
return input.read(Data, GetSize());
EbmlElement::Read() discards that return value (src/EbmlElement.cpp:554). When the input ends before GetSize() bytes are available, the tail of the buffer is never written, yet ValueIsSet() is true and EbmlBinary::RenderData() writes the whole buffer back out (src/EbmlBinary.cpp:57, output.writeFully(Data, GetSize())).

Any consumer that re-serialises a parsed element therefore writes uninitialised heap memory into its output. For a remuxer such as mkvmerge this means heap residue from the process ends up inside the produced .mkv; in a server-side pipeline the residue can include other requests' data.

Reproducer

35-byte Matroska file (python3 lab/make_mkv_poc.py poc.mkv):

18538067 500D 1654AE6B 5007 AE 5004 63A2 5000
42424242424242424242424242424242
Segment/Tracks/TrackEntry declare their children correctly; CodecPrivate declares 4096 bytes but the file ends after 16. Parsing with FindNextID(EBML_INFO(KaxSegment), 0xFFFFFFFF) + Read() and then rendering CodecPrivate yields 4100 bytes, of which 4080 are allocator fill:

$ MALLOC_PERTURB_=65 ./mkv_bin_leak
file=35 bytes, CodecPrivate declared size=4096
rendered=4100 bytes, allocator-fill (0xbe) bytes=4080
rendered head: 63 a2 50 00 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42
be be be be ...
0xbe == MALLOC_PERTURB_ ^ 0xff. With MALLOC_PERTURB_=170 the same 4080 bytes are 0x55, with =1 they are 0xfe — the data tracks the allocator, not the file. AddressSanitizer shows the same with its 0xbe malloc fill.

Suggested fix

const auto read = input.read(Data, GetSize());
if (read != GetSize()) {
free(Data); Data = nullptr;
throw std::runtime_error("unexpected EOF in binary element");
}
return read;
or zero-fill the tail if partial elements must be tolerated. Rejecting declared sizes larger than the remaining input would also help (see issue 2).

Activity

  1. robUx4 commented on Oct 27, 2026

    @robUx4
    Contributor

    Good catch! The other calls of IOCallback::read() are just reading one byte and they check that byte was read properly.

  2. added theissue type on Oct 27, 2026
  3. robUx4 commented on Oct 27, 2026

    @robUx4
    Contributor

    Dunno about mkvtoolnix but VLC is not affected as it uses its own IOCallback which either reads all the data or returns 0.

  4. JudithMalshini commented on Nov 7, 2026

    @JudithMalshini
    Author

    Thanks @robUx4 for the quick fix in #362 and #363.Good to know about VLC.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions