Isn’t there a race condition in GetFileVersionInfoSize?


In response to my explanation of what the lpdwHandle parameter in Get­File­Version­Info­Size is used for, Steve Nuchia wonders if there's a race condition between the time you get the size and the time you ask for the data.

Yes, there is a race condition, but calling the function in a loop won't help because the Get­File­Version­Info function does not report that the buffer is too small to hold all the version data. It just fills the buffer as much as it can and truncates the rest.

In practice, this is not a problem because you are usually getting the versions of files that you expect to be stable. For example, you might be obtaining the version resources of the files your application is using in order to show them in diagnostics. The file can't change because you're preventing them from changing by using them. In the case that the file changes out from under you, then yes, you will sometimes get partial data.

While I'm on the subject of Get­File­Version­Info, I figured I'd mention that there's a good amount of code in Ver­Query­Value to handle the following scenario:

  • On Windows NT 3.1, a program calls Get­File­Version­Info to obtain a file version information block.
  • The program writes the information block to a file.
  • The file is preserved in amber for millions of years.
  • A curious scientists discovers the file version information block, loads it from the file back into memory, and calls Ver­Query­Value.

The modern implementation of Ver­Query­Value still understands the file version information block created by all previous versions of Windows, and if you hand it one of those frozen-in-amber information blocks, it still knows how to extract information from it. It may not be able to do as good a job due to the lack of appropriate buffer space, but it does at least as well as the version of Windows the file version information block was originally generated from. I have no idea whether anybody actually takes advantage of this behavior, but since persisting the file version information block was never explicitly disallowed in the documentation, one could argue that doing so was legal, and the code therefore needs to be ready for it. (Heck, even if it were explicitly disallowed, there would still be a good chance that there's somebody who's doing it.)

What Ver­Query­Value doesn't handle is people who hand it a file version information block that never came from Get­File­Version­Info in the first place.

