[PATCH v2 22/22] ntdll: Deny library load or unload from LDR notification callbacks.

Paul Gofman pgofman at codeweavers.com
Tue Oct 5 17:49:28 CDT 2021


Signed-off-by: Paul Gofman <pgofman at codeweavers.com>
---
    - fixed RtlIsCriticalSectionLocked() -> RtlIsCriticalSectionLockedByThread().

 dlls/kernel32/tests/loader.c | 97 +++++++++++++++++++++++++++++++++++-
 dlls/ntdll/loader.c          | 17 +++++++
 2 files changed, 113 insertions(+), 1 deletion(-)

diff --git a/dlls/kernel32/tests/loader.c b/dlls/kernel32/tests/loader.c
index 5a706fadc48..8532eca2633 100644
--- a/dlls/kernel32/tests/loader.c
+++ b/dlls/kernel32/tests/loader.c
@@ -4049,6 +4049,7 @@ static HMODULE lock_dll_handle;
 static HANDLE lock_ready_event, next_test_event;
 static BOOL test_loader_lock_abort_test;
 static volatile LONG test_loader_lock_timeout_count;
+static BOOL blocks_on_load_in_progress_module;
 
 #define BLOCKING_TESTS_ENABLED 0
 
@@ -4062,6 +4063,53 @@ static void CALLBACK test_loader_lock_module_enum_locking_callback(LDR_DATA_TABL
     *stop = TRUE;
 }
 
+static void CALLBACK test_loader_lock_dummy_callback(LDR_DATA_TABLE_ENTRY *mod,
+        void *context, BOOLEAN *stop)
+{
+    *stop = TRUE;
+}
+
+static void CALLBACK test_loader_lock_ldr_notify_locking(ULONG reason, LDR_DLL_NOTIFICATION_DATA *data, void *context)
+{
+    NTSTATUS status;
+    HMODULE hmodule;
+    void *cookie;
+    BOOL bret;
+
+    if (reason != LDR_DLL_NOTIFICATION_REASON_UNLOADED) return;
+
+    status = LdrRegisterDllNotification(0, test_loader_lock_ldr_notify_locking, NULL, &cookie);
+    ok(!status, "Got unexpected status %#x.\n", status);
+    status = LdrUnregisterDllNotification( cookie );
+    ok(!status, "Got unexpected status %#x.\n", status);
+
+    hmodule = LoadLibraryW(L"winmm.dll");
+    ok(!!hmodule, "GetModuleHandleW failed, err %u.\n", GetLastError());
+    bret = FreeLibrary(hmodule);
+    ok(bret, "FreeLibrary failed, err %u.\n", GetLastError());
+
+    if (!blocks_on_load_in_progress_module)
+    {
+        bret = GetModuleHandleExW(0, L"lock.dll", &hmodule);
+        ok(bret, "GetModuleHandleExW failed, err %u.\n", GetLastError());
+        ok(!!hmodule, "GetModuleHandleW failed, err %u.\n", GetLastError());
+        bret = FreeLibrary(hmodule);
+        ok(bret, "FreeLibrary failed, err %u.\n", GetLastError());
+    }
+
+    hmodule = LoadLibraryW(L"authz.dll");
+    ok(!hmodule, "LoadLibraryW succeeded.\n");
+    ok(GetLastError() == ERROR_NOT_FOUND || broken(blocks_on_load_in_progress_module
+            && GetLastError() == ERROR_MOD_NOT_FOUND), "Got unexpected error %u.\n", GetLastError());
+
+    status = pLdrEnumerateLoadedModules(NULL, test_loader_lock_dummy_callback, NULL);
+    ok(!status, "Got unexpected status %#x.\n", status);
+
+    SetEvent(lock_ready_event);
+    if (WaitForSingleObject(next_test_event, 3000) == WAIT_TIMEOUT)
+        ++test_loader_lock_timeout_count;
+}
+
 static DWORD WINAPI test_loader_lock_thread(void *param)
 {
     static const WCHAR env_var[] = L"test_wait_reason_value";
@@ -4069,6 +4117,7 @@ static DWORD WINAPI test_loader_lock_thread(void *param)
     NTSTATUS status;
     HMODULE hmodule;
     WCHAR str[32];
+    void *cookie;
     DWORD result;
     BOOL bret;
 
@@ -4177,6 +4226,28 @@ static DWORD WINAPI test_loader_lock_thread(void *param)
     SetEvent(lock_ready_event);
     WaitForSingleObject(next_test_event, INFINITE);
 
+    /* 5. Test with the thread blocked in LDR notification callback. */
+    LdrRegisterDllNotification(0, test_loader_lock_ldr_notify_locking, NULL, &cookie);
+    /* lock.dll will return FALSE from the DLL entry point. */
+    bret = SetEnvironmentVariableW(env_var, L"-1");
+    ok(bret, "SetEnvironmentVariableW failed.\n");
+    do
+    {
+        hmodule = LoadLibraryA("lock.dll");
+        ok(!!hmodule, "LoadLibrary failed, err %u.\n", GetLastError());
+        bret = FreeLibrary(hmodule);
+        ok(bret, "FreeLibrary failed, err %u.\n", GetLastError());
+        if (test_loader_lock_timeout_count)
+        {
+            WaitForSingleObject(next_test_event, INFINITE);
+            test_loader_lock_timeout_count = 0;
+        }
+    } while (test_loader_lock_repeat_lock);
+    LdrUnregisterDllNotification( cookie );
+
+    SetEvent(lock_ready_event);
+    WaitForSingleObject(next_test_event, INFINITE);
+
     SetEvent(lock_ready_event);
     return 0;
 }
