From d2fa25f215bc20ec2a8f4121995c3a48a1ac43ce Mon Sep 17 00:00:00 2001 From: Aizal Khan Date: Tue, 11 Aug 2026 23:37:55 +0530 Subject: [PATCH] zero-init entrypoints in loader_add_to_dev_ext_list --- loader/loader.c | 2 ++ tests/loader_alloc_callback_tests.cpp | 40 +++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/loader/loader.c b/loader/loader.c index 4856bdb39..2574d4f4d 100644 --- a/loader/loader.c +++ b/loader/loader.c @@ -1217,6 +1217,8 @@ VkResult loader_add_to_dev_ext_list(const struct loader_instance *inst, struct l if (entrys) { ext_list->list[idx].entrypoints = *entrys; should_free_entrys = false; + } else { + memset(&ext_list->list[idx].entrypoints, 0, sizeof(ext_list->list[idx].entrypoints)); } ext_list->count++; out: diff --git a/tests/loader_alloc_callback_tests.cpp b/tests/loader_alloc_callback_tests.cpp index 559e9b575..86305adc6 100644 --- a/tests/loader_alloc_callback_tests.cpp +++ b/tests/loader_alloc_callback_tests.cpp @@ -55,6 +55,7 @@ class MemoryTracker { bool fail_next_growing_reallocation = false; bool fail_growing_reallocation_after_skips = false; size_t growing_reallocations_to_skip = 0; + bool poison_growing_reallocations = false; std::unordered_map allocations; void* allocate(size_t size, size_t alignment, VkSystemAllocationScope alloc_scope) { @@ -111,6 +112,11 @@ class MemoryTracker { allocation_count--; // allocate() increments this, we we don't want that call_count--; // allocate() also increments this, we don't want that memcpy(new_alloc, pOriginal, original_size); + if (poison_growing_reallocations && size > original_size) { + // The loader does not zero the region an app pfnReallocation grows, so fill it with a non-zero + // pattern to make any read of an unwritten grown slot deterministic. + memset(static_cast(new_alloc) + original_size, 0xff, size - original_size); + } allocations.erase(elem); return new_alloc; } @@ -187,6 +193,13 @@ class MemoryTracker { growing_reallocations_to_skip = skip_count; } + // Fill the newly grown region of every growing reallocation with a non-zero pattern, matching what an app + // pfnReallocation that does not zero grown memory would leave behind. + void poison_grown_reallocations() noexcept { + std::lock_guard lg(main_mutex); + poison_growing_reallocations = true; + } + // Static callbacks static VKAPI_ATTR void* VKAPI_CALL public_allocation(void* pUserData, size_t size, size_t alignment, VkSystemAllocationScope allocationScope) noexcept { @@ -1129,6 +1142,33 @@ TEST(Allocation, EnumeratePhysicalDevicesIntentionalAllocFail) { reached_the_end = true; } } + +// A layer device_extension entry with no "entrypoints" leaves loader_dev_ext_props::entrypoints unwritten. +// The first 32 list slots come from a zero-filled allocation, but the 33rd lands in the region grown by the app +// pfnReallocation, which the loader does not zero. Freeing the discovered layer then walks that indeterminate +// loader_string_list. Poison the grown region so the read is deterministic. +TEST(Allocation, LayerDeviceExtensionsWithoutEntrypoints) { + FrameworkEnvironment env{}; + env.add_icd(TEST_ICD_PATH_VERSION_2); + + std::vector layer_exts; + for (uint32_t i = 0; i < 40; i++) { + layer_exts.emplace_back(std::string("VK_TEST_dev_ext_") + std::to_string(i), i + 1); + } + env.add_explicit_layer(ManifestOptions{}.set_json_name("test_dev_ext_layer.json"), + ManifestLayer{}.add_layer(ManifestLayer::LayerDescription{} + .set_name("VK_LAYER_test_dev_ext") + .set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2) + .add_device_extensions(layer_exts))); + + MemoryTracker tracker; + tracker.poison_grown_reallocations(); + { + InstWrapper inst{env.vulkan_functions, tracker.get()}; + ASSERT_NO_FATAL_FAILURE(inst.CheckCreate()); + } + ASSERT_TRUE(tracker.empty()); +} #if defined(WIN32) // Test failure during vkCreateInstance and vkCreateDevice to make sure we don't // leak memory if one of the out-of-memory conditions trigger.