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();
+}