From 630b4aa6c0c93b4bb4ce600cf35d3e8ba568a217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=27=C3=A9lectron=20rare?= <108685187+electron-rare@users.noreply.github.com> Date: Mon, 18 May 2026 15:53:44 +0200 Subject: [PATCH] fix(avlivewire): cap demuxer payload length A corrupt header with a huge UInt32 length made feed buffer forever waiting for bytes that never arrive. Add an 8 MB max payload cap; a header exceeding it is treated as corrupt, its magic is skipped, and the demuxer resyncs on the next frame. --- .../Sources/AVLiveWire/StreamDemuxer.swift | 10 ++++++++ .../AVLiveWireTests/StreamDemuxerTests.swift | 25 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/shared/AVLiveWire/Sources/AVLiveWire/StreamDemuxer.swift b/shared/AVLiveWire/Sources/AVLiveWire/StreamDemuxer.swift index 1ab7378..2a263bd 100644 --- a/shared/AVLiveWire/Sources/AVLiveWire/StreamDemuxer.swift +++ b/shared/AVLiveWire/Sources/AVLiveWire/StreamDemuxer.swift @@ -6,6 +6,10 @@ public struct StreamDemuxer { public let payload: Data } + /// Largest plausible payload (8 MB) — comfortably covers any HEVC + /// access unit. A header claiming more is treated as corrupt. + public static let maxPayloadLength: UInt32 = 8 * 1024 * 1024 + private var buffer = Data() public init() {} @@ -24,6 +28,12 @@ public struct StreamDemuxer { if start > 0 { buffer.removeFirst(start) } guard buffer.count >= FrameHeader.byteCount, let h = FrameHeader(decoding: buffer) else { break } + if h.length > Self.maxPayloadLength { + // Implausible length — corrupt header; skip the magic + // and resync on the next one. + buffer.removeFirst(FrameHeader.magic.count) + continue + } let total = FrameHeader.byteCount + Int(h.length) guard buffer.count >= total else { break } let payloadStart = buffer.index( diff --git a/shared/AVLiveWire/Tests/AVLiveWireTests/StreamDemuxerTests.swift b/shared/AVLiveWire/Tests/AVLiveWireTests/StreamDemuxerTests.swift index f7d3656..ecb23dc 100644 --- a/shared/AVLiveWire/Tests/AVLiveWireTests/StreamDemuxerTests.swift +++ b/shared/AVLiveWire/Tests/AVLiveWireTests/StreamDemuxerTests.swift @@ -38,4 +38,29 @@ final class StreamDemuxerTests: XCTestCase { XCTAssertEqual(out.count, 1) XCTAssertEqual(out[0].payload, Data([7])) } + + func testPartialMagicAtBoundary() { + var d = StreamDemuxer() + XCTAssertTrue(d.feed(Data([0x41, 0x56, 0x4C])).isEmpty) + let payload = Data([42]) + let h = FrameHeader(tag: .meta, pid: 0, timestamp: 0, + length: UInt32(payload.count)) + var rest = Data([0x31]) + rest.append(h.encoded().dropFirst(4)) + rest.append(payload) + let out = d.feed(rest) + XCTAssertEqual(out.count, 1) + XCTAssertEqual(out[0].payload, payload) + } + + func testSkipsCorruptOversizedLength() { + var d = StreamDemuxer() + // Header claiming a 4 GB payload, then a valid frame after. + let bad = FrameHeader(tag: .video, pid: 0, timestamp: 0, + length: UInt32.max).encoded() + let good = frame(.video, Data([5, 5])) + let out = d.feed(bad + good) + XCTAssertEqual(out.count, 1) + XCTAssertEqual(out[0].payload, Data([5, 5])) + } }