From 16c8a6c255a4109df1c7343d3def543fdb153963 Mon Sep 17 00:00:00 2001 From: Vanilagy <1696106+Vanilagy@users.noreply.github.com> Date: Wed, 13 Aug 2025 09:46:27 +0200 Subject: [PATCH] Gracefully handle invalid EBML headers --- src/matroska/ebml.ts | 31 +++++++++++++++++----- src/matroska/matroska-demuxer.ts | 45 +++++++++++++++++++++++++++----- 2 files changed, 63 insertions(+), 13 deletions(-) diff --git a/src/matroska/ebml.ts b/src/matroska/ebml.ts index ab5d8d4..b8b7829 100644 --- a/src/matroska/ebml.ts +++ b/src/matroska/ebml.ts @@ -379,7 +379,7 @@ export class EBMLWriter { const MAX_VAR_INT_SIZE = 8; export const MIN_HEADER_SIZE = 2; // 1-byte ID and 1-byte size -export const MAX_HEADER_SIZE = 4 + MAX_VAR_INT_SIZE; // 4-byte ID and 8-byte size +export const MAX_HEADER_SIZE = 2 * MAX_VAR_INT_SIZE; // 8-byte ID and 8-byte size export class EBMLReader { pos = 0; @@ -411,9 +411,13 @@ export class EBMLReader { const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 1); const firstByte = view.getUint8(offset); + if (firstByte === 0) { + return null; // Invalid VINT + } + let width = 1; let mask = 0x80; - while ((firstByte & mask) === 0 && width < 8) { + while ((firstByte & mask) === 0) { width++; mask >>= 1; } @@ -426,10 +430,14 @@ export class EBMLReader { const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 1); const firstByte = view.getUint8(offset); + if (firstByte === 0) { + return null; // Invalid VINT + } + // Find the position of VINT_MARKER, which determines the width let width = 1; let mask = 1 << 7; - while ((firstByte & mask) === 0 && width < MAX_VAR_INT_SIZE) { + while ((firstByte & mask) === 0) { width++; mask >>= 1; } @@ -509,8 +517,11 @@ export class EBMLReader { readElementId() { const size = this.readVarIntSize(); - const id = this.readUnsignedInt(size); + if (size === null) { + return null; + } + const id = this.readUnsignedInt(size); return id; } @@ -538,6 +549,10 @@ export class EBMLReader { readElementHeader() { const id = this.readElementId(); + if (id === null) { + return null; + } + const size = this.readElementSize(); return { id, size }; @@ -548,13 +563,17 @@ export class EBMLReader { const loadChunkSize = 2 ** 20; // 1 MiB const idsSet = new Set(ids); - while (this.pos < until - MAX_HEADER_SIZE) { - if (!this.reader.rangeIsLoaded(this.pos, this.pos + MAX_HEADER_SIZE)) { + while (this.pos <= until - MIN_HEADER_SIZE) { + if (!this.reader.rangeIsLoaded(this.pos, Math.min(this.pos + MAX_HEADER_SIZE, until))) { await this.reader.loadRange(this.pos, Math.min(this.pos + loadChunkSize, until)); } const elementStartPos = this.pos; const elementHeader = this.readElementHeader(); + if (!elementHeader) { + break; + } + if (idsSet.has(elementHeader.id)) { return elementStartPos; } diff --git a/src/matroska/matroska-demuxer.ts b/src/matroska/matroska-demuxer.ts index 641f3e6..9d3bfa5 100644 --- a/src/matroska/matroska-demuxer.ts +++ b/src/matroska/matroska-demuxer.ts @@ -239,6 +239,10 @@ export class MatroskaDemuxer extends Demuxer { ); const header = this.metadataReader.readElementHeader(); + if (!header) { + break; // Zero padding at the end of the file triggers this, for example + } + const id = header.id; let size = header.size; const startPos = this.metadataReader.pos; @@ -318,14 +322,19 @@ export class MatroskaDemuxer extends Demuxer { ); let clusterEncountered = false; - while (this.metadataReader.pos < this.currentSegment.elementEndPos) { + while (this.metadataReader.pos <= this.currentSegment.elementEndPos - MIN_HEADER_SIZE) { await this.metadataReader.reader.loadRange( this.metadataReader.pos, this.metadataReader.pos + MAX_HEADER_SIZE, ); const elementStartPos = this.metadataReader.pos; - const { id, size } = this.metadataReader.readElementHeader(); + const header = this.metadataReader.readElementHeader(); + if (!header) { + break; + } + + const { id, size } = header; const dataStartPos = this.metadataReader.pos; const metadataElementIndex = METADATA_ELEMENTS.findIndex(x => x.id === id); @@ -392,7 +401,10 @@ export class MatroskaDemuxer extends Demuxer { this.metadataReader.pos, this.metadataReader.pos + 2 ** 12, // Load a larger range, assuming the correct element will be there ); - const { id, size } = this.metadataReader.readElementHeader(); + const header = this.metadataReader.readElementHeader(); + if (!header) continue; + + const { id, size } = header; if (id !== target.id) continue; assertDefinedSize(size); @@ -469,6 +481,8 @@ export class MatroskaDemuxer extends Demuxer { const elementStartPos = this.metadataReader.pos; const elementHeader = this.metadataReader.readElementHeader(); + assert(elementHeader); + const id = elementHeader.id; let size = elementHeader.size; const dataStartPos = this.metadataReader.pos; @@ -720,12 +734,20 @@ export class MatroskaDemuxer extends Demuxer { const startIndex = reader.pos; while (reader.pos - startIndex <= totalSize - MIN_HEADER_SIZE) { - this.traverseElement(reader); + const foundElement = this.traverseElement(reader); + if (!foundElement) { + break; + } } } - traverseElement(reader: EBMLReader) { - const { id, size } = reader.readElementHeader(); + traverseElement(reader: EBMLReader): boolean { + const header = reader.readElementHeader(); + if (!header) { + return false; + } + + const { id, size } = header; const dataStartPos = reader.pos; assertDefinedSize(size); @@ -1113,6 +1135,8 @@ export class MatroskaDemuxer extends Demuxer { if (!this.currentCluster) break; const trackNumber = reader.readVarInt(); + if (trackNumber === null) break; + const relativeTimestamp = reader.readS16(); const flags = reader.readU8(); @@ -1148,6 +1172,8 @@ export class MatroskaDemuxer extends Demuxer { if (!this.currentCluster) break; const trackNumber = reader.readVarInt(); + if (trackNumber === null) break; + const relativeTimestamp = reader.readS16(); const flags = reader.readU8(); @@ -1184,6 +1210,7 @@ export class MatroskaDemuxer extends Demuxer { } reader.pos = dataStartPos + size; + return true; } } @@ -1562,7 +1589,7 @@ abstract class MatroskaTrackBacking implements InputTrackBacking { } } - while (metadataReader.pos < segment.elementEndPos) { + while (metadataReader.pos <= segment.elementEndPos - MIN_HEADER_SIZE) { if (prevCluster) { const trackData = prevCluster.trackData.get(this.internalTrack.id); if (trackData && trackData.startTimestamp > latestTimestamp) { @@ -1582,6 +1609,10 @@ abstract class MatroskaTrackBacking implements InputTrackBacking { await metadataReader.reader.loadRange(metadataReader.pos, metadataReader.pos + MAX_HEADER_SIZE); const elementStartPos = metadataReader.pos; const elementHeader = metadataReader.readElementHeader(); + if (!elementHeader) { + break; + } + const id = elementHeader.id; let size = elementHeader.size; const dataStartPos = metadataReader.pos;