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.
This commit is contained in:
L'électron rare
2026-05-18 15:53:44 +02:00
parent e7b3b63f5f
commit 630b4aa6c0
2 changed files with 35 additions and 0 deletions
@@ -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(
@@ -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]))
}
}