Why do I get ERROR_INVALID_HANDLE from GetModuleFileNameEx when I know the process handle is valid?


Consider the following program:

#define UNICODE
#define _UNICODE
#include <windows.h>
#include <psapi.h>
#include <stdio.h> // horrors! mixing C and C++!

int __cdecl wmain(int, wchar_t **)
{
 STARTUPINFO si = { sizeof(si) };
 PROCESS_INFORMATION pi;
 wchar_t szBuf[MAX_PATH] = L"C:\\Windows\\System32\\notepad.exe";

 if (CreateProcess(szBuf, szBuf, NULL, NULL, FALSE,
                   CREATE_SUSPENDED,
                   NULL, NULL, &si, &pi)) {
  if (GetModuleFileNameEx(pi.hProcess, NULL, szBuf, ARRAYSIZE(szBuf))) {
   wprintf(L"Executable is %ls\n", szBuf);
  } else {
   wprintf(L"Failed to get module file name: %d\n", GetLastError());
  }
  TerminateProcess(pi.hProcess, 0);
  CloseHandle(pi.hProcess);
  CloseHandle(pi.hThread);
 } else {
  wprintf(L"Failed to create process: %d\n", GetLastError());
 }

 return 0;
}

This program prints

Failed to get module file name: 6

and error 6 is ERROR_INVALID_HANDLE. "How can the process handle be invalid? I just created the process!"

Oh, the process handle is valid. The handle that isn't valid is the NULL.

"But the documentation says that NULL is a valid value for the second parameter. It retrieves the path to the executable."

In Windows, processes are initialized in-process. (In other words, processes are self-initializing.) The Create­Process function creates a process object, sets the initial state of that object, copies some information into the address space of the new process (like the command line parameters), and sets the instruction pointer to the process startup code inside ntdll.dll. From there, the startup code in ntdll.dll pulls the process up by its bootstraps. It creates the default heap. It loads the primary executable and the associated bookkeeping that says "Here is the module information for the primary executable, in case anybody asks." It identifies all the DLLs referenced by the primary executable, the DLLs referenced by those DLLs, and so on. It loads each of the DLLs in turn, creating the module information that says "Here is another module that this process loaded, in case anybody asks," and then it initializes the DLLs in the proper order. Once all the process bootstrapping is complete, ntdll.dll calls the executable entry point, and the program takes control.

An interesting take-away from this is that modules are a user-mode concept. Kernel mode does not know about modules. All kernel mode sees is that somebody in user mode asked to map sections of a file into memory.

Okay, so if the process is responsible for managing its modules, how do functions like Get­Module­File­Name­Ex work? They issue a bunch of Read­Process­Memory calls and manually parse the in-memory data structures of another process. Normally, this would be considered "undocumented reliance on internal data structures that can change at any time," and in fact those data structures do change quite often. But it's okay because the people who maintain the module loader (and therefore would be the ones who change the data structures) are also the people who maintain Get­Module­File­Name­Ex (so they know to update the parser to match the new data structures).

With this background information, let's go back to the original question. Why is Get­Module­File­Name­Ex failing with ERROR_INVALID_HANDLE?

Observe that the process was created suspended. This means that the process object has been created, the initialization parameters have been injected into the new process's address space, but no code in the process has run yet. In particular, the startup code inside ntdll.dll hasn't run. This means that the code to add a module information entry for the main executable hasn't run.

Now we can connect the dots. Since the module information entry for the main executable hasn't been added to the module table, the call to Get­Module­File­Name­Ex is going to try to parse the module table from the suspended Notepad process, and it will see that the table is empty. Actually, it's worse than that. The module table hasn't been created yet. The function then reports, "There is no module table entry for NULL," and it tells you that the handle NULL is invalid.

