Commit 4cbd8a1c for openh264
commit 4cbd8a1cb6e1a1825b481c4156bcf57535181c09
Author: BenzhengZhang <140143892+BenzhengZhang@users.noreply.github.com>
Date: Tue Aug 25 15:58:05 2026 +0800
decoder: re-derive stale thread picture before reference write (#3984)
In multi-threaded decode a resolution change tears down and reallocates the decoded-picture buffer, but the previous thread context's cached picture pointer (pLastThreadCtx->pDec) was left dangling. DecodeCurrentAccessUnit trusted the stale non-NULL pointer and wrote reference metadata through it, causing a heap use-after-free write.
Always re-derive the cached picture from the current picture buffer via the bounds-checked PrefetchLastPicForThread(), and NULL-check before use with a safe fallback that copies reference state without marking a freed picture as reference.
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 2be06c47..65df2067 100644
--- a/codec/decoder/core/src/decoder_core.cpp
+++ b/codec/decoder/core/src/decoder_core.cpp
@@ -2503,10 +2503,15 @@ int32_t DecodeCurrentAccessUnit (PWelsDecoderContext pCtx, uint8_t** ppDst, SBuf
PWelsDecoderThreadCTX pLastThreadCtx = NULL;
if (pCtx->pLastThreadCtx != NULL) {
pLastThreadCtx = (PWelsDecoderThreadCTX) (pCtx->pLastThreadCtx);
- if (pLastThreadCtx->pDec == NULL) {
- pLastThreadCtx->pDec = PrefetchLastPicForThread (pCtx->pPicBuff,
- pLastThreadCtx->iPicBuffIdx);
- }
+ // Always re-derive the cached picture from the current picture buffer. A
+ // previously cached non-NULL pDec can dangle into a DPB that was freed or
+ // reallocated on a resolution/sequence change; writing reference metadata
+ // through such a stale pointer would be a use-after-free.
+ // PrefetchLastPicForThread() bounds-checks the index against the current
+ // buffer and returns NULL when it no longer maps to a live picture.
+ pLastThreadCtx->pDec = (pCtx->pPicBuff != NULL)
+ ? PrefetchLastPicForThread (pCtx->pPicBuff, pLastThreadCtx->iPicBuffIdx)
+ : NULL;
}
int32_t iThreadCount = GetThreadCount (pCtx);
int32_t iPpsId = 0;
@@ -2544,19 +2549,26 @@ int32_t DecodeCurrentAccessUnit (PWelsDecoderContext pCtx, uint8_t** ppDst, SBuf
//this prevents from possible thread-decoding hanging
pCtx->pDec = PrefetchPic (pCtx->pPicBuff);
if (pLastThreadCtx != NULL) {
- pLastThreadCtx->pDec->bUsedAsRef = pLastThreadCtx->pCtx->uiNalRefIdc > 0;
- if (pLastThreadCtx->pDec->bUsedAsRef) {
- for (int32_t listIdx = LIST_0; listIdx < LIST_A; ++listIdx) {
- uint32_t i = 0;
- while (i < MAX_REF_PIC_COUNT && pLastThreadCtx->pCtx->sRefPic.pRefList[listIdx][i]) {
- pLastThreadCtx->pDec->pRefPic[listIdx][i] = pLastThreadCtx->pCtx->sRefPic.pRefList[listIdx][i];
- ++i;
+ if (pLastThreadCtx->pDec != NULL) {
+ pLastThreadCtx->pDec->bUsedAsRef = pLastThreadCtx->pCtx->uiNalRefIdc > 0;
+ if (pLastThreadCtx->pDec->bUsedAsRef) {
+ for (int32_t listIdx = LIST_0; listIdx < LIST_A; ++listIdx) {
+ uint32_t i = 0;
+ while (i < MAX_REF_PIC_COUNT && pLastThreadCtx->pCtx->sRefPic.pRefList[listIdx][i]) {
+ pLastThreadCtx->pDec->pRefPic[listIdx][i] = pLastThreadCtx->pCtx->sRefPic.pRefList[listIdx][i];
+ ++i;
+ }
}
+ pLastThreadCtx->pCtx->sTmpRefPic = pLastThreadCtx->pCtx->sRefPic;
+ WelsMarkAsRef (pLastThreadCtx->pCtx, pLastThreadCtx->pDec);
+ pCtx->sRefPic = pLastThreadCtx->pCtx->sTmpRefPic;
+ } else {
+ pCtx->sRefPic = pLastThreadCtx->pCtx->sRefPic;
}
- pLastThreadCtx->pCtx->sTmpRefPic = pLastThreadCtx->pCtx->sRefPic;
- WelsMarkAsRef (pLastThreadCtx->pCtx, pLastThreadCtx->pDec);
- pCtx->sRefPic = pLastThreadCtx->pCtx->sTmpRefPic;
} else {
+ // The cached last-thread picture no longer maps to a live buffer
+ // entry (DPB was freed/reallocated). Copy reference state without
+ // marking a stale picture as reference.
pCtx->sRefPic = pLastThreadCtx->pCtx->sRefPic;
}
}