D3D12: Mitigate the security issue for texture corruption

2D Array texture might corrupt on some devices, making out-of-bound
texture access and memory information leak from another texture.
This is a critical security issue.

This change aim at mitigating the security issue via allocating
sufficent extra memory for each texture allocation for 2D array
texture on such devices.

Bug: dawn:949

Change-Id: I3629eeb13be872b2107effa55539e5c24522d0fc
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/96220
Commit-Queue: Yunchao He <yunchao.he@intel.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/dawn/native/Toggles.cpp b/src/dawn/native/Toggles.cpp
index a04bfce..55ac82f 100644
--- a/src/dawn/native/Toggles.cpp
+++ b/src/dawn/native/Toggles.cpp
@@ -285,6 +285,13 @@
       "Toggle is enabled by default on the D3D12 platforms where CastingFullyTypedFormatSupported "
       "is false.",
       "https://crbug.com/dawn/1276"}},
+    {Toggle::D3D12AllocateExtraMemoryFor2DArrayTexture,
+     {"d3d12_allocate_extra_memory_for_2d_array_texture",
+      "Memory allocation for 2D array texture may be smaller than it should be on D3D12 on some "
+      "Intel devices. So texture access can be out-of-bound, which may cause critical security "
+      "issue. We can workaround this security issue via allocating extra memory and limiting its "
+      "access in itself.",
+      "https://crbug.com/dawn/949"}},
     // Comment to separate the }} so it is clearer what to copy-paste to add a toggle.
 }};
 }  // anonymous namespace
diff --git a/src/dawn/native/Toggles.h b/src/dawn/native/Toggles.h
index 77f7d07..32aa009 100644
--- a/src/dawn/native/Toggles.h
+++ b/src/dawn/native/Toggles.h
@@ -75,6 +75,7 @@
     D3D12ForceClearCopyableDepthStencilTextureOnCreation,
     D3D12DontSetClearValueOnDepthTextureCreation,
     D3D12AlwaysUseTypelessFormatsForCastableTexture,
+    D3D12AllocateExtraMemoryFor2DArrayTexture,
 
     EnumCount,
     InvalidEnum = EnumCount,
diff --git a/src/dawn/native/d3d12/DeviceD3D12.cpp b/src/dawn/native/d3d12/DeviceD3D12.cpp
index ef6baec..10846ba 100644
--- a/src/dawn/native/d3d12/DeviceD3D12.cpp
+++ b/src/dawn/native/d3d12/DeviceD3D12.cpp
@@ -673,6 +673,16 @@
     // But we may need to limit it if D3D12 runtime fixes the bug on its new release. See
     // https://crbug.com/dawn/1289 for more information.
     SetToggle(Toggle::D3D12SplitBufferTextureCopyForRowsPerImagePaddings, true);
