Commit 562ffca1 for openh264
commit 562ffca1b6d694f5aa21ace9abfc8a12f554f2f4
Author: BenzhengZhang <140143892+BenzhengZhang@users.noreply.github.com>
Date: Mon Aug 31 16:54:55 2026 +0800
decoder: guard sRawData wrap against queued slice overwrite (#3993)
Co-authored-by: benzzhan <benzzhan@cisco.com>
diff --git a/codec/decoder/core/src/decoder.cpp b/codec/decoder/core/src/decoder.cpp
index 65da8a45..e3d36c26 100644
--- a/codec/decoder/core/src/decoder.cpp
+++ b/codec/decoder/core/src/decoder.cpp
@@ -738,6 +738,21 @@ void GetVclNalTemporalId (PWelsDecoderContext pCtx) {
* \note N/A
*************************************************************************************
*/
+// Returns false if wrapping sRawData to pHead would overwrite bytes still referenced
+// by a queued NAL's slice bit-reader, indicating the wrap is unsafe.
+static bool RawDataWrapIsClean (PWelsDecoderContext pCtx, int32_t iNewBytes) {
+ const PAccessUnit pAu = pCtx->pAccessUnitList;
+ if (pAu == NULL || pAu->pNalUnitsList == NULL || pAu->uiAvailUnitsNum == 0) return true;
+ const uint8_t* pHead = pCtx->sRawData.pHead;
+ for (uint32_t i = 0; i < pAu->uiAvailUnitsNum; ++i) {
+ const PNalUnit pNal = pAu->pNalUnitsList[i];
+ if (pNal == NULL) continue;
+ const uint8_t* pStart = pNal->sNalData.sVclNal.sSliceBitsRead.pStartBuf;
+ if (pStart != NULL && pStart >= pHead && pStart < pHead + iNewBytes) return false;
+ }
+ return true;
+}
+
int32_t WelsDecodeBs (PWelsDecoderContext pCtx, const uint8_t* kpBsBuf, const int32_t kiBsLen,
uint8_t** ppDst, SBufferInfo* pDstBufInfo, SParserBsInfo* pDstBsInfo) {
if (!pCtx->bEndOfStreamFlag) {
@@ -767,6 +782,10 @@ int32_t WelsDecodeBs (PWelsDecoderContext pCtx, const uint8_t* kpBsBuf, const in
iSrcLength = kiBsLen - iOffset;
if ((kiBsLen + 4) > (pRawData->pEnd - pRawData->pCurPos)) {
+ if (!RawDataWrapIsClean (pCtx, kiBsLen + 4)) {
+ pCtx->iErrorCode |= dsOutOfMemory;
+ return pCtx->iErrorCode;
+ }
pRawData->pCurPos = pRawData->pHead;
}
@@ -853,6 +872,10 @@ int32_t WelsDecodeBs (PWelsDecoderContext pCtx, const uint8_t* kpBsBuf, const in
pDstNal += (iDstIdx + 4); //init, increase 4 reserved zero bytes, used to store the next NAL
if ((iSrcLength - iSrcConsumed + 4) > (pRawData->pEnd - pDstNal)) {
+ if (!RawDataWrapIsClean (pCtx, iSrcLength - iSrcConsumed + 4)) {
+ pCtx->iErrorCode |= dsOutOfMemory;
+ return pCtx->iErrorCode;
+ }
pDstNal = pRawData->pCurPos = pRawData->pHead;
} else {
pRawData->pCurPos = pDstNal;
diff --git a/test/decoder/DecUT_ParseSyntax.cpp b/test/decoder/DecUT_ParseSyntax.cpp
index 93de13d7..1cd831ca 100644
--- a/test/decoder/DecUT_ParseSyntax.cpp
+++ b/test/decoder/DecUT_ParseSyntax.cpp
@@ -720,4 +720,49 @@ TEST (DecoderBitStreamBoundsTest, BsGetBitsStopsOnTwoByteOverread) {
EXPECT_EQ (ERR_INFO_READ_OVERFLOW, BsGetBits (&sBs, 16, &uiCode));
}
+// Regression: WelsDecodeBs must not wrap sRawData to pHead when queued VCL
+// NALs have sSliceBitsRead.pStartBuf pointing into the region that would be
+// overwritten by the incoming write. Pre-fix, the wrap was unconditional and
+// silently replaced queued slice bytes with the new input bytes while leaving
+// the saved bit-reader pointer unchanged.
+TEST_F (DecoderParseSyntaxTest, WelsDecodeBsRejectsWrapIntoQueuedSlice) {
+ ASSERT_EQ (ERR_NONE, Init());
+ ASSERT_TRUE (m_pCtx != NULL);
+ ASSERT_TRUE (m_pCtx->pAccessUnitList != NULL);
+ ASSERT_TRUE (m_pCtx->sRawData.pHead != NULL);
+
+ PAccessUnit pAu = m_pCtx->pAccessUnitList;
+ const int32_t kiBufSize = m_pCtx->iMaxBsBufferSizeInByte;
+
+ // Place a synthetic queued VCL slice bit-reader at offset 8 from pHead,
+ // within the region that a full-buffer wrap would overwrite.
+ PBitStringAux pBs = &pAu->pNalUnitsList[0]->sNalData.sVclNal.sSliceBitsRead;
+ pBs->pStartBuf = m_pCtx->sRawData.pHead + 8;
+ pBs->pCurBuf = m_pCtx->sRawData.pHead + 8;
+ pBs->pEndBuf = m_pCtx->sRawData.pHead + kiBufSize / 4;
+ pAu->uiAvailUnitsNum = 1;
+
+ // Position write cursor near pEnd so any new input forces a wrap.
+ m_pCtx->sRawData.pCurPos = m_pCtx->sRawData.pEnd - 3;
+
+ // Remember the bytes the queued slice points to before the wrap attempt.
+ uint8_t snapshot[8];
+ memcpy (snapshot, pBs->pStartBuf, sizeof (snapshot));
+ const uint8_t* pStartBefore = pBs->pStartBuf;
+
+ // Input large enough to trigger wrap check (kiBsLen + 4 > pEnd - pCurPos).
+ static const uint8_t kFiller[16] = {0, 0, 0, 1, 0x0c, 0x80, 0, 0, 0, 1, 0x0c, 0x80, 0, 0, 0, 1};
+ uint8_t* dst[3] = {NULL, NULL, NULL};
+ SBufferInfo dstInfo;
+ memset (&dstInfo, 0, sizeof (dstInfo));
+ m_pCtx->bEndOfStreamFlag = false;
+ WelsDecodeBs (m_pCtx, kFiller, static_cast<int32_t> (sizeof (kFiller)), dst, &dstInfo, NULL);
+
+ // The pointer must not have moved (no retarget happened).
+ EXPECT_EQ (pStartBefore, pBs->pStartBuf);
+ // The bytes at the queued slice start must be unchanged.
+ EXPECT_EQ (0, memcmp (snapshot, pBs->pStartBuf, sizeof (snapshot)));
+
+ Uninit();
+}