diff --git a/src/gpu/graphite/BufferManager.cpp b/src/gpu/graphite/BufferManager.cpp index bd72625150c..b11a794b419 100644 --- a/src/gpu/graphite/BufferManager.cpp +++ b/src/gpu/graphite/BufferManager.cpp @@ -35,6 +35,21 @@ namespace skgpu::graphite { +// POC fault injection: simulates findOrCreateBuffer() failure (GPU OOM) which +// is a real production failure path. When >0, decrements on each call and +// returns null when it reaches 0. This does NOT change the vulnerable code +// path; it only makes the OOM deterministic. +int gPocFailBufferAllocAfterN = 0; +int gPocBufferAllocCount = 0; +bool gPocLogAllocs = false; +// When true, transferToRecording's fMappingFailed branch additionally clears +// the leftover fUsedBuffers / fAvailableBuffer entries that were pushed by +// stack-local BufferSubAllocator destructors AFTER onFailedBuffer() ran. This +// neutralises a *sibling* stale-Buffer* bug (leftover fUsedBuffers -> +// CopyBufferToBufferTask with freed transfer-buffer ptr) so that the +// fUniformDataCache stale-pointer path can be observed in isolation. +bool gPocIsolateUniformCachePath = false; + namespace { // The limit for all data created by the StaticBufferManager. This data remains alive for @@ -325,6 +340,17 @@ DrawBufferManager::BufferState::BufferState(BufferType type, sk_sp DrawBufferManager::BufferState::findOrCreateBuffer(ResourceProvider* provider, Shareable shareable, uint32_t byteCount) { + // POC fault injection: simulate GPU OOM (real production failure mode). + int n = ++gPocBufferAllocCount; + if (gPocLogAllocs) { + SkDebugf("[POC] findOrCreateBuffer #%d type=%d size=%u label=%s\n", + n, (int)fType, byteCount, fLabel); + } + if (gPocFailBufferAllocAfterN > 0 && --gPocFailBufferAllocAfterN == 0) { + SkDebugf("[POC] *** Simulating buffer-alloc failure (GPU OOM) at #%d type=%d ***\n", + n, (int)fType); + return nullptr; + } if (shareable == Shareable::kScratch) { sk_sp scratchBuffer = provider->findOrCreateScratchBuffer( byteCount, fType, fAccessPattern, fLabel, fUnavailableScratchBuffers); @@ -389,6 +415,19 @@ void DrawBufferManager::onFailedBuffer() { bool DrawBufferManager::transferToRecording(Recording* recording) { if (fMappingFailed) { + if (gPocIsolateUniformCachePath) { + // POC: stack-local allocators (UniformTracker::fCurrentBuffer, + // DrawWriter) destruct AFTER onFailedBuffer() and re-populate + // fUsedBuffers / fAvailableBuffer with entries whose + // fTransferBuffer.fBuffer is a raw ptr that + // UploadBufferManager::transferToRecording is about to drop. This + // is a *separate* UAF that fires before the fUniformDataCache one; + // clear it here so the report's specific path can be observed. + for (auto& state : fCurrentBuffers) { + state.fAvailableBuffer = BufferSubAllocator(); + } + fUsedBuffers.clear(); + } // All state should have been reset by onFailedBuffer() except for this error flag. SkASSERT(fUsedBuffers.empty() && fClearList.empty()); #if defined(SK_DEBUG)