@@ -4222,7 +4293,6 @@ static void test_loader_lock(void)
 {
     static const WCHAR not_loaded_dll_name[] = L"authz.dll";
     static const WCHAR preloaded_dll_name[] = L"winmm.dll";
-    BOOL blocks_on_load_in_progress_module = FALSE;
     BOOL blocks_on_decref_library = FALSE;
     HMODULE hmodule_preloaded, hmodule;
     WCHAR buffer[MAX_PATH];
@@ -4484,6 +4554,31 @@ static void test_loader_lock(void)
     LdrUnregisterDllNotification( cookie );
     check_timeout(FALSE);
 
+    /* 5. Test with the thread blocked in LDR notification callback. */
+    trace("Test 5.\n");
+    test_loader_lock_next_test();
+
+    hmodule = GetModuleHandleW(preloaded_dll_name);
+    ok(!!hmodule, "LoadLibrary failed, err %u.\n", GetLastError());
+    check_timeout(FALSE);
+
+    if (BLOCKING_TESTS_ENABLED)
+    {
+        status = LdrRegisterDllNotification(0, test_loader_lock_ldr_notify, NULL, &cookie);
+        check_timeout(TRUE);
+        ok(!status, "Got unexpected status %#x.\n", status);
+        status = LdrUnregisterDllNotification( cookie );
+        check_timeout(TRUE);
+        ok(!status, "Got unexpected status %#x.\n", status);
+
+        /* This doesn't block in load notifications on Windows, only unload. */
+        status = pLdrLockLoaderLock(0, NULL, &magic);
+        ok(!status, "Got unexpected status %#x.\n", status);
+        status = pLdrUnlockLoaderLock(0, magic);
+        ok(!status, "Got unexpected status %#x.\n", status);
+        check_timeout(TRUE);
+    }
+
     test_loader_lock_next_test();
 
     if (blocks_on_decref_library)
diff --git a/dlls/ntdll/loader.c b/dlls/ntdll/loader.c
index 65cf618626b..c1a826914ea 100644
--- a/dlls/ntdll/loader.c
+++ b/dlls/ntdll/loader.c
@@ -2121,6 +2121,12 @@ NTSTATUS WINAPI LdrGetProcedureAddress( HMODULE module, const ANSI_STRING *name,
 
     if (ret != STATUS_NOT_FOUND) return ret;
 
+    if (RtlIsCriticalSectionLockedByThread( &ldr_notifications_section ))
+    {
+        WARN( "Attempt to unload a library from notification callback.\n" );
+        return STATUS_NOT_FOUND;
+    }
+
     lock_loader_exclusive();
     ret = get_procedure_address( module, name, ord, address );
     unlock_loader();
@@ -3351,6 +3357,11 @@ NTSTATUS WINAPI DECLSPEC_HOTPATCH LdrLoadDll(LPCWSTR path_name, DWORD flags,
 
     if (!LdrGetDllHandleEx( 0, path_name, NULL, libname, hModule )) return STATUS_SUCCESS;
 
+    if (RtlIsCriticalSectionLockedByThread( &ldr_notifications_section ))
+    {
+        WARN( "Attempt to load a new library from notification callback.\n" );
+        return STATUS_NOT_FOUND;
+    }
     lock_loader_exclusive();
 
     nts = load_dll( path_name, libname->Buffer, L".dll", flags, &wm );
@@ -4027,6 +4038,12 @@ NTSTATUS WINAPI LdrUnloadDll( HMODULE hModule )
     unlock_loader();
     if (!need_exclusive) return retv;
 
+    if (RtlIsCriticalSectionLockedByThread( &ldr_notifications_section ))
+    {
+        WARN( "Attempt to unload a library from notification callback.\n" );
+        return STATUS_NOT_FOUND;
+    }
+
     lock_loader_exclusive();
 
     free_lib_count++;
-- 
2.31.1




More information about the wine-devel mailing list