Throwing garbage on the sidewalk: The sad history of the rundll32 program


During the development of Windows Vista, the application comaptibility team traced a bunch of issues back to people corrupting the stack by using the rundll32 program to call functions that were not designed to be called by rundll32.

The problems were often subtle. For example, a batch file which used rundll32 incorrectly ended up hanging because the rundll32 process never returned. The misaligned stack resulted in registers being restored from the stack incorrectly, and then the cleanup code inside rundll32 ends up getting confused and wedging itself. The programs got away with it on previous versions of Windows by sheer luck. The version of the compiler used by Windows Vista contains different optimizations, and it ended up arranging stack variables and using registers differently, and what in previous versions of Windows was some corruption that went largely unnoticed became corruption that resulted in the program getting stuck in an infinite loop. Lucky no longer.

I was asked to come up with a solution for this problem, to fix the rundll32 program so it was more resilient to people who used it incorrectly. To fix other people's bugs for them.

The solution: Before calling the function, push a hundred bytes of garbage onto the stack (in case the called function pops too many bytes off the stack) and save the stack pointer in a global variable. After the function returns, restore the stack pointer, in case the called function pops too many or too few bytes off the stack. I think I may even have saved the processor registers in global variables, I forget.

Do not consider this free license to continue abusing the rundll32 program. When the pet store opens on Sundays, that doesn't mean that it's okay to keep throwing garbage on the sidewalk.