Comments (18)
  1. John says:

    Aren't GetFileVersionInfo and VerQueryValue merely convenience functions?  You could implement equivalent logic yourself via FindResource and friends and parse the data yourself.  That way you could have the function allocate the memory rather than having the caller need to make two calls with a potential race condition.

  2. EMB says:

    Raymond "(Heck, even if it were explicitly disallowed, there would still be a good chance that there's somebody who's doing it.)"

    Yup. Nature Always Finds A Way. VersionRaptors.

  3. Arguably, almost all functions are merely or largely convenience functions, for varying levels of convenience: the Win32 file APIs save you having to create counted strings and call the Native API, the file system itself saves you from having to read and write individual disk sectors or network packets. Memory allocation? You could just grab whole bunches of pages and keep track of which bits you're using. C structs? You could just use pointer arithmetic in a block of memory… More relevantly, if you're going to parse the raw data yourself, you could skip FindResource too and parse the PE file format directly, but it doesn't have much appeal when a cleaner interface does the same job.

    "I have no idea whether anybody actually takes advantage of this behavior, but since persisting the file version information block was never explicitly disallowed in the documentation, one could argue that doing so was legal"

    Yes – not hard to imagine a use case where a bug-reporting or other diagnostic tool walks a list of the DLLs the product cares about, dumping the version info block from each into a diagnostic file for later analysis, which will probably entail using VerQueryValue to unpack the data.

    (Even if version compatibility were explicitly disavowed, I can imagine someone poring over a crash dump: "hm, the code croaked trying to decipher this version info structure – weird, what's that squirrel noise doing in the middle of the string? Is it corrupt? Let's try VerQueryValue and see if the structure's parseable.)

    Talking of versions, I had a fun bug report (not my code!) yesterday: something "not appearing properly on IE 7". Illustrated with a screenshot – of IE 6. It actually identified itself as being IE 7 in the About dialog – but it wasn't: the user had skipped the reboot after installing, so mshtml.dll and co were still the previous version until the delayed MoveFileEx operation could be completed. In this particular case I saw the user's screen and knew the window was wrong for the version it claimed to be; presumably why Microsoft Office gained a button on the About dialog to launch msinfo32.exe and check the actual versions of things rather than relying on the user's deductions or assumptions.

  4. John says:

    @jas88:  If you want to take it that far why stop there?  All of these abstractions eventually boil down to moving electrons around.  This particular instance is just a Win32 wrapper around other Win32 functions.  It was my impression that in general Microsoft doesn't provide wrapper functions that you can implement yourself.  On the other hand I suppose this is more akin to something like LoadBitmap where the raw resource is converted into something more manageable to work with.

  5. WS says:

    (OT) Sadly the undocumented VerQueryValueIndex function was removed in Vista just because explorer's propertysheet was dumbed down…

  6. chentiangemalc says:

    if you are worried about this race condition I feel sorry for you. do you ever complete a software project?

  7. DWalker says:

    @John: "It was my impression that in general Microsoft doesn't provide wrapper functions that you can implement yourself."  I think jas88 is right; many Win32 function calls/APIs/COM interfaces ARE just wrapper functions that we COULD implement ourselves if we wanted to.

  8. John says:

    @DWalker:  You guys are taking my comments too literally.  Why stop at Win32/COM when you have the BIOS, boot loader, file system, kernel, etc?  GetFileVersionInfo is probably a bad example because the parsing logic is non-trivial.  The point I was trying to make is that if Win32 provides building blocks like OpenStream and DecryptStream it generally doesn't also provide a convenience wrapper like OpenAndDecryptStream.

  9. Anonymous Coward says:

    So to be sure you'd have to lock the file and the MUI file if any. @chentiangemalc: Some of us develop runtime libraries, language bindings, frameworks, &c. and we have to worry about things like this. Because if something can be fixed by us, the fix is a fix for everyone (or at least, everyone we care about) and in such a scenario fixing it is clearly the right thing to do.

    In any case, this should have been implemented as a GetFileVersionInfo / FreeFileVersionInfo pair.

  10. GregM says:

    Anon, just because you CAN "fix" it, doesn't mean that you SHOULD.  If 99.999% of your customers don't care about it, but do care about performance, then you shouldn't do it, and let the 0.001% that need the information to be exactly current and correct (at least until the call returns, at which point it can be wrong again), then let them handle it.

  11. Anonymous Coward says:

    @Raymond's reply to David Walker @ first link in article: That is the premise of most call-in shows, yes. Otherwise you'd just phone the guy or write him a letter. But a call-in show, like say a blog, is a public forum where the host establishes a topic for people to *** about. Of course, some hosts (especially on Fox) cut off everyone who says something they don't like. They are asses.

  12. Arno says:

    I am still using VerQueryValue on in-memory modules rather than blocks obtained through GetFileVersionInfo. I found that for the kind of files I am doing it on (Office .EXEs and .DLLs) and the kind of information I want (version numbers), it is working ok, while GetFileVersionInfo often does not work. Why? Because it needs a file, but when Application Virtualization is running on the customer's machine, GetModuleFileName often points to nowhere!

  13. jefito says:

    @John, re "moving electrons around": XKCD is there already (per usual); http://xkcd.com/378/

  14. cheong00 says:

    I wonder why "cat" is mentioned in the XKCD comics but not "sed". I think that's more popular among programmers and fits better in the theme of "editors".

  15. TC says:

    chentiangemalc:

    if you are worried about this race condition I feel sorry for you. do you ever complete a software project?

    What's your definition of "complete a software project"? To shovel loads of poorly written and buggy code, out the door, as fast as you can?

  16. chentiangemalc says:

    Re @TC has anyone actually had issues caused by this race condition? To me is this is a theoretical potential problem, not a practical one. Of course with good real world examples will change my mind.

  17. TC says:

    @chentiangemalc

    Solid (robust) coding is all about saying: "what if 'x' happens?" – even if 'x' is unlikely – in fact, *especially when* 'x' is unlikely!

    Your previous comment seemed to say, why bother when the case is rare? My view is, that a careful consideration of rare cases, is what distinguishes solid code, from buggy code.

    The solid coder thinks carefully about rare cases! The buggy coder doesn't :-)

  18. @TC says:

    I'm with you in general about your distinction between "solid" und "buggy" coders, but you have to draw a line here: When checking file version, the only hazard-free solution would be to provide your process a stable view at any file, by stopping all multi-tasking, or by making the file system(s) at the computer readonly to every other process.

Comments are closed.

Skip to main content