1 /*------------------------------------------------------------------------
2 * Vulkan Conformance Tests
3 * ------------------------
4 *
5 * Copyright (c) 2020 The Khronos Group Inc.
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 *//*!
20 * \file
21 * \brief Experimental crash postmortem use after free tests
22 *//*--------------------------------------------------------------------*/
23
24 #include "vktTestCase.hpp"
25 #include "vktTestCaseUtil.hpp"
26 #include "vktTestGroupUtil.hpp"
27 #include "vktCustomInstancesDevices.hpp"
28 #include "vktPostmortemTests.hpp"
29 #include "vktPostmortemUseAfterFreeTests.hpp"
30
31 #include "vkDefs.hpp"
32 #include "vkRef.hpp"
33 #include "vkRefUtil.hpp"
34 #include "vkPlatform.hpp"
35 #include "vkPrograms.hpp"
36 #include "vkRefUtil.hpp"
37 #include "vkMemUtil.hpp"
38 #include "vkBarrierUtil.hpp"
39 #include "vkQueryUtil.hpp"
40 #include "vkBuilderUtil.hpp"
41 #include "vkTypeUtil.hpp"
42 #include "vkDeviceUtil.hpp"
43 #include "vkCmdUtil.hpp"
44 #include "vkObjUtil.hpp"
45 #include "vkBufferWithMemory.hpp"
46
47 #include "tcuCommandLine.hpp"
48 #include "tcuTestLog.hpp"
49
50 #include "deStringUtil.hpp"
51 #include "deUniquePtr.hpp"
52 #include "deRandom.hpp"
53 #include "vktPostmortemUtil.hpp"
54
55 #include <vector>
56 #include <memory>
57
58 using namespace vk;
59
60 namespace vkt
61 {
62 namespace postmortem
63 {
64 namespace
65 {
66
67 enum BufferType
68 {
69 BUFFER_TYPE_UNIFORM = 0,
70 BUFFER_TYPE_SSBO,
71 };
72
73 class Buffer
74 {
75 public:
76 Buffer (const vk::DeviceInterface& vk,
77 const vk::VkDevice device,
78 vk::Allocator& allocator,
79 const vk::VkBufferCreateInfo& bufferCreateInfo,
80 const vk::MemoryRequirement memoryRequirement);
81
get(void) const82 const vk::VkBuffer& get (void) const { return *m_buffer; }
operator *(void) const83 const vk::VkBuffer& operator* (void) const { return get(); }
getAllocation(void) const84 vk::Allocation& getAllocation (void) const { return *m_allocation; }
85
freeAllocation(void)86 void freeAllocation (void) { delete m_allocation.release(); }
87
88 private:
89 de::MovePtr<vk::Allocation> m_allocation;
90 vk::Move<vk::VkBuffer> m_buffer;
91
92 Buffer(const Buffer&); // "deleted"
93 Buffer& operator= (const Buffer&);
94 };
95
Buffer(const DeviceInterface & vk,const VkDevice device,Allocator & allocator,const VkBufferCreateInfo & bufferCreateInfo,const MemoryRequirement memoryRequirement)96 Buffer::Buffer(const DeviceInterface& vk,
97 const VkDevice device,
98 Allocator& allocator,
99 const VkBufferCreateInfo& bufferCreateInfo,
100 const MemoryRequirement memoryRequirement)
101 {
102 m_buffer = createBuffer(vk, device, &bufferCreateInfo);
103 m_allocation = allocator.allocate(getBufferMemoryRequirements(vk, device, *m_buffer), memoryRequirement);
104 VK_CHECK(vk.bindBufferMemory(device, *m_buffer, m_allocation->getMemory(), m_allocation->getOffset()));
105 }
106
107
108 class UseAfterFreeTestCase : public vkt::TestCase
109 {
110 public:
111 void initPrograms (vk::SourceCollections& sourceCollections) const;
112 TestInstance* createInstance (Context& context) const;
113
114 static UseAfterFreeTestCase* UBOToSSBOInvertCase (tcu::TestContext& testCtx,
115 const std::string& name,
116 const deUint32 numValues,
117 const tcu::IVec3& localSize,
118 const tcu::IVec3& workSize);
119
120 static UseAfterFreeTestCase* CopyInvertSSBOCase (tcu::TestContext& testCtx,
121 const std::string& name,
122 const deUint32 numValues,
123 const tcu::IVec3& localSize,
124 const tcu::IVec3& workSize);
125
126 private:
127 UseAfterFreeTestCase (tcu::TestContext& testCtx,
128 const std::string& name,
129 const deUint32 numValues,
130 const tcu::IVec3& localSize,
131 const tcu::IVec3& workSize,
132 const BufferType bufferType);
133
134 const BufferType m_bufferType;
135 const deUint32 m_numValues;
136 const tcu::IVec3 m_localSize;
137 const tcu::IVec3 m_workSize;
138 };
139
140 class UseAfterFreeTestInstance : public PostmortemTestInstance
141 {
142 public:
143 UseAfterFreeTestInstance(Context& context,
144 const deUint32 numValues,
145 const tcu::IVec3& localSize,
146 const tcu::IVec3& workSize,
147 const BufferType bufferType);
148
149 tcu::TestStatus iterate (void);
150
151 private:
152 const BufferType m_bufferType;
153 const deUint32 m_numValues;
154 const tcu::IVec3 m_localSize;
155 const tcu::IVec3 m_workSize;
156 };
157
158 template<typename T, int size>
multiplyComponents(const tcu::Vector<T,size> & v)159 T multiplyComponents(const tcu::Vector<T, size>& v)
160 {
161 T accum = 1;
162 for (int i = 0; i < size; ++i)
163 accum *= v[i];
164 return accum;
165 }
166
UseAfterFreeTestCase(tcu::TestContext & testCtx,const std::string & name,const deUint32 numValues,const tcu::IVec3 & localSize,const tcu::IVec3 & workSize,const BufferType bufferType)167 UseAfterFreeTestCase::UseAfterFreeTestCase (tcu::TestContext& testCtx,
168 const std::string& name,
169 const deUint32 numValues,
170 const tcu::IVec3& localSize,
171 const tcu::IVec3& workSize,
172 const BufferType bufferType)
173 : TestCase (testCtx, name)
174 , m_bufferType (bufferType)
175 , m_numValues (numValues)
176 , m_localSize (localSize)
177 , m_workSize (workSize)
178 {
179 DE_ASSERT(m_numValues % (multiplyComponents(m_workSize) * multiplyComponents(m_localSize)) == 0);
180 DE_ASSERT(m_bufferType == BUFFER_TYPE_UNIFORM || m_bufferType == BUFFER_TYPE_SSBO);
181 }
182
UBOToSSBOInvertCase(tcu::TestContext & testCtx,const std::string & name,const deUint32 numValues,const tcu::IVec3 & localSize,const tcu::IVec3 & workSize)183 UseAfterFreeTestCase* UseAfterFreeTestCase::UBOToSSBOInvertCase (tcu::TestContext& testCtx,
184 const std::string& name,
185 const deUint32 numValues,
186 const tcu::IVec3& localSize,
187 const tcu::IVec3& workSize)
188 {
189 return new UseAfterFreeTestCase(testCtx, name, numValues, localSize, workSize, BUFFER_TYPE_UNIFORM);
190 }
191
CopyInvertSSBOCase(tcu::TestContext & testCtx,const std::string & name,const deUint32 numValues,const tcu::IVec3 & localSize,const tcu::IVec3 & workSize)192 UseAfterFreeTestCase* UseAfterFreeTestCase::CopyInvertSSBOCase (tcu::TestContext& testCtx,
193 const std::string& name,
194 const deUint32 numValues,
195 const tcu::IVec3& localSize,
196 const tcu::IVec3& workSize)
197 {
198 return new UseAfterFreeTestCase(testCtx, name, numValues, localSize, workSize, BUFFER_TYPE_SSBO);
199 }
200
initPrograms(SourceCollections & sourceCollections) const201 void UseAfterFreeTestCase::initPrograms (SourceCollections& sourceCollections) const
202 {
203 std::ostringstream src;
204 if (m_bufferType == BUFFER_TYPE_UNIFORM)
205 {
206 src << "#version 310 es\n"
207 << "layout (local_size_x = " << m_localSize.x() << ", local_size_y = " << m_localSize.y() << ", local_size_z = " << m_localSize.z() << ") in;\n"
208 << "layout(binding = 0) readonly uniform Input {\n"
209 << " uint values[" << m_numValues << "];\n"
210 << "} ub_in;\n"
211 << "layout(binding = 1, std140) writeonly buffer Output {\n"
212 << " uint values[" << m_numValues << "];\n"
213 << "} sb_out;\n"
214 << "void main (void) {\n"
215 << " uvec3 size = gl_NumWorkGroups * gl_WorkGroupSize;\n"
216 << " uint numValuesPerInv = uint(ub_in.values.length()) / (size.x*size.y*size.z);\n"
217 << " uint groupNdx = size.x*size.y*gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + gl_GlobalInvocationID.x;\n"
218 << " uint offset = numValuesPerInv*groupNdx;\n"
219 << "\n"
220 << " for (uint ndx = 0u; ndx < numValuesPerInv; ndx++)\n"
221 << " sb_out.values[offset + ndx] = ~ub_in.values[offset + ndx];\n"
222 << "}\n";
223 }
224 else if (m_bufferType == BUFFER_TYPE_SSBO)
225 {
226 src << "#version 310 es\n"
227 << "layout (local_size_x = " << m_localSize.x() << ", local_size_y = " << m_localSize.y() << ", local_size_z = " << m_localSize.z() << ") in;\n"
228 << "layout(binding = 0, std140) readonly buffer Input {\n"
229 << " uint values[" << m_numValues << "];\n"
230 << "} sb_in;\n"
231 << "layout (binding = 1, std140) writeonly buffer Output {\n"
232 << " uint values[" << m_numValues << "];\n"
233 << "} sb_out;\n"
234 << "void main (void) {\n"
235 << " uvec3 size = gl_NumWorkGroups * gl_WorkGroupSize;\n"
236 << " uint numValuesPerInv = uint(sb_in.values.length()) / (size.x*size.y*size.z);\n"
237 << " uint groupNdx = size.x*size.y*gl_GlobalInvocationID.z + size.x*gl_GlobalInvocationID.y + gl_GlobalInvocationID.x;\n"
238 << " uint offset = numValuesPerInv*groupNdx;\n"
239 << "\n"
240 << " for (uint ndx = 0u; ndx < numValuesPerInv; ndx++)\n"
241 << " sb_out.values[offset + ndx] = ~sb_in.values[offset + ndx];\n"
242 << "}\n";
243 }
244
245 sourceCollections.glslSources.add("comp") << glu::ComputeSource(src.str());
246 }
247
createInstance(Context & context) const248 TestInstance* UseAfterFreeTestCase::createInstance(Context& context) const
249 {
250 return new UseAfterFreeTestInstance(context, m_numValues, m_localSize, m_workSize, m_bufferType);
251 }
252
UseAfterFreeTestInstance(Context & context,const deUint32 numValues,const tcu::IVec3 & localSize,const tcu::IVec3 & workSize,const BufferType bufferType)253 UseAfterFreeTestInstance::UseAfterFreeTestInstance (Context& context,
254 const deUint32 numValues,
255 const tcu::IVec3& localSize,
256 const tcu::IVec3& workSize,
257 const BufferType bufferType)
258 : PostmortemTestInstance(context)
259 , m_bufferType(bufferType)
260 , m_numValues(numValues)
261 , m_localSize(localSize)
262 , m_workSize(workSize)
263 {
264
265 }
266
iterate(void)267 tcu::TestStatus UseAfterFreeTestInstance::iterate(void)
268 {
269 const VkDevice device = *m_logicalDevice;
270 const DeviceInterface& vk = m_deviceDriver;
271 const VkQueue queue = m_queue;
272 const deUint32 queueFamilyIndex = m_queueFamilyIndex;
273 Allocator& allocator = m_allocator;
274
275 // Customize the test based on buffer type
276
277 const VkBufferUsageFlags inputBufferUsageFlags = (m_bufferType == BUFFER_TYPE_UNIFORM ? VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT : VK_BUFFER_USAGE_STORAGE_BUFFER_BIT);
278 const VkDescriptorType inputBufferDescriptorType = (m_bufferType == BUFFER_TYPE_UNIFORM ? VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER : VK_DESCRIPTOR_TYPE_STORAGE_BUFFER);
279 const deUint32 randomSeed = (m_bufferType == BUFFER_TYPE_UNIFORM ? 0x111223f : 0x124fef);
280
281 // Create an input buffer
282
283 const VkDeviceSize bufferSizeBytes = sizeof(tcu::UVec4) * m_numValues;
284 Buffer inputBuffer(vk, device, allocator, makeBufferCreateInfo(bufferSizeBytes, inputBufferUsageFlags), MemoryRequirement::HostVisible);
285
286 // Fill the input buffer with data
287 {
288 de::Random rnd(randomSeed);
289 const Allocation& inputBufferAllocation = inputBuffer.getAllocation();
290 tcu::UVec4* bufferPtr = static_cast<tcu::UVec4*>(inputBufferAllocation.getHostPtr());
291 for (deUint32 i = 0; i < m_numValues; ++i)
292 bufferPtr[i].x() = rnd.getUint32();
293
294 flushAlloc(vk, device, inputBufferAllocation);
295 }
296
297 // Create an output buffer
298
299 Buffer outputBuffer(vk, device, allocator, makeBufferCreateInfo(bufferSizeBytes, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT), MemoryRequirement::HostVisible);
300
301 // Create descriptor set
302
303 const Unique<VkDescriptorSetLayout> descriptorSetLayout(
304 DescriptorSetLayoutBuilder()
305 .addSingleBinding(inputBufferDescriptorType, VK_SHADER_STAGE_COMPUTE_BIT)
306 .addSingleBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_COMPUTE_BIT)
307 .build(vk, device));
308
309 const Unique<VkDescriptorPool> descriptorPool(
310 DescriptorPoolBuilder()
311 .addType(inputBufferDescriptorType)
312 .addType(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER)
313 .build(vk, device, VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, 1u));
314
315 const Unique<VkDescriptorSet> descriptorSet(makeDescriptorSet(vk, device, *descriptorPool, *descriptorSetLayout));
316
317 const VkDescriptorBufferInfo inputBufferDescriptorInfo = makeDescriptorBufferInfo(*inputBuffer, 0ull, bufferSizeBytes);
318 const VkDescriptorBufferInfo outputBufferDescriptorInfo = makeDescriptorBufferInfo(*outputBuffer, 0ull, bufferSizeBytes);
319 DescriptorSetUpdateBuilder()
320 .writeSingle(*descriptorSet, DescriptorSetUpdateBuilder::Location::binding(0u), inputBufferDescriptorType, &inputBufferDescriptorInfo)
321 .writeSingle(*descriptorSet, DescriptorSetUpdateBuilder::Location::binding(1u), VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, &outputBufferDescriptorInfo)
322 .update(vk, device);
323
324 // Perform the computation
325
326 const Unique<VkShaderModule> shaderModule(createShaderModule(vk, device, m_context.getBinaryCollection().get("comp"), 0u));
327 const Unique<VkPipelineLayout> pipelineLayout(makePipelineLayout(vk, device, *descriptorSetLayout));
328 const Unique<VkPipeline> pipeline(makeComputePipeline(vk, device, *pipelineLayout, *shaderModule));
329
330 const VkBufferMemoryBarrier hostWriteBarrier = makeBufferMemoryBarrier(VK_ACCESS_HOST_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, *inputBuffer, 0ull, bufferSizeBytes);
331
332 const VkBufferMemoryBarrier shaderWriteBarrier = makeBufferMemoryBarrier(VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_HOST_READ_BIT, *outputBuffer, 0ull, bufferSizeBytes);
333
334 const Unique<VkCommandPool> cmdPool(makeCommandPool(vk, device, queueFamilyIndex));
335 const Unique<VkCommandBuffer> cmdBuffer(allocateCommandBuffer(vk, device, *cmdPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY));
336
337 // Start recording commands
338
339 beginCommandBuffer(vk, *cmdBuffer);
340
341 vk.cmdBindPipeline(*cmdBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
342 vk.cmdBindDescriptorSets(*cmdBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, *pipelineLayout, 0u, 1u, &descriptorSet.get(), 0u, DE_NULL);
343
344 vk.cmdPipelineBarrier(*cmdBuffer, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, (VkDependencyFlags)0, 0, (const VkMemoryBarrier*)DE_NULL, 1, &hostWriteBarrier, 0, (const VkImageMemoryBarrier*)DE_NULL);
345 vk.cmdDispatch(*cmdBuffer, m_workSize.x(), m_workSize.y(), m_workSize.z());
346 vk.cmdPipelineBarrier(*cmdBuffer, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_HOST_BIT, (VkDependencyFlags)0, 0, (const VkMemoryBarrier*)DE_NULL, 1, &shaderWriteBarrier, 0, (const VkImageMemoryBarrier*)DE_NULL);
347
348 endCommandBuffer(vk, *cmdBuffer);
349
350 // Free the memory backing the buffer
351 inputBuffer.freeAllocation();
352 outputBuffer.freeAllocation();
353
354 // Wait for completion
355 submitCommandsAndWait(vk, device, queue, *cmdBuffer);
356
357 // Pointers are invalid, so nothing to verify
358 return tcu::TestStatus::pass("Test succeeded without device loss");
359 }
360 }
361
createUseAfterFreeTests(tcu::TestContext & testCtx)362 tcu::TestCaseGroup* createUseAfterFreeTests(tcu::TestContext& testCtx)
363 {
364 // Use buffer after free.
365 de::MovePtr<tcu::TestCaseGroup> useAfterFreeGroup(new tcu::TestCaseGroup(testCtx, "use_after_free"));
366
367 // Copy from UBO to SSBO, inverting bits
368 useAfterFreeGroup->addChild(UseAfterFreeTestCase::UBOToSSBOInvertCase(testCtx, "ubo_to_ssbo_single_invocation", 256, tcu::IVec3(1, 1, 1), tcu::IVec3(1, 1, 1)));
369 // Copy from SSBO to SSBO, inverting bits
370 useAfterFreeGroup->addChild(UseAfterFreeTestCase::CopyInvertSSBOCase (testCtx, "ssbo_to_ssbo_single_invocation", 256, tcu::IVec3(1, 1, 1), tcu::IVec3(1, 1, 1)));
371
372 return useAfterFreeGroup.release();
373 }
374
375 } // postmortem
376 } // vkt
377