From 3e9ae418d4938cf7c34bdb9f20911e5fac82bd9d Mon Sep 17 00:00:00 2001 From: Christopher Date: Tue, 31 Mar 2026 11:22:45 -0500 Subject: [PATCH] fix(h265): add bounds safety in mpeg4 codec parsing and remove dead code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DecodeConfig: add a minimum 28-byte length check before accessing conf[1:4] and conf[23:], plus per-section length guards before slicing each VPS/SPS/PPS region — a truncated HEVCDecoderConfigurationRecord previously caused an index-out-of-range panic. EncodeConfig: return nil early when vps/pps are empty or sps is shorter than 6 bytes; the existing code copied sps[3:6] unconditionally. rtp.go RTPDepay: remove the dead check `if nuStart > len(buf)+4`. nuStart is assigned from len(buf) before appending, so after at least 6 bytes are appended the condition is structurally always false and the buf[:0] reset that followed it never fired. The size field is written correctly by the PutUint32 on the next line regardless. --- pkg/h265/mpeg4.go | 28 ++++++++++++++++++++++------ pkg/h265/rtp.go | 6 ------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/pkg/h265/mpeg4.go b/pkg/h265/mpeg4.go index e06d08365..db3c357cb 100644 --- a/pkg/h265/mpeg4.go +++ b/pkg/h265/mpeg4.go @@ -10,33 +10,49 @@ import ( ) func DecodeConfig(conf []byte) (profile, vps, sps, pps []byte) { + // minimum: 23-byte header + 5-byte VPS prefix (no data) + if len(conf) < 28 { + return + } profile = conf[1:4] b := conf[23:] - if binary.BigEndian.Uint16(b[1:]) != 1 { + if len(b) < 5 || binary.BigEndian.Uint16(b[1:]) != 1 { + return + } + vpsSize := int(binary.BigEndian.Uint16(b[3:])) + if len(b) < 5+vpsSize { return } - vpsSize := binary.BigEndian.Uint16(b[3:]) vps = b[5 : 5+vpsSize] b = conf[23+5+vpsSize:] - if binary.BigEndian.Uint16(b[1:]) != 1 { + if len(b) < 5 || binary.BigEndian.Uint16(b[1:]) != 1 { + return + } + spsSize := int(binary.BigEndian.Uint16(b[3:])) + if len(b) < 5+spsSize { return } - spsSize := binary.BigEndian.Uint16(b[3:]) sps = b[5 : 5+spsSize] b = conf[23+5+vpsSize+5+spsSize:] - if binary.BigEndian.Uint16(b[1:]) != 1 { + if len(b) < 5 || binary.BigEndian.Uint16(b[1:]) != 1 { + return + } + ppsSize := int(binary.BigEndian.Uint16(b[3:])) + if len(b) < 5+ppsSize { return } - ppsSize := binary.BigEndian.Uint16(b[3:]) pps = b[5 : 5+ppsSize] return } func EncodeConfig(vps, sps, pps []byte) []byte { + if len(vps) == 0 || len(sps) < 6 || len(pps) == 0 { + return nil + } vpsSize := uint16(len(vps)) spsSize := uint16(len(sps)) ppsSize := uint16(len(pps)) diff --git a/pkg/h265/rtp.go b/pkg/h265/rtp.go index 9c571ec5f..e86fa143b 100644 --- a/pkg/h265/rtp.go +++ b/pkg/h265/rtp.go @@ -76,12 +76,6 @@ func RTPDepay(codec *core.Codec, handler core.HandlerFunc) core.HandlerFunc { buf = append(buf, data[3:]...) - if nuStart > len(buf)+4 { - //log.Printf("broken H265 fragment") - buf = buf[:0] // drop data - return - } - binary.BigEndian.PutUint32(buf[nuStart:], uint32(len(buf)-nuStart-4)) case 0b11: // wrong RFC 7798 realisation from OpenIPC project // A non-fragmented NAL unit MUST NOT be transmitted in one FU; i.e.,