Comments (51)
  1. Cesar says:

    It would be interesting if rundll32 verified whether the stack/registers were corrupted (by comparing the returned values with the expected values after the return), and complained loudly to the event log in that case (it cannot complain to stderr since that could break too much).

    Sadly, even that would be risky. What if, for instance, the garbage passed to the called function made it corrupt the heap, such that it is no longer safe to call into the event log code? Or what if the event log is set to a limited size and configured to BSOD the machine if it ever fills up (I recall seeing an option to do that somewhere)?

  2. Ivan K says:

    Run, Raymond, run! From the garbage. Or not. Thanks, legend.

  3. Dan Bugglin says:

    @Cesar That wouldn't work, who looks at the Event Log unless their computer is broken?

    Now if the garbage call blew up with an error or otherwise alerted the user, but ONLY while a debugger was attached, the dev can think "stupid new Windows" all they want, but they'll know to go and fix the call (ideally), while any app not being debugged will continue working.

    ["We run all our tests with the debugger, and the debugger is detecting an error in code we did not write (and therefore cannot fix). How can we disable this error so that our tests continue to run?" -Raymond]
  4. Cesar says:

    @The MAZZTer: debugger? Why would they have a debugger attached to rundll32? The example Raymond mentioned was a batch file – probably they did not even have a debugger installed on that machine.

    True, people usually ignore the event log, except when things are broken – and then they would see the scary rundll32 error message, even if it is not what they were looking for.

  5. MarcBernard says:

    Why isn't it free license to continue the abuse?  When Raymond will come along someday and sweep up after the elephants…

  6. Jeff Curless says:

    The way I worked around this exact issue was to setup a __try/__except block with some inline assembler and then use the fact that the previous ebp is stored at fs:[0] to properly restore the stack.

  7. Raph says:

    That fix won't work on x64 because it would misalign the stack (100 isn't divisable by 8). I suggest changing it to 128.

  8. Joshua says:

    I would have turned around and written rundll32.exe in assembly language (there's so little code to write anyway), passed a bunch of zeros after the arguments (just like Raymond did) and call ExitProcess directly afterwords so as to not trust sp after the call.

    [You can't call ExitProcess immediately afterwards because you have to clean up the window handle. And writing rundll32.exe in assembly language means that porting to a new architecture means having to rewrite rundll32 for the new processor. (How good are your ia64 assembly skills?) So much for being a portable operating system. -Raymond]
  9. Alex Grigoriev says:

    [having to rewrite rundll32 for the new processor. (How good are your ia64 assembly skills?) So much for being a portable operating system. -Raymond]

    Raymond,

    Did you miss the memo about IA64 support discontinuation in 2008R2+?

    Also, it's perfectly fair to say: "If you want your DLL be called by ARM build of rundll32, fix YOUR DLL". Stop being such a (offensive word deleted), MS. Show some tough love to ISVs.

    [I made the apparently unwarranted assumption that you were not a nitpicker and could figure out that "ia64" was a placeholder for "some new architecture." I would have deleted your comment for offensive words except Cesar replied to it. (See Cesar's reply.) -Raymond]
  10. Cesar says:

    @Alex Grigoriev: the problem Raymond is talking about is not in the DLLs. The problem is in the users incorrectly trying to use rundll32 to call DLL functions which do not have the exact function signature rundll32 was designed for ("void CALLBACK function(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow)"), and thus messing up the stack badly (see the blog post Raymond linked to).

    [Exactly. Tell this guy "Hey you need to fix the user32.dll DLL." -Raymond]
  11. John says:

    I don't think people are intentionally abusing rundll32 (well, maybe some are); I think most people just don't know any better.  As for the bigger picture, I'm not sure what you're complaining about; you should know better by now.  If a backward compatibility hack allows 'bad thing X' to continue to work, then people will continue to do 'bad thing X' (if for nothing else than out of ignorance); there is just no way to get around that.

  12. Alex Grigoriev says:

    @Cesar:

    MSC compiler supports stack pointer checking for quite some time. If a callback function returns with incorrect ESP/RSP, rundll32 could throw an exception.

    [And then a batch file stops working and the customer has to… um, do what exactly? "Don't upgrade to the next version of Windows. Your batch files will stop working." -Raymond]
  13. mgetz says:

    @Raph: actually on x64 this is a moot post, all the calling conventions have been superseded by the AMD64 convention which should be the only one used

  14. Adam Rosenfield says:

    "And writing rundll32.exe in assembly language means that porting to a new architecture means having to rewrite rundll32 for the new processor."

    But doesn't the workaround you wrote of saving and restoring the stack pointer via a global variable require assembly language, or at the very least an architecture-specific compiler intrinsic?  That's certainly not 100% portable, e.g. if you had to port this to some bizarre architecture that didn't even have a stack pointer.

  15. Klimax says:

    @Adam Rosenfield 9 Sep 2011 12:14 PM:

    Intrinsic funcitons,macross and/or template functions from C++… This is not that difficult as you are writting small function inside programm,which you can at worst disable until support written. (Rest is compiler+assembler job)

    If you have to rewritte whole programm,then you have to be completly finished.

  16. 640k says:

    It should be called rundll64.exe on 64-bit windows. Problem solved.

  17. Adam Rosenfield says:

    @MAZZTer: Who (among application developers) debugs rundll32?  Maybe you would if you're debugging your DLL, but if you were doing that, wouldn't you be debugging it in its native environment, i.e. when being loaded by the executable that normally loads it?  How many people use rundll32 to run functions from their own DLLs?  I would expect it to be used much more frequently to try to use functions from *other peoples'* DLLs, for which you don't have the source code and thus are more likely to get the function parameters wrong.

  18. Joshua says:

    Bad idea, 640k. We were just discussing breaking batch files.

  19. ErikF says:

    It seems that some people are wanting the equivalent of a gasoline-powered engine that can also run on diesel, kerosene, hydrogen and raw sewage. Raymond, I don't envy your job; not only do you have to support programs that rely on old behaviour, but you also have to support programs that by all rights should never have worked at all!

    I suppose it's too late to just deprecate the silly thing and move on.

  20. Anonymous Coward says:

    As Marc suggests, this *is* a free licence to continue the abuse. It's even questionable whether it's still abuse, considering that rundll32 has been explicitly written to handle the situation. It's expected behaviour now.

  21. 640k says:

    Why was it called runndll32 in the first place? Why not only rundll? Please tell me this isn't yet one more stupid mistake of ms employees inveting stuff which isn't forward compatible.

    Naming exe (or dll) files with the bitness in the name? Why does anyone do that? If there's no reason, it's obvious stupid. So what is the reason this time?

  22. Ens says:

    Why be antagonistic in the first place?  If there's no reason, then obviously you just want to make the world a worse place.

    Rundll.dll was the 16 bit version.  So in the 32 bit switchover they tried it your way.  Maybe they decided that was a bad idea, or they decided that circumstances were different this time around.

    But anyway, what problem are you solving by renaming it?  I haven't seen anybody say anything in this post that would be solved by renaming rundll on a 64-bit system.  The only discussion of bitness at all was about hand-coded assembly language, and the little sidebar about stack alignment, neither of which have anything whatsoever to do with how rundll is named.  I think you just said "problem solved" without figuring out what the problem was.

  23. 640k says:

    So we are stuck with name32.exe forever? Sigh.

    The same problem solved by renaming rundll.exe to rundll32.exe on 32-bit systems will be solved by renaming it to rundll64.exe on 64-bit systems.

  24. JamesNT says:

    Raymond,

    I am usually one of the first to defend you.  You are truly my programming god.  And, yes, I still have that pic you and Paul Thurrot made for me.  

    Today, it is my sad duty to confess that the pull – nay, the seduction – of the dark side is upon me. Truly, my heart doth skip a beat thereby depriving my entire body of needed nutrients when I say much to mine own horror: Why not break such apps to teach a lesson?

    Help me, Obi Wan Chen. You are my only hope.

    JamesNT

    [Who is the "they" you are teaching a lesson, and what is the lesson? The "they" is the customer. The lesson is "Don't upgrade to Windows Vista. It breaks applications." The developer who wrote the bad code is long gone. -Raymond]
  25. Clipboarder Gadget says:

    I don't understand why so many people here complain about this fix. It doesn't slow down the computer, it requires no diskspace and there is no undefined behaviour when apps abuse rundll32.exe. The fix also doesn't require any major redesign and is very unlikly to break well-behaving applications. It also doesn't complicate the way to develop good applications.

    I'd say it's an easy decision to include this fix.

  26. 無名 says:

    I'll quote the end of one of the bonus chapters from your book, which sort of fits here:

    "Thank you for going insane."

    :-)

  27. JamesNT says:

    Raymond,

    I knew I could count on you to bring me back to the world of the pragmatic programmer.  It is important for all of us to remember a few details:

    1.  The programmers that caused these issues may not be bad programmers, just beginners.  These could be honest mistakes, but users still need their apps to run.
    2.  The programmers that caused these issues may have just being doing the best they could with the tools/time they had.

    3.  The victims here are the users.  Someone, somewhere must advocate for them.  Microsoft (read:  Mr. Chen) are really the only ones in a good position to do so.

    4.  Yes, it sucks that MS spending time correcting the mistakes of others creates a rather horrible opportunity cost, but the alternative to not paying that cost is worse.

    Mr. Chen, your position as my programming god remains ever secure.  

    Thank you for all you do.

    JamesNT

  28. Gabe says:

    JamesNT: It's not programmers who "caused" the issue, it's users. What happens is that somebody wants to invoke a function from a DLL. A programmer has a compiler and can just write a trivial program that calls the function. A user, however, has no compiler. When they want to invoke a function locked up in a DLL, the only tool they have is rundll32.

    Since users know nothing of arguments and calling conventions. They just found out somehow that there's a function that exists in a DLL, and putting it on the rundll32 command line does what they want. They don't know why it does what they want and are not qualified to determine that it only works by coincidence. You can call these users "programmers", but that's stretching the term.

    Look at what vlaurie.com/…/rundll32.htm has to say about swapping mouse buttons:

    "Another example is a command that allows you to switch the mouse-buttons for left-hand use. Unfortunately, once the switch is made it seems that it can only be undone by the old-fashioned method of going to Control Panel. In other words, it doesn't act as a toggle but seems to be one way. The command is

    "RUNDLL32.EXE USER32.DLL,SwapMouseButton"

    Some user reads this, learns some "trick" to do something, puts it in a batch file or shortcut, and expects it to keep working.

  29. Privacy Statement says:

    Gabe: That makes them programmers. It's a fine line.

  30. Bob says:

    "RUNDLL32.EXE USER32.DLL,SwapMouseButton"

    This demonstrates the saying that "a little knowledge is a dangerous thing"

  31. Cesar says:

    @Clipboarder Gadget: Most people here are programmers, and will react with horror to such an horrible kludge. It might work perfectly and have no downsides (other than the psychological one of letting people get away with things they shouldn't), but it is an eyesore.

  32. TC says:

    [And then a batch file stops working and the customer has to… um, do what exactly? "Don't upgrade to the next version of Windows. Your batch files will stop working." -Raymond]

    Sort of like when upgrading from Win XP to Win 7, and loads of code stops working because CAPICOM has been, uh, how can I put this – arbitrarily deleted for no apparent reason?

    MS (as an organization) clearly puts lots of effort into backwards compatibility – when it suits you. But when it doesn't, you don't give a fig!

    So please don't use that argument as if it were the "be all and end all" of whether you do or don't make breaking changes. MS has no hesitation in making breaking changes, for no apparent technical reason, when it suits you.

    [You appear to be under the impression that there is a single "compatibility committee" that makes all compatibility-related decisions with unfailing consistency according to rigid absolutes. -Raymond]
  33. Gabe says:

    Privacy: Even if that *does* make them programmers (and I really hate using the term programmer for somebody who writes things like macros, HTML pages, or command lines), they're not the programmer who wrote rundll32 or the DLL that's causing the problem. In fact, my rudimentary web searches show that it's mostly MS who writes the DLLs that get abused by rundll32!

    My point to JamesNT was that in this case, the people in details 1 and 2 ("programmers") were actually the same as the victims in detail 3 ("users").

  34. Random832 says:

    "And then a batch file stops working and the customer has to… um, do what exactly?" Create a C program to call the DLL properly – same as with your objections to writing undocumented registry keys with regedit rather than calling the documented API.

  35. Gabe says:

    Random832: So you would tell customers that if they want to upgrade to Vista they will have to download a C compiler, learn C, and then create a C program to call the DLL properly? I think Microsoft actually WANTS customers to upgrade, which is why they used Raymond's scheme instead of your plan.

  36. TC says:

    I agree that (2) is foolish. I've never said or suppurted that.

  37. Christian says:

    Thanks you, Raymond (and all the others at Microsoft doing similar things), for all the times you fix problems like this and just make stuff work. With the days and weeks you spend doing such "horrible", but sensible hacks you safe sooo many humans around the glob so many years of time!

    Everytime someone just makes his program compatible, keep working, working around bugs instead of complaining, they should pat their own back and thing that they did a good deed today

  38. TC says:

    [You appear to be under the impression that there is a single "compatibility committee" that makes all compatibility-related decisions with unfailing consistency according to rigid absolutes. -Raymond]

    On the contrary. *You're* the one who implied this, when you told Alex G that MS couldn't modify rundll32 to throw an error, because that would break existing code. I responded to that implication, by pointing out that there *isn't* a consistent policy across the organization (and gave an example).

    (sorry if multiple posts, the comment form is acting up)

    [I did not intend to give the impression that the explanation came from the "compatibility committee's book of rigid absolutes." But when the two sides of the issue are (1) don't break existing code, and (2) teach "people" a "lesson" (for some vague sense of "people" and "lesson"), the "don't break existing code" side tends to win. -Raymond]
  39. 640k says:

    64-bit dlls has a single defined calling convention (atleast in windows). Problems which rundll32 has wouldn't be possible with rundll64.

    [Consider what goes wrong if you call a function that accepts more than 4 parameters. -Raymond]
  40. Alex Grigoriev says:

    Too bad that rundll32 has essentially become a poor man's scripting tool.

    I''m not quite sure when cscript.exe has become first available, but one can run VB and JScript from command line easily.

    Here is a fun exercise for you.

    Create a text file named, for example, speech.vbs, with the following line:

    CreateObject("SAPI.SpVoice").Speak "Do not consider this free license to continue abusing the rundll32 program"

    Then type speech.vbs in the CMD prompt.

  41. cheong00 says:

    How about making rundll32 depreciated in this case? Afterall for many many things we want to do the involves trival calls to APIs, either there's already freeware/shareware doing that (still remember someone making a program that's probe current COM port settings for US$9.99), or you can do it with functions provided in PowerShell.

  42. Yuhong Bao says:

    Yea, PowerShell allows you to call anything that can be called from .NET from the command-line, including all .NET Framework API functions.

  43. 640k says:

    99% of all api functions which are called from command line/batch files uses <5 args. There's always limits anyway, you can set it at 4 args and make many people happy.

  44. Gabe says:

    640k: Are you suggesting that rundll32 should have a listing of all APIs in the world that accept up to 4 args and only allow you to abuse those?

  45. cheong00 says:

    @Gabe: If they want to make such a list, it'd be easier to let them create a list of "safe to run in rundll32" API.

    That's just involve test cases to limited list of commonly "recommanded to used in rundll32" APIs. Run them to see if it'll corrupt the stack / heap. Just declare those won't case problem "safe".

    Now if you use rundll32 to call "other" API, Microsoft makes no guarantee that it'll work. And if it works, it's by pure luck. If it don't, don't complain, period. :P

    [What's the difference to the end user between "known safe" and "if it works, it's by pure luck"? In both cases, it "works". How would an end user know whether they were in the "known safe" case or the "pure luck" case? -Raymond]
  46. Neil says:

    This makes me glad that the only problem I had with RunDLL32 was that it created hidden owner windows which meant that they didn't show up in the Windows 95 Alt+Tab list.

  47. Gabe says:

    Neil: The only problem I had with rundll32 was that it corrupted the command line in its initial parsing. It was probably easier to fix than what Raymond had to do.

  48. cheong00 says:

    > What's the difference to the end user between "known safe" and "if it works, it's by pure luck"?

    None for the home users, but hopefully those who know enough to create tips that uses rundll32 will check the list to see whether the method they're about to suggest is "safe". (It may not work the first few times, but eventially they would be pointed to the list when some behaviour is unexpected in certain cases, and learnt to check before making suggestion.) I don't think average home user will use rundll32 without being told to do so by some random "experts".

    Anyway I don't think Microsoft is going to make such list (just making it depreciated is easier), so little point to argue on this topic.

    [I don't have confidence in the "eventually they would be pointed to the list when they encounter some weird behavior" because that didn't stop people from abusing rundll32 today. The weird behavior may not exhibit itself until the next version of the OS, at which point it's too late. -Raymond]
  49. cheong00 says:

    The point is to have a list spell out which API is safe for this program to call. You may also add different section for each tested OS.

    If they use the program to run API and found it hurts, they'll know there is a URL they can check the information. If they can't find the API call there, they'll know this call may not work in that OS version (Especially if one API is marked as safe in one of the previous OS, but removed in a later one. That usually indicate the API have problem in that OS or later). Hopefully the "expert" who want to repost the tips will notice this sign and add more workaround that involve any of the freeware/shareware/PS script equivalent that'll do the job.

    [Okay, here's your list: (1) Control_RunDLL (for debugging purposes only. That's all. -Raymond]
  50. 640k says:

    @Gabe: Are you suggesting that rundll32 should have a listing of all APIs in the world that accept up to 4 args and only allow you to abuse those?

    No. I suggest that rundll64 should display an error message if there's more than 4 args to it.

    [I don't think you understand what rundll32 does. -Raymond]
  51. 640k says:

    32-bit is doomed to fix anyway. You better focus on fixing 64-bit rundll. PLEASE don't make the same mistakes as with 32-bit rundll.

Comments are closed.

Skip to main content