You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
EbmlBinary::ReadData() ignores short reads, exposing uninitialised heap memoryt #360
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.
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
robUx4 commentedon Oct 27, 2026
Good catch! The other calls of
IOCallback::read()are just reading one byte and they check that byte was read properly.robUx4 commentedon Oct 27, 2026
Dunno about mkvtoolnix but VLC is not affected as it uses its own
IOCallbackwhich either reads all the data or returns 0.JudithMalshini commentedon Nov 7, 2026
Thanks @robUx4 for the quick fix in #362 and #363.Good to know about VLC.