Functions like Get­Module­File­Name­Ex and Create­Tool­help­32­Snapshot are designed for diagnostic or debugging tools. There are naturally race conditions involved, because the process you are inspecting is certainly free to load or unload a module immediately after the call returns, at which point your information may be out of date. What's worse, the process you are inspecting may be in the middle of updating its module table, in which case the call may simply fail with a strange error like ERROR_PARTIAL_COPY. (Protecting the data structures with a critical section isn't good enough because critical sections do not cross processes, and the process doing the inspecting is going to be using Read­Process­Memory, which doesn't care about critical sections.)

In the particular example above, the code could avoid the problem by using the Query­Full­Process­Image­Name function to get the path to the executable.

Bonus chatter: The Create­Tool­help­32­Snapshot function extracts the information in a different way from Get­Module­File­Name­Ex. Rather than trying to parse the information via Read­Process­Memory, it injects a thread into the target process and runs code to extract the information from within the process, and then marshals the results back. I'm not sure whether this is more crazy than using Read­Process­Memory or less crazy.

Second bonus chatter: A colleague of mine chose to describe this situation more directly. "Let's cut to the heart of the matter. These APIs don't really work by the normally-accepted definitions of 'work'." These snooping-around functions are best-effort, so use them in situations where best-effort is better than nothing. For example, if you have a diagnostic tool, you're probably happy that it gets information at all, even if it may sometimes be incomplete. (Debuggers don't use any of these APIs. Debuggers receive special events to notify them of modules as they are loaded and unloaded, and those notifications are generated by the loader itself, so they are reliable.)

Exercise: Diagnose this customer's problem: "If we launch a process suspended, the Get­Module­Information function fails with ERROR_INVALID_HANDLE."

#include <windows.h>
#include <psapi.h>
#include <iostream>

int __cdecl wmain(int, wchar_t **)
{
 STARTUPINFO si = { sizeof(si) };
 PROCESS_INFORMATION pi;
 wchar_t szBuf[MAX_PATH] = L"C:\\Windows\\System32\\notepad.exe";

 if (CreateProcess(szBuf, szBuf, NULL, NULL, FALSE,
                   CREATE_SUSPENDED,
                   NULL, NULL, &si, &pi)) {
  DWORD addr;
  std::cin >> std::hex >> addr;
  MODULEINFO mi;
  if (GetModuleInformation(pi.hProcess, (HINSTANCE)addr,
                           &mi, sizeof(mi))) {
   wprintf(L"Got the module information\n");
  } else {
   wprintf(L"Failed to get module information: %d\n", GetLastError());
  }
  TerminateProcess(hProcess, 0);
  CloseHandle(pi.hProcess);
  CloseHandle(pi.hThread);
 } else {
  wprintf(L"Failed to create process: %d\n", GetLastError());
 }

 return 0;
}

Run Process Explorer, then run this program. When the program asks for an address, enter the address that Process Explorer reports for the base address of the module.

