Commit a72a8466 for openh264
commit a72a846670406f2a52a9e2643a013147f315a253
Author: BenzhengZhang <140143892+BenzhengZhang@users.noreply.github.com>
Date: Fri Sep 4 16:01:20 2026 +0800
Revert "decoder: avoid threaded post-return writes to caller ppDst (#4001)" (#4003)
This reverts commit 5bfbf01cf85a00a4d02f3eecbbc268a116f940ce.
diff --git a/codec/decoder/core/inc/decoder_context.h b/codec/decoder/core/inc/decoder_context.h
index f1f83d25..4d9adebc 100644
--- a/codec/decoder/core/inc/decoder_context.h
+++ b/codec/decoder/core/inc/decoder_context.h
@@ -537,7 +537,6 @@ typedef struct tagSWelsDecThreadCtx {
void* threadCtxOwner;
uint8_t* kpSrc;
int32_t kiSrcLen;
- uint8_t* pDst[3];
uint8_t** ppDst;
SBufferInfo sDstInfo;
PPicture pDec;
diff --git a/codec/decoder/plus/src/welsDecoderExt.cpp b/codec/decoder/plus/src/welsDecoderExt.cpp
index b879651d..73ead68d 100644
--- a/codec/decoder/plus/src/welsDecoderExt.cpp
+++ b/codec/decoder/plus/src/welsDecoderExt.cpp
@@ -321,7 +321,6 @@ void CWelsDecoder::OpenDecoderThreads() {
m_pDecThrCtx[i].threadCtxOwner = this;
m_pDecThrCtx[i].kpSrc = NULL;
m_pDecThrCtx[i].kiSrcLen = 0;
- m_pDecThrCtx[i].pDst[0] = m_pDecThrCtx[i].pDst[1] = m_pDecThrCtx[i].pDst[2] = NULL;
m_pDecThrCtx[i].ppDst = NULL;
m_pDecThrCtx[i].pDec = NULL;
CREATE_EVENT (&m_pDecThrCtx[i].sImageReady, 1, 0, NULL);
@@ -1426,9 +1425,7 @@ int CWelsDecoder::ThreadDecodeFrameInternal (const unsigned char* kpSrc, const i
}
m_pDecThrCtx[signal].kpSrc = const_cast<uint8_t*> (kpSrc);
m_pDecThrCtx[signal].kiSrcLen = kiSrcLen;
- // Worker threads must not write into caller-owned ppDst after API returns.
- m_pDecThrCtx[signal].pDst[0] = m_pDecThrCtx[signal].pDst[1] = m_pDecThrCtx[signal].pDst[2] = NULL;
- m_pDecThrCtx[signal].ppDst = m_pDecThrCtx[signal].pDst;
+ m_pDecThrCtx[signal].ppDst = ppDst;
memcpy (&m_pDecThrCtx[signal].sDstInfo, pDstInfo, sizeof (SBufferInfo));
state = ParseAccessUnit (m_pDecThrCtx[signal]);
diff --git a/test/api/thread_decoder_test.cpp b/test/api/thread_decoder_test.cpp
index 4eddcff5..0f148fda 100644
--- a/test/api/thread_decoder_test.cpp
+++ b/test/api/thread_decoder_test.cpp
@@ -7,11 +7,6 @@
#include <string>
#include <vector>
-#if !defined(_WIN32)
-#include <sys/mman.h>
-#include <unistd.h>
-#endif
-
static void UpdateHashFromPlane (SHA1Context* ctx, const uint8_t* plane,
int width, int height, int stride) {
for (int i = 0; i < height; i++) {
@@ -476,81 +471,3 @@ TEST_F (ThreadDecoderReorderQueueRaceTest, BufferedPictureQueueDrainsAllFrames)
ASSERT_FALSE (HasFatalFailure());
EXPECT_EQ (iDecodedFrames_, 50);
}
-
-// Regression coverage for the threaded ppDst post-return write.
-// In threaded decode, DecodeFrameNoDelay() hands the caller-owned ppDst
-// pointer array to a worker thread; decoding continues asynchronously and can
-// write into ppDst after the API call has already returned. If the caller's
-// storage is short-lived (e.g. freed or unmapped right after the call), that
-// late write is a use-after-free. The worker now writes into thread-owned
-// storage only, so the caller's page can be unmapped immediately after each
-// call without a crash.
-//
-// Iterations are capped: a separate, already-tracked AU-list counter overflow
-// in ResetCurrentAccessUnit surfaces around iteration 13 on this input
-// regardless of this fix, so this test stays inside that bound to isolate the
-// ppDst behavior under test.
-TEST (ThreadDecoderSecurityTest, DecodeFrameNoDelayNoPostReturnWriteToCallerPpDst) {
-#if defined(_WIN32)
- GTEST_SKIP() << "mmap/munmap based lifetime stress is POSIX-only";
-#else
- ISVCDecoder* decoder = NULL;
- ASSERT_EQ (0, WelsCreateDecoder (&decoder));
- ASSERT_TRUE (decoder != NULL);
-
- SDecodingParam decParam;
- memset (&decParam, 0, sizeof (decParam));
- decParam.uiTargetDqLayer = UCHAR_MAX;
- decParam.eEcActiveIdc = ERROR_CON_SLICE_COPY;
- decParam.sVideoProperty.eVideoBsType = VIDEO_BITSTREAM_DEFAULT;
- int32_t iThreadCount = 2;
- decoder->SetOption (DECODER_OPTION_NUM_OF_THREADS, &iThreadCount);
- ASSERT_EQ (0, decoder->Initialize (&decParam));
-
- std::ifstream file ("res/BA_MW_D.264", std::ios::in | std::ios::binary);
- ASSERT_TRUE (file.is_open());
- std::vector<uint8_t> bitstream ((std::istreambuf_iterator<char> (file)), std::istreambuf_iterator<char>());
- ASSERT_FALSE (bitstream.empty());
-
- const size_t pageSize = static_cast<size_t> (sysconf (_SC_PAGESIZE));
- const size_t chunkSize = 1200;
- const int32_t kMaxIters = 12;
-
- for (size_t off = 0, iter = 0; off < bitstream.size() && iter < static_cast<size_t> (kMaxIters);
- off += chunkSize, ++iter) {
- void* page = mmap (NULL, pageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- ASSERT_NE (MAP_FAILED, page);
-
- unsigned char** ppDst = reinterpret_cast<unsigned char**> (page);
- memset (ppDst, 0, pageSize);
-
- SBufferInfo dstInfo;
- memset (&dstInfo, 0, sizeof (dstInfo));
- int32_t len = static_cast<int32_t> (std::min (chunkSize, bitstream.size() - off));
- DECODING_STATE rv = decoder->DecodeFrameNoDelay (bitstream.data() + off, len, ppDst, &dstInfo);
- // The 1200-byte offsets are not NAL/AU aligned, so bitstream-parsing-level
- // states (dsBitstreamError, dsDataErrorConcealed, ...) are expected with
- // ERROR_CON_SLICE_COPY; only logic-level failures indicate a real problem.
- EXPECT_EQ (0, rv & (dsInvalidArgument | dsInitialOptExpected | dsOutOfMemory));
-
- munmap (page, pageSize);
- usleep (5000);
- }
-
- // Drain pipelined in-flight frames before teardown so Uninitialize() does not
- // free decoder state while a worker thread is still reconstructing.
- uint8_t* dst[3] = {NULL, NULL, NULL};
- SBufferInfo info;
- int32_t endOfStream = 1;
- decoder->SetOption (DECODER_OPTION_END_OF_STREAM, &endOfStream);
- int32_t remaining = 0;
- decoder->GetOption (DECODER_OPTION_NUM_OF_FRAMES_REMAINING_IN_BUFFER, &remaining);
- for (int32_t i = 0; i < remaining; ++i) {
- memset (&info, 0, sizeof (info));
- decoder->FlushFrame (dst, &info);
- }
-
- decoder->Uninitialize();
- WelsDestroyDecoder (decoder);
-#endif
-}