From d9403a4167162e5e2431aa4b68c1fbc9f655391b Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 18 Apr 2024 16:36:39 +0200 Subject: [PATCH 1/3] GTiff: multi-threaded decoding: fix potential mutex deadlock --- frmts/gtiff/gtiffdataset_read.cpp | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/frmts/gtiff/gtiffdataset_read.cpp b/frmts/gtiff/gtiffdataset_read.cpp index e9f52d11f706..d4f4226527a4 100644 --- a/frmts/gtiff/gtiffdataset_read.cpp +++ b/frmts/gtiff/gtiffdataset_read.cpp @@ -349,7 +349,10 @@ CPLErr GTiffDataset::ReadCompressedData(const char *pszFormat, int nXOff, struct GTiffDecompressContext { - std::mutex oMutex{}; + // The mutex must be recursive because ThreadDecompressionFuncErrorHandler() + // which acquires the mutex can be called from a section where the mutex is + // already acquired. + std::recursive_mutex oMutex{}; bool bSuccess = true; std::vector aoErrors{}; @@ -416,7 +419,7 @@ static void CPL_STDCALL ThreadDecompressionFuncErrorHandler( { GTiffDecompressContext *psContext = static_cast(CPLGetErrorHandlerUserData()); - std::lock_guard oLock(psContext->oMutex); + std::lock_guard oLock(psContext->oMutex); psContext->aoErrors.emplace_back(eErr, eErrorNum, pszMsg); } @@ -495,7 +498,7 @@ static void CPL_STDCALL ThreadDecompressionFuncErrorHandler( if (psJob->nSize == 0) { { - std::lock_guard oLock(psContext->oMutex); + std::lock_guard oLock(psContext->oMutex); if (!psContext->bSuccess) return; } @@ -616,7 +619,7 @@ static void CPL_STDCALL ThreadDecompressionFuncErrorHandler( if (psContext->bHasPRead) { { - std::lock_guard oLock(psContext->oMutex); + std::lock_guard oLock(psContext->oMutex); if (!psContext->bSuccess) return; @@ -634,7 +637,7 @@ static void CPL_STDCALL ThreadDecompressionFuncErrorHandler( { if (!AllocInputBuffer()) { - std::lock_guard oLock(psContext->oMutex); + std::lock_guard oLock(psContext->oMutex); psContext->bSuccess = false; return; } @@ -647,7 +650,7 @@ static void CPL_STDCALL ThreadDecompressionFuncErrorHandler( static_cast(psJob->nSize), static_cast(psJob->nOffset)); - std::lock_guard oLock(psContext->oMutex); + std::lock_guard oLock(psContext->oMutex); psContext->bSuccess = false; return; } @@ -655,7 +658,7 @@ static void CPL_STDCALL ThreadDecompressionFuncErrorHandler( } else { - std::lock_guard oLock(psContext->oMutex); + std::lock_guard oLock(psContext->oMutex); if (!psContext->bSuccess) return; @@ -808,7 +811,7 @@ static void CPL_STDCALL ThreadDecompressionFuncErrorHandler( if (!bRet) { - std::lock_guard oLock(psContext->oMutex); + std::lock_guard oLock(psContext->oMutex); psContext->bSuccess = false; return; } @@ -1298,7 +1301,8 @@ CPLErr GTiffDataset::MultiThreadedRead(int nXOff, int nYOff, int nXSize, // false since we could have concurrent uses of the handle, // when when reading the TIFF TileOffsets / TileByteCounts // array - std::lock_guard oLock(sContext.oMutex); + std::lock_guard oLock( + sContext.oMutex); IsBlockAvailable(nBlockId, &asJobs[iJob].nOffset, &asJobs[iJob].nSize); @@ -1314,7 +1318,8 @@ CPLErr GTiffDataset::MultiThreadedRead(int nXOff, int nYOff, int nXSize, { if (nFileSize == 0) { - std::lock_guard oLock(sContext.oMutex); + std::lock_guard oLock( + sContext.oMutex); sContext.poHandle->Seek(0, SEEK_END); nFileSize = sContext.poHandle->Tell(); } @@ -1326,7 +1331,8 @@ CPLErr GTiffDataset::MultiThreadedRead(int nXOff, int nYOff, int nXSize, static_cast(asJobs[iJob].nSize), static_cast(asJobs[iJob].nOffset)); - std::lock_guard oLock(sContext.oMutex); + std::lock_guard oLock( + sContext.oMutex); sContext.bSuccess = false; break; } From c1209a579a44df18a8d6fb8b9663c599f17f7789 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 18 Apr 2024 17:01:49 +0200 Subject: [PATCH 2/3] Add VSIVirtualHandle::GetAdviseReadTotalBytesLimit() --- port/cpl_vsi_virtual.h | 17 +++++++++++++++++ port/cpl_vsil_curl.cpp | 26 +++++++++++++++++++++++--- port/cpl_vsil_curl_class.h | 2 ++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/port/cpl_vsi_virtual.h b/port/cpl_vsi_virtual.h index e2458e37371e..225e2eded076 100644 --- a/port/cpl_vsi_virtual.h +++ b/port/cpl_vsi_virtual.h @@ -87,6 +87,23 @@ struct CPL_DLL VSIVirtualHandle { } + /** Return the total maximum number of bytes that AdviseRead() can handle + * at once. + * + * Some AdviseRead() implementations may give up if the sum of the values + * in the panSizes[] array provided to AdviseRead() exceeds a limit. + * + * Callers might use that threshold to optimize the efficiency of + * AdviseRead(). + * + * A returned value of 0 indicates a unknown limit. + * @since GDAL 3.9 + */ + virtual size_t GetAdviseReadTotalBytesLimit() const + { + return 0; + } + virtual size_t Write(const void *pBuffer, size_t nSize, size_t nCount) = 0; int Printf(CPL_FORMAT_STRING(const char *pszFormat), ...) diff --git a/port/cpl_vsil_curl.cpp b/port/cpl_vsil_curl.cpp index c8d6640a7100..38d6140529bb 100644 --- a/port/cpl_vsil_curl.cpp +++ b/port/cpl_vsil_curl.cpp @@ -32,9 +32,10 @@ #include #include -#include +#include #include #include +#include #include "cpl_aws.h" #include "cpl_json.h" @@ -3153,6 +3154,21 @@ size_t VSICurlHandle::PRead(void *pBuffer, size_t nSize, return nRet; } +/************************************************************************/ +/* GetAdviseReadTotalBytesLimit() */ +/************************************************************************/ + +size_t VSICurlHandle::GetAdviseReadTotalBytesLimit() const +{ + return static_cast(std::min( + std::numeric_limits::max(), + // 100 MB + std::strtoull( + CPLGetConfigOption("CPL_VSIL_CURL_ADVISE_READ_TOTAL_BYTES_LIMIT", + "104857600"), + nullptr, 10))); +} + /************************************************************************/ /* AdviseRead() */ /************************************************************************/ @@ -3171,9 +3187,10 @@ void VSICurlHandle::AdviseRead(int nRanges, const vsi_l_offset *panOffsets, // Give up if we need to allocate too much memory vsi_l_offset nMaxSize = 0; + const size_t nLimit = GetAdviseReadTotalBytesLimit(); for (int i = 0; i < nRanges; ++i) { - if (panSizes[i] > 100 * 1024 * 1024 - nMaxSize) + if (panSizes[i] > nLimit - nMaxSize) { CPLDebug(poFS->GetDebugKey(), "Trying to request too many bytes in AdviseRead()"); @@ -3994,7 +4011,10 @@ const char *VSICurlFilesystemHandlerBase::GetActualURL(const char *pszFilename) "default='16384000'/>" \ "