Comments (18)
  1. anonymouscommenter says:

    Calling such methods as these on your own process has no such caveats, right?

  2. anonymouscommenter says:

    "Debuggers don't use any of these APIs. Debuggers receive special events to notify them of modules as they are loaded and unloaded…."

    The debugger I'm working on gets notified for DLLs that are loaded and unloaded, but there doesn't seem to be a debug event for the load of the initial executable module.  The only way I've found to get that is via GetModuleFileNameEx. Am I missing something?

    [CREATE_PROCESS_DEBUG_INFO? -Raymond]
  3. Emjayen says:

    @Joshua If you're calling one of those methods at all then the loader has already done it's job and passed control to your module (so in that case: no). However in the case of DLLs I *believe* the loader lock is taken during it's callback ("DllMain") and therefor results are undefined when it comes to standard methods of querying the module directory via PSAPI calls (personally, I've always used my own functions for enumerating modules and such so I can't be certain)

  4. anonymouscommenter says:

    @Joshua, provided you're single threaded. Otherwise another thread could call FreeLibrary while you're calling GetModuleFileNameEx.

  5. anonymouscommenter says:

    If you know the address of the module in question inside the target process, you can use [K32]GetMappedFileNameW to reliably get the image mapped at that address.  It works without reading data structures in the target process that may not yet be initialized, as in this article.  You can use VirtualQueryEx to distinguish between MEM_IMAGE and MAP_MAPPED; the former is how a loaded .exe or .dll gets mapped.

    ([K32] refers to the fact that in Windows 7 and later, this function was moved from psapi.dll to kernel32.dll, and renamed with "K32" at the beginning of the export name: K32GetMappedFileNameW.  psapi.dll is just a thin wrapper calling the K32 version on Windows 7 and later.  See the PSAPI_VERSION #define.)

    The only issue with GetMappedFileNameW is that it returns an NT path, not a DOS path.  Some labor is required, typically via QueryDosDeviceW, to convert such a path to a Win32 path.  (And you'll have to special-case SystemRoot as GetWindowsDirectoryW.)

    If it is only the .exe name that you are after, QueryFullProcessImageNameW is a better choice, because you don't need the base address, and because you can ask it to do the NT-to-DOS path conversion for you.  (I wish this NT<->DOS path conversion were available in a documented API.)  GetMappedFileNameW supports any module in the target process, not just the .exe

    [But of course you would never ship an app that called an undocumented API like K32GetMappedFileNameW, right? -Raymond]
  6. Myria says:

    Separate post for separate point.

    "It loads the primary executable" is partially incorrect for ntdll.dll.  When you call NtCreateUserProcess, the kernel maps in the command line, environment, ntdll.dll, the primary executable, and the first thread's new stack.  The process is entirely empty beyond that.  So when ntdll!LdrInitializeThunk is called at thread start, the .exe is already mapped into memory, but none of the other aspects required for loading, such as relocations(*) and DLL imports, have been done.  ntdll.dll's Ldr code is responsible for that, not the kernel.

    Prior to Vista, creating a process, via NtCreateProcess, was even more "pure": the *only* things in the new process were the PEB, ntdll.dll, and the .exe.  The creating process had to use the equivalent of VirtualAllocEx and WriteProcessMemory to copy the environment and command line into the target process.  The creating process also had to create the initial thread, by essentially calling VirtualAllocEx, then creating the raw thread using NtCreateThread with context->Esp pointing to this new stack.  This was changed in Vista to having the kernel doing the copying of the command line and environment and the creation of the first thread.  This redesign was primarily motivated by DRM reasons (**).

    (*) Relocations for ASLR are done by the kernel, not ntdll.dll, to enable copy-on-write behavior despite relocation.  Each /dynamicbase DLL is assigned a random base address by ASLR; the kernel then relocates the executable to this address.  If mapping the DLL at this random address fails for a particular process, it gets mapped to a different address, and then ntdll.dll is responsible for relocating it, rather than the kernel.

    (**) According to Arun Kishan, NT kernel developer.  I think this is the video where he says that: channel9.msdn.com/…/Arun-Kishan-Process-Management-in-Windows-Vista

  7. anonymouscommenter says:

    [CREATE_PROCESS_DEBUG_INFO? -Raymond]

    Thanks, but I should have made clear that I was talking about when the debugger attaches to an existing process (DebugActiveProcess).  In that case, CREATE_PROCESS_DEBUG_INFO does not fill out the lpImageFile field.  You get notifications for all the DLLs that were already loaded, but no hints for the initial executable.  In these cases, GetModuleFileNameEx frequently works but not always.

    [It doesn't fill in the lpImageFile? That sucks. -Raymond]
  8. anonymouscommenter says:

    [But of course you would never ship an app that called an undocumented API like K32GetMappedFileNameW, right? -Raymond]

    Undocumented API?  msdn.microsoft.com/…/ms683195(v=vs.85).aspx

    Even the "K32" part is documented on that page…  Sorry, I don't know what you mean.

    [Ugh. Once again, documentation trying to be helpful by providing non-contractual implementation details. -Raymond]
  9. anonymouscommenter says:

    As Myria already pointed out, the executable is mapped into the new process ahead of time.  One interesting feature is that if you're using CreateProcessAsUser (or variants) that mapping uses the security context of the parent, not that of the child – I took advantage of that once, to allow a service to launch an application in the user's context while ensuring that the user couldn't launch the same application directly.  (The ACL on the executable file denied read and execute permissions to the user, but granted them to the service.)

  10. anonymouscommenter says:

    Come to think of it, I wonder if the new process inherits a handle to its own executable?

  11. anonymouscommenter says:

    Well, those implementation details (about K32xxx) are already documented, in the PSAPI.H file (not findable only through groveling for exported names), so MSDN is just restating published info.  

    MSDN also says "Programs … should always call this function as GetMappedFileName.", so that info about K32xxx – forget you ever heard it.

  12. anonymouscommenter says:

    @I heart MSDN: The part you omitted in "…" is very important: "Programs that must run on earlier versions of Windows as well as Windows 7 and later versions".  MSDN is saying to use GetMappedFileNameW when you must support pre-7 versions of Windows, but you don't have to when you only target 7 and later.

  13. anonymouscommenter says:

    @Harry Johnston: Keep in mind that a user has generally access to OpenProcess(PROCESS_VM_READ) on processes running as them.  You can use this to read the executable from memory, effectively bypassing the lockout against read access to the file.  Even if you were to make the process inaccessible to the user using an ACL, because its user owns the process object (in the standard CreateProcessAsUserW implementation), another process running as that user can use OpenProcess(WRITE_DAC) and change the ACL.  There might also be issues like SetWindowsHookExW.

    In short, don't rely upon the security of read-proofing an executable while still executing it.

  14. anonymouscommenter says:

    I think injecting a thread is even more crazy than Read­Process­Memory.

    Does the injected thread leave behind any detectable trails after it's gone?

  15. anonymouscommenter says:

    The name K32GetMappedFileNameW is mapped in a #define, which means that the executable that calls it will have the K32-name compiled into it. That makes it contractual, the same way that FooW and FooA are contractual even though the documentation is only for function Foo.

    And the documentation for CREATE_PROCESS_DEBUG_INFO states "The system also does not provide this information in the case of debug events that originate from a call to the DebugActiveProcess function", so this aspect is also contractual.

  16. anonymouscommenter says:

    IMHO GetModuleFileNameEx is that kind of functions that are buggy by design. It doesn't hold loader lock, thus even for completely initialized and alive process it may randomly fail or even return garbage

  17. Killer{R} says:

    Remembered one more thing about Create­Tool­help­32­Snapshot. As noted, it creates thread remotely that queries information. This allows to correctly query information always BUT has another annoying sideeffect. If target process stuck with loader lock (or some other important synch) held – then that thread also stuck. And subsequently Module32First/Next (and may be some other *32First/Next) also stuck waiting that thread. However there is (was?) never documented trick. Toolhelp uses (or used at that times) alertable wait to wait remote thread finish. So it was possible to almost gracefully 'cancel' stucked toolhelp call by calling QueueUserApc(ExitThread,..) onto a thread that called toolhelp.

    However since alertable wait in toolhelp was never documented – I can guess it could also cause bugs in software that doesn't expect their APC will be processed during Module32**t…

  18. anonymouscommenter says:

    @Myria: good point.  In my case that didn't matter; I wasn't trying to prevent users from intentionally pirating the application (doubtless they could find a copy online if that was what they wanted) I just needed to keep track of how many instances were running at any one time.  It also should be safe enough in a sufficiently locked-down environment, i.e., an application whitelist, assuming said whitelist doesn't need to include a debugger. :-)

    (Is it straightforward to reconstruct the executable file from the contents of the process address space?)

Comments are closed.

Skip to main content