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;
         }
       }