Commit 772e63d3 for openh264
commit 772e63d37344dc9159aec9ce1b97708c48b154dd
Author: BenzhengZhang <140143892+BenzhengZhang@users.noreply.github.com>
Date: Tue Aug 25 16:42:10 2026 +0800
security: fix ExpandBsBuffer queued retarget UAF (SPARK-838034) (#3985)
Co-authored-by: benzzhan <benzzhan@cisco.com>
diff --git a/codec/decoder/core/src/decoder_core.cpp b/codec/decoder/core/src/decoder_core.cpp
index 65df2067..3bde051c 100644
--- a/codec/decoder/core/src/decoder_core.cpp
+++ b/codec/decoder/core/src/decoder_core.cpp
@@ -661,9 +661,16 @@ int32_t ExpandBsBuffer (PWelsDecoderContext pCtx, const int kiSrcLen) {
return ERR_INFO_OUT_OF_MEMORY;
}
- //Calculate and set the bs start and end position
- for (uint32_t i = 0; i <= pCtx->pAccessUnitList->uiActualUnitsNum; i++) {
- PBitStringAux pSliceBitsRead = &pCtx->pAccessUnitList->pNalUnitsList[i]->sNalData.sVclNal.sSliceBitsRead;
+ // Retarget all queued NAL units in current AU list. uiAvailUnitsNum is the
+ // queued-NAL count that can be consumed later; using uiActualUnitsNum here
+ // can leave queued entries pointing to freed old buffer.
+ const uint32_t kuiRetargetNum = pCtx->pAccessUnitList->uiAvailUnitsNum;
+ for (uint32_t i = 0; i < kuiRetargetNum; ++i) {
+ PNalUnit pNal = pCtx->pAccessUnitList->pNalUnitsList[i];
+ if (pNal == NULL) {
+ continue;
+ }
+ PBitStringAux pSliceBitsRead = &pNal->sNalData.sVclNal.sSliceBitsRead;
pSliceBitsRead->pStartBuf = pSliceBitsRead->pStartBuf - pCtx->sRawData.pHead + pNewBsBuff;
pSliceBitsRead->pEndBuf = pSliceBitsRead->pEndBuf - pCtx->sRawData.pHead + pNewBsBuff;
pSliceBitsRead->pCurBuf = pSliceBitsRead->pCurBuf - pCtx->sRawData.pHead + pNewBsBuff;
diff --git a/test/decoder/DecUT_ParseSyntax.cpp b/test/decoder/DecUT_ParseSyntax.cpp
index ea2f076a..b19fee0f 100644
--- a/test/decoder/DecUT_ParseSyntax.cpp
+++ b/test/decoder/DecUT_ParseSyntax.cpp
@@ -601,4 +601,54 @@ TEST (DecoderReorderingBufferTest, FullResetInitializesPicBuffIdx) {
EXPECT_EQ (0, sStatus.iLargestBufferedPicIndex);
}
+TEST_F (DecoderParseSyntaxTest, ExpandBsBufferRetargetsQueuedNalUnitsOnly) {
+ ASSERT_EQ (ERR_NONE, Init());
+ ASSERT_TRUE (m_pCtx != NULL);
+ ASSERT_TRUE (m_pCtx->pAccessUnitList != NULL);
+
+ PAccessUnit pAu = m_pCtx->pAccessUnitList;
+ ASSERT_TRUE (pAu->uiCountUnitsNum >= 3);
+ ASSERT_TRUE (m_pCtx->sRawData.pHead != NULL);
+
+ uint8_t* pOldHead = m_pCtx->sRawData.pHead;
+ PBitStringAux pActual = &pAu->pNalUnitsList[0]->sNalData.sVclNal.sSliceBitsRead;
+ pActual->pStartBuf = pOldHead + 8;
+ pActual->pCurBuf = pOldHead + 12;
+ pActual->pEndBuf = pOldHead + 16;
+
+ PBitStringAux pQueued = &pAu->pNalUnitsList[1]->sNalData.sVclNal.sSliceBitsRead;
+ pQueued->pStartBuf = pOldHead + 24;
+ pQueued->pCurBuf = pOldHead + 28;
+ pQueued->pEndBuf = pOldHead + 32;
+
+ PBitStringAux pOnePastAvail = &pAu->pNalUnitsList[2]->sNalData.sVclNal.sSliceBitsRead;
+ pOnePastAvail->pStartBuf = pOldHead + 40;
+ pOnePastAvail->pCurBuf = pOldHead + 44;
+ pOnePastAvail->pEndBuf = pOldHead + 48;
+ uint8_t* pOnePastStartBefore = pOnePastAvail->pStartBuf;
+ uint8_t* pOnePastCurBefore = pOnePastAvail->pCurBuf;
+ uint8_t* pOnePastEndBefore = pOnePastAvail->pEndBuf;
+
+ pAu->uiAvailUnitsNum = 2;
+ pAu->uiActualUnitsNum = 1; // queued count is still 2, so index 1 must be retargeted
+
+ ASSERT_EQ (ERR_NONE, ExpandBsBuffer (m_pCtx, m_pCtx->iMaxBsBufferSizeInByte));
+ ASSERT_TRUE (m_pCtx->sRawData.pHead != pOldHead);
+
+ EXPECT_EQ (m_pCtx->sRawData.pHead + 8, pActual->pStartBuf);
+ EXPECT_EQ (m_pCtx->sRawData.pHead + 12, pActual->pCurBuf);
+ EXPECT_EQ (m_pCtx->sRawData.pHead + 16, pActual->pEndBuf);
+
+ EXPECT_EQ (m_pCtx->sRawData.pHead + 24, pQueued->pStartBuf);
+ EXPECT_EQ (m_pCtx->sRawData.pHead + 28, pQueued->pCurBuf);
+ EXPECT_EQ (m_pCtx->sRawData.pHead + 32, pQueued->pEndBuf);
+
+ // Slot at index uiAvailUnitsNum (one-past queued range) must not be touched.
+ EXPECT_EQ (pOnePastStartBefore, pOnePastAvail->pStartBuf);
+ EXPECT_EQ (pOnePastCurBefore, pOnePastAvail->pCurBuf);
+ EXPECT_EQ (pOnePastEndBefore, pOnePastAvail->pEndBuf);
+
+ Uninit();
+}
+