Could there be any problems with calling GetModuleFileNameEx on your own process?

In response to my discussion of why you can get an ERROR_INVALID_HANDLE from Get­Module­File­Name­Ex even though the process handle is valid, Joshua asks, "Calling such methods as these on your own process has no such caveats, right?"

Well, one of the issues is that the process you are querying from hasn't yet completed its initialization. That's not an issue here, because the call is coming from within the process itself. (If it weren't initialized, then your code wouldn't be running.)

Another issue is that "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." That issue doesn't go away just because you're making the call from within the process. Another thread in the process might be in the middle of a Load­Library call, and the module manager is adding a new entry to its table to track the new module. If you try to inspect the module table while it is being updated, you might see a partially-loaded module, you might see a linked list that is temporarily corrupt (because it is in the middle of being rewritten), you might get a corrupt string (because the file name length was copied to the entry, but the characters of the file name haven't been copied yet), or you might get an access violation (the pointer to the string hasn't been initialized yet). Any of those things can result in Get­Module­File­Name­Ex failing, even when called on its own process.

Fortunately, there's a solution: Don't use Get­Module­File­Name­Ex to get information about your own process. Just use the regular Get­Module­File­Name function. This function queries information for the current process, and since it always runs in-process, it can use critical sections and other synchronization objects so that it can gain access to the shared information, making sure that nobody is modifying the data structures while it is reading them.

As noted in the original article, "These APIs don't really work by the normally-accepted definitions of 'work'." They are best-effort, and sometimes the best effort fails.

Comments (21)
  1. Yuhong Bao says:

    Part of the problem is that GetModuleFileNameEx was originally a PSAPI function, despite its function name.

  2. Brian_EE says:

    Doesn’t this boil down to the 2nd paragraph of the Remarks section in the MSDN documentation for Get­Module­File­Name­Ex?

  3. Joshua says:

    Another mauled import. Most of the comments are missing from the linked article.

    1. Alexander S. says:

      One of the few comments present in that article even references the linked comment, which isn’t present.

    2. That’s just sad. There’s usually a lot of good information in the comments of these articles. Wonder if the WayBack machine has all of these articles captured?

      1. skSdnW says:

        The old blog used some type of javascript to load the comments and could not really handle it so not all the archived posts have working comments, depends on the year, the older stuff is probably ok. Even MS KB articles are broken on now because of silly javascript design for what should just be a page with some text!

  4. ranta says:

    The IsBadReadPtr topic in MSDN Library warns that “Dereferencing potentially invalid pointers can disable stack expansion in other threads”, presumably by swallowing the guard page exception. Is there a similar risk when using invalid pointers with cross-process functions like Get­Module­File­Name­Ex or ReadProcessMemory? I hope the kernel would not raise the exception in this case (regardless of whether the handle refers to the calling process) and would not reset the PAGE_GUARD bit either.

    1. Darran Rowe says:

      IsBadxxxPtr functions should be wiped clean from your memory, completely.
      I would assume that ReadProcessMemory/WriteProcessMemory would check the VM tables using the virtual memory management functions, like VirtualQueryEx. This would allow you to check the status of the memory page that the pointer is associated with but do it in a way that doesn’t just dereference it and then swallow any exceptions.

      1. immibis says:

        Can someone explain why the IsBadXxxPtr functions don’t just call VirtualQuery and check the result?

        I mean, they still wouldn’t be very useful, but at least they’d do what they say on the tin, and not break unrelated things.

        1. Fire up that time machine! IsBadXxxPtr was introduced in 1989, four years before VirtualQuery.

          1. immibis says:

            So why not change it now?

            Are there likely to be applications that rely on it silently removing guard page status?

        2. Why bother changing something that is marked “Don’t call this!”?

          1. immibis says:

            Because if it was changed, it wouldn’t need to be marked “Don’t call this!” any more?

        3. Darran Rowe says:

          The problem is, even if it was changed, it would still be useless. While checking the VM related stuff would avoid the situation where it eats the guard page exception, it would still end up with the rest of the problems. In this case !bad != good.
          The major reason why that set of functions are frowned upon is that the pointer you pass it can point anywhere, if you get the base address of your executable image and pass it to IsBadReadPtr, then it will come up with it being readable, if you create a new heap, never allocate anything in it and grab any pointer from that heap and pass it to IsBadReadPtr or even IsBadWritePtr then it will being a valid pointer. The problem is, in the first case, reading the executable is normally pointless, and in the second case it is obviously not valid due to the heap never being allocated.
          So the problem with this set of functions is that it doesn’t verify if the pointer is good, it can’t for hopefully obvious reasons, all it can do is verify that you are able to access the memory.

          1. immibis says:

            As I said already, they would still not be very useful, but at least they would do what they’re supposed to.

        4. Darran Rowe says:

          Well, if you think of it from the implementers point of view, you change the function from just trying to dereference a pointer and just eating SEH exceptions to actually checking the VM page properties. But at the end of the day the reason why you shouldn’t use them hasn’t changed, so you have just put work into functions that will never be called.

  5. Myria says:

    Or just use QueryFullProcessImageName (or GetProcessImageFileName before Vista) to get your own EXE’s name. These functions don’t rely upon the LDR tables at all.

    1. poizan42 says:

      And GetMappedFileName can be used on any module and works even when the process is newly created or it bypasses the normal loader. Actually you probably shouldn’t ever use GetModuleFileName[Ex].

      1. immibis says:

        Doesn’t GetMappedFileName rely on the implementation detail that modules are memory-mapped?

        1. poizan42 says:

          That is true I guess. But it has worked that way for as long as Windows has had memory mapped files AFAIR, and it doesn’t seems sensible to do it any other way. Maybe there could be special kinds of memory mapped files in the future, but Microsoft kinda have to support those by GetMappedFileName as well or a lot of existing software will break.

          This may not be what Raymond likes to hear, but fact is that the slightly undocumented way is much more robust.

        2. Darran Rowe says:

          The thing is, there is also stuff documented if you know where to look. CreateFileMapping has a flag, SEC_IMAGE, that is used to load executable files, the kernel mode version ZwCreateSection (yes, in kernel land they call it a section), actually refers you to CreateFileMapping for descriptions of these flags. There is also the SEC_IMAGE_NO_EXECUTE flag which is documented to not do the forced integrity checking of an executable and not notify driver callbacks used to notify of executable images being loaded.
          If you then do VirtualQuery, you can find pages marked as MEM_IMAGE, which specifies that the region is an executable image section.
          So while it isn’t explicitly documented, there is enough of the infrastructure visible to infer this.

Comments are closed.

Skip to main content