+
+    // This workaround is only needed on Intel Gen12LP with driver prior to 30.0.101.1960.
+    // See http://crbug.com/dawn/949 for more information.
+    if (gpu_info::IsIntelXe(vendorId, deviceId)) {
+        const gpu_info::D3DDriverVersion version = {30, 0, 101, 1960};
+        if (gpu_info::CompareD3DDriverVersion(vendorId, ToBackend(GetAdapter())->GetDriverVersion(),
+                                              version) == -1) {
+            SetToggle(Toggle::D3D12AllocateExtraMemoryFor2DArrayTexture, true);
+        }
+    }
 }
 
 MaybeError Device::WaitForIdleForDestruction() {
diff --git a/src/dawn/native/d3d12/ResourceAllocatorManagerD3D12.cpp b/src/dawn/native/d3d12/ResourceAllocatorManagerD3D12.cpp
index 454b924..d1f8b26 100644
--- a/src/dawn/native/d3d12/ResourceAllocatorManagerD3D12.cpp
+++ b/src/dawn/native/d3d12/ResourceAllocatorManagerD3D12.cpp
@@ -24,6 +24,8 @@
 #include "dawn/native/d3d12/ResidencyManagerD3D12.h"
 #include "dawn/native/d3d12/UtilsD3D12.h"
 
+static constexpr uint32_t kExtraMemoryToMitigateTextureCorruption = 24576u;
+
 namespace dawn::native::d3d12 {
 namespace {
 MemorySegment GetMemorySegment(Device* device, D3D12_HEAP_TYPE heapType) {
@@ -311,6 +313,8 @@
             mDevice->GetD3D12Device()->GetResourceAllocationInfo(0, 1, &resourceDescriptor);
     }
 
+    resourceInfo.SizeInBytes += GetResourcePadding(resourceDescriptor);
+
     // If d3d tells us the resource size is invalid, treat the error as OOM.
     // Otherwise, creating the resource could cause a device loss (too large).
     // This is because NextPowerOfTwo(UINT64_MAX) overflows and proceeds to
@@ -333,6 +337,21 @@
 
     Heap* heap = ToBackend(allocation.GetResourceHeap());
 
+    ComPtr<ID3D12Resource> placedResource;
+    DAWN_TRY_ASSIGN(placedResource,
+                    CreatePlacedResourceInHeap(heap, allocation.GetOffset(), resourceDescriptor,
+                                               optimizedClearValue, initialUsage));
+    return ResourceHeapAllocation{allocation.GetInfo(), allocation.GetOffset(),
+                                  std::move(placedResource), heap};
+}
+
+ResultOrError<ComPtr<ID3D12Resource>> ResourceAllocatorManager::CreatePlacedResourceInHeap(
+    Heap* heap,
+    const uint64_t offset,
+    const D3D12_RESOURCE_DESC& resourceDescriptor,
+    const D3D12_CLEAR_VALUE* optimizedClearValue,
+    D3D12_RESOURCE_STATES initialUsage) {
+    ComPtr<ID3D12Resource> placedResource;
     // Before calling CreatePlacedResource, we must ensure the target heap is resident.
     // CreatePlacedResource will fail if it is not.
     DAWN_TRY(mDevice->GetResidencyManager()->LockAllocation(heap));
@@ -344,19 +363,16 @@
     // within the same command-list and does not require additional synchronization (aliasing
     // barrier).
     // https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12device-createplacedresource
-    ComPtr<ID3D12Resource> placedResource;
-    DAWN_TRY(CheckOutOfMemoryHRESULT(
-        mDevice->GetD3D12Device()->CreatePlacedResource(
-            heap->GetD3D12Heap(), allocation.GetOffset(), &resourceDescriptor, initialUsage,
-            optimizedClearValue, IID_PPV_ARGS(&placedResource)),
-        "ID3D12Device::CreatePlacedResource"));
+    DAWN_TRY(
+        CheckOutOfMemoryHRESULT(mDevice->GetD3D12Device()->CreatePlacedResource(
+                                    heap->GetD3D12Heap(), offset, &resourceDescriptor, initialUsage,
+                                    optimizedClearValue, IID_PPV_ARGS(&placedResource)),
+                                "ID3D12Device::CreatePlacedResource"));
 
     // After CreatePlacedResource has finished, the heap can be unlocked from residency. This
     // will insert it into the residency LRU.
     mDevice->GetResidencyManager()->UnlockAllocation(heap);
-
-    return ResourceHeapAllocation{allocation.GetInfo(), allocation.GetOffset(),
-                                  std::move(placedResource), heap};
+    return std::move(placedResource);
 }
 
 ResultOrError<ResourceHeapAllocation> ResourceAllocatorManager::CreateCommittedResource(
@@ -377,6 +393,10 @@
     // incorrectly allocate a mismatched size.
     D3D12_RESOURCE_ALLOCATION_INFO resourceInfo =
         mDevice->GetD3D12Device()->GetResourceAllocationInfo(0, 1, &resourceDescriptor);
+
+    uint64_t extraMemory = GetResourcePadding(resourceDescriptor);
+    resourceInfo.SizeInBytes += extraMemory;
+
     if (resourceInfo.SizeInBytes == 0 ||
         resourceInfo.SizeInBytes == std::numeric_limits<uint64_t>::max()) {
         return DAWN_OUT_OF_MEMORY_ERROR("Resource allocation size was invalid.");
@@ -395,11 +415,23 @@
     // Note: Heap flags are inferred by the resource descriptor and do not need to be explicitly
     // provided to CreateCommittedResource.
     ComPtr<ID3D12Resource> committedResource;
-    DAWN_TRY(CheckOutOfMemoryHRESULT(
-        mDevice->GetD3D12Device()->CreateCommittedResource(
-            &heapProperties, D3D12_HEAP_FLAG_NONE, &resourceDescriptor, initialUsage,
-            optimizedClearValue, IID_PPV_ARGS(&committedResource)),
-        "ID3D12Device::CreateCommittedResource"));
+    if (extraMemory > 0) {
+        const ResourceHeapKind resourceHeapKind = GetResourceHeapKind(
+            resourceDescriptor.Dimension, heapType, resourceDescriptor.Flags, mResourceHeapTier);
+        std::unique_ptr<ResourceHeapBase> heapBase;
+        DAWN_TRY_ASSIGN(heapBase, mPooledHeapAllocators[resourceHeapKind]->AllocateResourceHeap(
+                                      resourceInfo.SizeInBytes));
+        Heap* heap = ToBackend(heapBase.get());
+        DAWN_TRY_ASSIGN(committedResource,
+                        CreatePlacedResourceInHeap(heap, 0, resourceDescriptor, optimizedClearValue,
+                                                   initialUsage));
+    } else {
+        DAWN_TRY(CheckOutOfMemoryHRESULT(
+            mDevice->GetD3D12Device()->CreateCommittedResource(
+                &heapProperties, D3D12_HEAP_FLAG_NONE, &resourceDescriptor, initialUsage,
+                optimizedClearValue, IID_PPV_ARGS(&committedResource)),
+            "ID3D12Device::CreateCommittedResource"));
+    }
 
     // When using CreateCommittedResource, D3D12 creates an implicit heap that contains the
     // resource allocation. Because Dawn's memory residency management occurs at the resource
@@ -420,6 +452,17 @@
                                   /*offset*/ 0, std::move(committedResource), heap};
 }
 
+uint64_t ResourceAllocatorManager::GetResourcePadding(
+    const D3D12_RESOURCE_DESC& resourceDescriptor) const {
+    // If we are allocating memory for a 2D array texture on D3D12 backend, we need to allocate
+    // extra memory on some devices, see crbug.com/dawn/949 for details.
+    if (mDevice->IsToggleEnabled(Toggle::D3D12AllocateExtraMemoryFor2DArrayTexture) &&
+        resourceDescriptor.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE2D &&
+        resourceDescriptor.DepthOrArraySize > 1) {
+        return kExtraMemoryToMitigateTextureCorruption;
+    }
+    return 0;
+}
 void ResourceAllocatorManager::DestroyPool() {
     for (auto& alloc : mPooledHeapAllocators) {
         alloc->DestroyPool();
diff --git a/src/dawn/native/d3d12/ResourceAllocatorManagerD3D12.h b/src/dawn/native/d3d12/ResourceAllocatorManagerD3D12.h
index 5b6dfd8..7bb454d 100644
--- a/src/dawn/native/d3d12/ResourceAllocatorManagerD3D12.h
+++ b/src/dawn/native/d3d12/ResourceAllocatorManagerD3D12.h
@@ -86,6 +86,15 @@
         const D3D12_CLEAR_VALUE* optimizedClearValue,
         D3D12_RESOURCE_STATES initialUsage);
 
+    ResultOrError<ComPtr<ID3D12Resource>> CreatePlacedResourceInHeap(
+        Heap* heap,
+        const uint64_t offset,
+        const D3D12_RESOURCE_DESC& resourceDescriptor,
+        const D3D12_CLEAR_VALUE* optimizedClearValue,
+        D3D12_RESOURCE_STATES initialUsage);
+
+    uint64_t GetResourcePadding(const D3D12_RESOURCE_DESC& resourceDescriptor) const;
+
     Device* mDevice;
     uint32_t mResourceHeapTier;