Skip to content

Commit ef26bc7

Browse files
aizu-mcharles-lunarg
authored andcommitted
fix out-of-bounds read in vk_string_validate on truncated utf-8
The continuation-byte scan only stops at max_length, not at the string's null terminator, so a name ending in a lone multi-byte lead byte makes it read past the terminator, up to MaxLoaderStringLength bytes beyond the buffer. Stop the scan when a null is seen inside a sequence and report it as bad data.
1 parent 9a5f88e commit ef26bc7

3 files changed

Lines changed: 28 additions & 2 deletions

File tree

loader/loader.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7487,7 +7487,7 @@ VKAPI_ATTR VkResult VKAPI_CALL terminator_EnumerateDeviceExtensionProperties(VkP
74877487
return res;
74887488
}
74897489

7490-
VkStringErrorFlags vk_string_validate(const int max_length, const char *utf8) {
7490+
TEST_FUNCTION_EXPORT VkStringErrorFlags vk_string_validate(const int max_length, const char *utf8) {
74917491
VkStringErrorFlags result = VK_STRING_ERROR_NONE;
74927492
int num_char_bytes = 0;
74937493
int i, j;
@@ -7520,6 +7520,12 @@ VkStringErrorFlags vk_string_validate(const int max_length, const char *utf8) {
75207520
result |= VK_STRING_ERROR_LENGTH;
75217521
break;
75227522
}
7523+
if (utf8[i] == 0) {
7524+
// The string terminated in the middle of a multi-byte character. Stop here so the
7525+
// continuation-byte scan doesn't read past the terminator.
7526+
result |= VK_STRING_ERROR_BAD_DATA;
7527+
return result;
7528+
}
75237529
if ((utf8[i] & UTF8_DATA_BYTE_MASK) != UTF8_DATA_BYTE_CODE) {
75247530
result |= VK_STRING_ERROR_BAD_DATA;
75257531
}

loader/loader.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ void unload_drivers_without_physical_devices(struct loader_instance *inst);
233233
VkResult loader_apply_settings_device_configurations(struct loader_instance *inst, uint32_t *pPhysicalDeviceCount,
234234
VkPhysicalDevice *pPhysicalDevices);
235235

236-
VkStringErrorFlags vk_string_validate(const int max_length, const char *char_array);
236+
TEST_FUNCTION_EXPORT VkStringErrorFlags vk_string_validate(const int max_length, const char *char_array);
237237
char *loader_get_next_path(char *path);
238238
VkResult add_if_manifest_file(const struct loader_instance *inst, const char *file_name, struct loader_string_list *out_files);
239239
VkResult prepend_if_manifest_file(const struct loader_instance *inst, const char *file_name, struct loader_string_list *out_files);

tests/loader_fuzz_tests.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "framework/test_environment.h"
2929

3030
#include <fstream>
31+
#include <vector>
3132

3233
extern "C" {
3334
#include "loader.h"
@@ -341,3 +342,22 @@ TEST(BadJsonInput, ClusterFuzzTestCase_5123849246867456) {
341342
TEST(BadJsonInput, ClusterFuzzTestCase_4626669072875520) {
342343
execute_setting_fuzzer("clusterfuzz-testcase-minimized-settings_fuzzer-4626669072875520");
343344
}
345+
346+
// A string that ends in the middle of a multi-byte UTF-8 character must not make vk_string_validate read
347+
// past the terminator. Each buffer is sized to its exact contents so ASAN flags any over-read.
348+
TEST(StringValidation, TruncatedMultiByteDoesNotReadPastTerminator) {
349+
// 2-, 3- and 4-byte lead bytes, each immediately followed by the terminator.
350+
for (unsigned char lead : {0xC2u, 0xE2u, 0xF0u}) {
351+
std::vector<char> truncated = {static_cast<char>(lead), '\0'};
352+
EXPECT_EQ(vk_string_validate(MaxLoaderStringLength, truncated.data()), VK_STRING_ERROR_BAD_DATA);
353+
}
354+
// A lead byte with one valid continuation byte but still short of the required count.
355+
std::vector<char> short_seq = {static_cast<char>(0xE2), static_cast<char>(0x82), '\0'};
356+
EXPECT_EQ(vk_string_validate(MaxLoaderStringLength, short_seq.data()), VK_STRING_ERROR_BAD_DATA);
357+
358+
// Well-formed strings still validate cleanly.
359+
std::vector<char> ascii = {'a', 'b', 'c', '\0'};
360+
EXPECT_EQ(vk_string_validate(MaxLoaderStringLength, ascii.data()), VK_STRING_ERROR_NONE);
361+
std::vector<char> two_byte = {static_cast<char>(0xC2), static_cast<char>(0xA9), '\0'};
362+
EXPECT_EQ(vk_string_validate(MaxLoaderStringLength, two_byte.data()), VK_STRING_ERROR_NONE);
363+
}

0 commit comments

Comments
 (0)