[PATCH v46 0/2] MR10291: Fix GetModuleFileName string termination
After Windows XP the string termination behavior of GetModuleFileName was changed to always terminate the path returned, even if the buffer is insufficient to contain the null terminator. -- v46: kernel32: Fix string termination of GetModuleFileName https://gitlab.winehq.org/wine/wine/-/merge_requests/10291
From: Trent Waddington <trent.waddington@tensorworks.com.au> --- dlls/kernel32/tests/loader.c | 48 ++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/dlls/kernel32/tests/loader.c b/dlls/kernel32/tests/loader.c index 529b9de870d..2ea83147296 100644 --- a/dlls/kernel32/tests/loader.c +++ b/dlls/kernel32/tests/loader.c @@ -1552,6 +1552,52 @@ static void test_filenames(void) DeleteFileA( long_path ); } +static void test_getmodulefilenamew_string_termination(void) +{ + WCHAR dll_name[MAX_PATH]; + size_t dll_name_len, dll_name_term; + DWORD rv, err; + + GetModuleFileNameW(NULL, dll_name, MAX_PATH); + err = GetLastError(); + ok(err == ERROR_SUCCESS, "error getting path for NULL module: %lu\n", err); + dll_name_len = wcslen(dll_name); + ok(dll_name_len > 0, "can't get path for NULL module\n"); + + memset(dll_name, '*', sizeof(dll_name)); + rv = GetModuleFileNameW(NULL, dll_name, dll_name_len); + err = GetLastError(); + ok(err == ERROR_INSUFFICIENT_BUFFER, "didn't get expected error getting path for NULL module with short buffer: %lu\n", err); + dll_name_term = wcsnlen(dll_name, MAX_PATH); + ok(dll_name_term > 0, "can't get path for NULL module with short buffer, dll_name_term=%llu\n", dll_name_term); + todo_wine { + ok(dll_name_term == dll_name_len - 1, "incorrect path termination for NULL module with short buffer. Expected %llu got %llu. rv=%lu\n", dll_name_len - 1, dll_name_term, rv); + } +} + +static void test_getmodulefilenamea_string_termination(void) +{ + char dll_name[MAX_PATH]; + size_t dll_name_len, dll_name_term; + DWORD rv, err; + + GetModuleFileNameA(NULL, dll_name, MAX_PATH); + err = GetLastError(); + ok(err == ERROR_SUCCESS, "error getting path for NULL module: %d\n", (int)err); + dll_name_len = strlen(dll_name); + ok(dll_name_len > 0, "can't get path for NULL module\n"); + + memset(dll_name, '*', sizeof(dll_name)); + rv = GetModuleFileNameA(NULL, dll_name, dll_name_len); + err = GetLastError(); + ok(err == ERROR_INSUFFICIENT_BUFFER, "didn't get expected error getting path for NULL module with short buffer: %lu\n", err); + dll_name_term = strnlen(dll_name, MAX_PATH); + ok(dll_name_term > 0, "can't get path for NULL module with short buffer, dll_name_term=%llu\n", dll_name_term); + todo_wine { + ok(dll_name_term == dll_name_len - 1, "incorrect path termination for NULL module with short buffer. Expected %llu got %llu. rv=%lu\n", dll_name_len - 1, dll_name_term, rv); + } +} + /* Verify linking style of import descriptors */ static void test_ImportDescriptors(void) { @@ -4859,6 +4905,8 @@ START_TEST(loader) } test_filenames(); + test_getmodulefilenamew_string_termination(); + test_getmodulefilenamea_string_termination(); test_ResolveDelayLoadedAPI(); test_ImportDescriptors(); test_section_access(); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10291
From: Trent Waddington <trent.waddington@tensorworks.com.au> --- dlls/kernel32/tests/loader.c | 8 ++------ dlls/kernelbase/loader.c | 9 ++++++++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/dlls/kernel32/tests/loader.c b/dlls/kernel32/tests/loader.c index 2ea83147296..9daf09a4310 100644 --- a/dlls/kernel32/tests/loader.c +++ b/dlls/kernel32/tests/loader.c @@ -1570,9 +1570,7 @@ static void test_getmodulefilenamew_string_termination(void) ok(err == ERROR_INSUFFICIENT_BUFFER, "didn't get expected error getting path for NULL module with short buffer: %lu\n", err); dll_name_term = wcsnlen(dll_name, MAX_PATH); ok(dll_name_term > 0, "can't get path for NULL module with short buffer, dll_name_term=%llu\n", dll_name_term); - todo_wine { - ok(dll_name_term == dll_name_len - 1, "incorrect path termination for NULL module with short buffer. Expected %llu got %llu. rv=%lu\n", dll_name_len - 1, dll_name_term, rv); - } + ok(dll_name_term == dll_name_len - 1, "incorrect path termination for NULL module with short buffer. Expected %llu got %llu. rv=%lu\n", dll_name_len - 1, dll_name_term, rv); } static void test_getmodulefilenamea_string_termination(void) @@ -1593,9 +1591,7 @@ static void test_getmodulefilenamea_string_termination(void) ok(err == ERROR_INSUFFICIENT_BUFFER, "didn't get expected error getting path for NULL module with short buffer: %lu\n", err); dll_name_term = strnlen(dll_name, MAX_PATH); ok(dll_name_term > 0, "can't get path for NULL module with short buffer, dll_name_term=%llu\n", dll_name_term); - todo_wine { - ok(dll_name_term == dll_name_len - 1, "incorrect path termination for NULL module with short buffer. Expected %llu got %llu. rv=%lu\n", dll_name_len - 1, dll_name_term, rv); - } + ok(dll_name_term == dll_name_len - 1, "incorrect path termination for NULL module with short buffer. Expected %llu got %llu. rv=%lu\n", dll_name_len - 1, dll_name_term, rv); } /* Verify linking style of import descriptors */ diff --git a/dlls/kernelbase/loader.c b/dlls/kernelbase/loader.c index f4e1ca2e23a..aeac1c3ec77 100644 --- a/dlls/kernelbase/loader.c +++ b/dlls/kernelbase/loader.c @@ -315,7 +315,14 @@ DWORD WINAPI DECLSPEC_HOTPATCH GetModuleFileNameW( HMODULE module, LPWSTR filena name.Buffer = filename; name.MaximumLength = min( size, UNICODE_STRING_MAX_CHARS ) * sizeof(WCHAR); status = LdrGetDllFullName( module, &name ); - if (!status || status == STATUS_BUFFER_TOO_SMALL) len = name.Length / sizeof(WCHAR); + if (!status || status == STATUS_BUFFER_TOO_SMALL) + { + len = name.Length / sizeof(WCHAR); + if (len < size) + filename[len] = 0; + else if (size > 0) + filename[size - 1] = 0; + } SetLastError( RtlNtStatusToDosError( status )); done: TRACE( "%s\n", debugstr_wn(filename, len) ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10291
eric pouech (@epo) commented about dlls/kernel32/tests/loader.c:
+ WCHAR dll_name[MAX_PATH]; + size_t dll_name_len, dll_name_term; + DWORD rv, err; + + GetModuleFileNameW(NULL, dll_name, MAX_PATH); + err = GetLastError(); + ok(err == ERROR_SUCCESS, "error getting path for NULL module: %lu\n", err); + dll_name_len = wcslen(dll_name); + ok(dll_name_len > 0, "can't get path for NULL module\n"); + + memset(dll_name, '*', sizeof(dll_name)); + rv = GetModuleFileNameW(NULL, dll_name, dll_name_len); + err = GetLastError(); + ok(err == ERROR_INSUFFICIENT_BUFFER, "didn't get expected error getting path for NULL module with short buffer: %lu\n", err); + dll_name_term = wcsnlen(dll_name, MAX_PATH); + ok(dll_name_term > 0, "can't get path for NULL module with short buffer, dll_name_term=%llu\n", dll_name_term); you need `%Iu `for size_t
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10291#note_134029
eric pouech (@epo) commented about dlls/kernel32/tests/loader.c:
+ dll_name_term = wcsnlen(dll_name, MAX_PATH); + ok(dll_name_term > 0, "can't get path for NULL module with short buffer, dll_name_term=%llu\n", dll_name_term); + todo_wine { + ok(dll_name_term == dll_name_len - 1, "incorrect path termination for NULL module with short buffer. Expected %llu got %llu. rv=%lu\n", dll_name_len - 1, dll_name_term, rv); + } +} + +static void test_getmodulefilenamea_string_termination(void) +{ + char dll_name[MAX_PATH]; + size_t dll_name_len, dll_name_term; + DWORD rv, err; + + GetModuleFileNameA(NULL, dll_name, MAX_PATH); + err = GetLastError(); + ok(err == ERROR_SUCCESS, "error getting path for NULL module: %d\n", (int)err); please remove the cast
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10291#note_134030
eric pouech (@epo) commented about dlls/kernel32/tests/loader.c:
DeleteFileA( long_path ); }
+static void test_getmodulefilenamew_string_termination(void) +{ + WCHAR dll_name[MAX_PATH]; + size_t dll_name_len, dll_name_term; + DWORD rv, err; + + GetModuleFileNameW(NULL, dll_name, MAX_PATH);
sorry for not advising for this earlier on, but we usually insert `SetLastError(0xdeadbeef); `before calling the APIs it turns out that some APIs don't set last error in case of success; so this ensures that we check against the value set in the API call (shall be done before all the calls to GetModuleFileNameW) -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10291#note_134031
On Fri Mar 27 12:50:22 2026 +0000, Trent Waddington wrote:
I've moved the code from the done section but kept the `filename[len] = 0` in this function so the termination semantics are explicit in this API. if it's purely semantics, a comment would do
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10291#note_134032
participants (3)
-
eric pouech (@epo) -
Trent Waddington -
Trent Waddington (@trent.waddington)