-- v2: ntdll/tests: Add some tests for NtSetLdtEntries.
From: Dmitry Timoshkov dmitry@baikal.ru
Signed-off-by: Dmitry Timoshkov dmitry@baikal.ru --- dlls/kernel32/tests/thread.c | 75 ++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+)
diff --git a/dlls/kernel32/tests/thread.c b/dlls/kernel32/tests/thread.c index c0303b471e8..8affee41b19 100644 --- a/dlls/kernel32/tests/thread.c +++ b/dlls/kernel32/tests/thread.c @@ -103,6 +103,7 @@ static HRESULT (WINAPI *pSetThreadDescription)(HANDLE,const WCHAR *); static HRESULT (WINAPI *pGetThreadDescription)(HANDLE,WCHAR **); static PVOID (WINAPI *pRtlAddVectoredExceptionHandler)(ULONG,PVECTORED_EXCEPTION_HANDLER); static ULONG (WINAPI *pRtlRemoveVectoredExceptionHandler)(PVOID); +static NTSTATUS (WINAPI *pNtSetLdtEntries)(ULONG,ULONG,ULONG,ULONG,ULONG,ULONG);
static HANDLE create_target_process(const char *arg) { @@ -1319,6 +1320,78 @@ static void test_GetThreadSelectorEntry(void) ok(entry.HighWord.Bits.Granularity == 1, "expected 1, got %u\n", entry.HighWord.Bits.Granularity); }
+static void test_NtSetLdtEntries(void) +{ + THREAD_DESCRIPTOR_INFORMATION tdi; + LDT_ENTRY ds_entry; + CONTEXT ctx; + DWORD ret; + union + { + LDT_ENTRY entry; + DWORD dw[2]; + } sel; + + if (!pNtSetLdtEntries) + { + win_skip("NtSetLdtEntries is not available on this platform\n"); + return; + } + + if (pNtSetLdtEntries(0, 0, 0, 0, 0, 0) == STATUS_NOT_IMPLEMENTED) /* WoW64 */ + { + win_skip("NtSetLdtEntries is not implemented on this platform\n"); + return; + } + + ret = pNtSetLdtEntries(0, 0, 0, 0, 0, 0); + ok(!ret, "NtSetLdtEntries failed: %08lx\n", ret); + + ctx.ContextFlags = CONTEXT_SEGMENTS; + ret = GetThreadContext(GetCurrentThread(), &ctx); + ok(ret, "GetThreadContext failed\n"); + + tdi.Selector = ctx.SegDs; + ret = pNtQueryInformationThread(GetCurrentThread(), ThreadDescriptorTableEntry, &tdi, sizeof(tdi), &ret); + ok(!ret, "NtQueryInformationThread failed: %08lx\n", ret); + ds_entry = tdi.Entry; + + tdi.Selector = 0x000f; + ret = pNtQueryInformationThread(GetCurrentThread(), ThreadDescriptorTableEntry, &tdi, sizeof(tdi), &ret); + ok(ret == STATUS_UNSUCCESSFUL, "got %08lx\n", ret); + + tdi.Selector = 0x001f; + ret = pNtQueryInformationThread(GetCurrentThread(), ThreadDescriptorTableEntry, &tdi, sizeof(tdi), &ret); + ok(ret == STATUS_UNSUCCESSFUL, "NtQueryInformationThread returned %08lx\n", ret); + + ret = GetThreadSelectorEntry(GetCurrentThread(), 0x000f, &sel.entry); + ok(!ret, "GetThreadSelectorEntry should fail\n"); + + ret = GetThreadSelectorEntry(GetCurrentThread(), 0x001f, &sel.entry); + ok(!ret, "GetThreadSelectorEntry should fail\n"); + + memset(&sel.entry, 0x9a, sizeof(sel.entry)); + ret = GetThreadSelectorEntry(GetCurrentThread(), ctx.SegDs, &sel.entry); + ok(ret, "GetThreadSelectorEntry failed\n"); + ok(!memcmp(&ds_entry, &sel.entry, sizeof(ds_entry)), "entries do not match\n"); + + ret = pNtSetLdtEntries(0x000f, sel.dw[0], sel.dw[1], 0x001f, sel.dw[0], sel.dw[1]); + ok(!ret || broken(ret == STATUS_INVALID_LDT_DESCRIPTOR) /*XP*/, "NtSetLdtEntries failed: %08lx\n", ret); + + if (!ret) + { + memset(&sel.entry, 0x9a, sizeof(sel.entry)); + ret = GetThreadSelectorEntry(GetCurrentThread(), 0x000f, &sel.entry); + ok(ret, "GetThreadSelectorEntry failed\n"); + ok(!memcmp(&ds_entry, &sel.entry, sizeof(ds_entry)), "entries do not match\n"); + + memset(&sel.entry, 0x9a, sizeof(sel.entry)); + ret = GetThreadSelectorEntry(GetCurrentThread(), 0x001f, &sel.entry); + ok(ret, "GetThreadSelectorEntry failed\n"); + ok(!memcmp(&ds_entry, &sel.entry, sizeof(ds_entry)), "entries do not match\n"); + } +} + #endif /* __i386__ */
static HANDLE finish_event; @@ -2644,6 +2717,7 @@ static void init_funcs(void) X(NtSetInformationThread); X(RtlAddVectoredExceptionHandler); X(RtlRemoveVectoredExceptionHandler); + X(NtSetLdtEntries); } #undef X } @@ -2700,6 +2774,7 @@ START_TEST(thread) test_SetThreadContext(); test_GetThreadSelectorEntry(); test_GetThreadContext(); + test_NtSetLdtEntries(); #endif test_QueueUserWorkItem(); test_RegisterWaitForSingleObject();
v2: Looks like NtQueryInformationThread(ThreadDescriptorTableEntry) returning STATUS_UNSUCCESSFUL works as intended.
On Wed Sep 17 12:00:02 2025 +0000, Dmitry Timoshkov wrote:
v2: Looks like NtQueryInformationThread(ThreadDescriptorTableEntry) returning STATUS_UNSUCCESSFUL works as intended.
Yes, and as you no doubt noticed it already has more extensive tests in ntdll:wow64, I don't think it's necessary to duplicate them.
On Wed Sep 17 12:00:02 2025 +0000, Alexandre Julliard wrote:
Yes, and as you no doubt noticed it already has more extensive tests in ntdll:wow64, I don't think it's necessary to duplicate them.
Yes, ntdll:wow64 tests started to fail because of the (now removed) change. What do you mean by duplicated tests? I don't see NtSetLdtEntries() usage in existing tests, and the tests in this MR replicate behaviour of some protection schemes, so it seems useful to test exact sequence of actions that were previously failing (at the some these patches were worked on).
On Wed Sep 17 12:10:14 2025 +0000, Dmitry Timoshkov wrote:
Yes, ntdll:wow64 tests started to fail because of the (now removed) change. What do you mean by duplicated tests? I don't see NtSetLdtEntries() usage in existing tests, and the tests in this MR replicate behaviour of some protection schemes, so it seems useful to test exact sequence of actions that were previously failing (at the some these patches were worked on).
NtSetLdtEntries() tests are useful, but should probably go into ntdll:wow64. NtQueryInformationThread(ThreadDescriptorTableEntry) tests for invalid selectors are duplicated and can be removed.
On Wed Sep 17 12:27:02 2025 +0000, Alexandre Julliard wrote:
NtSetLdtEntries() tests are useful, but should probably go into ntdll:wow64. NtQueryInformationThread(ThreadDescriptorTableEntry) tests for invalid selectors are duplicated and can be removed.
Sure, I can move tests to ntdll:wow64.
The tests for invalid selectors are part of the test to make sure that the modified LDT entries didn't exist before initializing them.