You cannot globally reserve user-mode address space

Date:February 15, 2005 / year-entry #39
Tags:code
Orig Link:https://blogs.msdn.microsoft.com/oldnewthing/20050215-00/?p=36443
Comments:    41
Summary:Occasionally, somebody asks for a way to reserve user-mode address space globally. In other words, they want to allocate address space in all processes in the system (current and future). Typically this is because they want to map some memory into each process and don't want to go through the trouble of designing the shared...

Occasionally, somebody asks for a way to reserve user-mode address space globally. In other words, they want to allocate address space in all processes in the system (current and future). Typically this is because they want to map some memory into each process and don't want to go through the trouble of designing the shared memory blocks so that they can be relocated.

This is obviously not possible.

Why obviously? Well, imagine if this were possible.

"Imagine if this were possible" is one of the logic tests you can apply to a theory to see if it can possibly be true. Here, we're using it to determine whether a proposed behavior is possible. [Typo fixed 10am.] (There is a corresponding thought experiment, "Imagine if things actually worked that way.")

What are the consequences of global address space allocation?

Well, first of all, there's no guarantee that by the time you request your global address space, there will be any available addresses at all. Consider a program that uses every last scrape of user-mode address space. (It can do this by just calling VirtualAlloc(MEM_RESERVE) in a loop; since no memory is being committed, this doesn't actually require 2GB of RAM.) Run such a program and no global address space allocations are possible until that program exits.

So even if it were possible, it wouldn't be reliable. Your program would have to be prepared for the situation where no global address space was available. Since you're going to have to write fallback code anyway, you didn't save yourself any work.

Next, suppose it were possible, that there were some imaginary GlobalVirtualAlloc function. Well, then I can write a program that calls this imaginary function in a loop, sucking up all available global virtual address space, and run it as a non-administrator.

I just violated security. (The general principle here is that a non-administrator should not be allowed to affect other users. We've already seen one scenario where a non-administrator can crash a program running as administrator due to insecure use of shared memory.)

My imaginary program sucked up all global virtual address space, reducing the address space available to programs running as administrator. If there aren't many programs running on the system, my imaginary program will probably be able to suck up quite a lot of address space this way, which in turn will cause a corresponding reduction in address space to those administrative programs. I can therefore cause those programs to run out of address space sooner, resulting in premature failure (denial of service).

Yes, you could decide that "global" address space reservation is available only to administrators, but that wouldn't help a lazy programmer, since the program would not work when run as a non-administrator - you have to write the fallback code anyway. Or you could decide that "global" address space reservation applies only to users within the same session with the same security token, but again, you have to write the fallback code anyway if the global reservation fails; you didn't save yourself any work. When the time comes, you can use VirtualAlloc and pass a preferred address to try to get the memory at that address; if it fails, then use the fallback code that you already wrote.

The moral of the story is that each process gets its own address space and each process manages its address space independently of other processes. (Of course, a process can grant a user PROCESS_VM_OPERATION permission, which gives that user permission to mess with the address space of that process. But changes to that process's address space does not affect the address space of other processes.)


Comments (41)
  1. Dave says:

    "Since you’re going to have to write fallback code anyway…"

    My my, such a fancy name for the ASSERT macro! :)

  2. Couldn’t something similar be achieved by creating a non-rebasable DLL and forcing it to be loaded into every process (I think there is some hook to make every process load a DLL but I could be mistaken)? I thought GDI did something along these lines to store the GDI handle table at the same address in each Win32 process. I am not suggesting this is a good idea but it might achieve something similar to globally reserving some memory.

  3. kbiel says:

    " Occasionally, somebody asks for a way to reserve user-mode address space globally."

    One might as well ask why we can’t go back to the good old days of the Win16 memory model.

  4. Mike Dunn says:

    This is actually possible on 9x, no? IIRC addresses used by memory-mapped files are always in the upper 2GB of the process space, which is shared among all processes. So you can map a block of memory once and pass the address to your child processes.

    Of course, no one’s really supposed to know this or rely on it, but that never stopped lazy programmers before.

  5. Moi says:

    Raymond – "Here were’s" doesn’t make sense. I guess you meant "Here we’re"?

  6. rnk says:

    IIRC, OS/2 shared memory was mapped to the same addresses in all processes. Made writing marshalling code trivial compared to NT. Of course, OS/2 limited processes to 512MB so there was plenty of address space to go around.

    Who would even need more than 512MB?

  7. lowercase josh says:

    Do not use asserts for things like this that you can legitimately expect to happen during normal operation. If your program crashes because of a perfectly valid system state, it is buggy. That’s what an assert would do.

  8. 640k says:

    If the system is out of ram, it’s of little help to write fallback code.

    A program isn’t buggy if the developer chooses it to require an amount of availabale ram.

  9. 640k says:

    If the system is out of ram, it’s of little help to write fallback code.

    A program isn’t buggy if the developer chooses it to require an amount of availabale ram.

  10. Mike Dimmick says:

    I concur with josh. An ASSERT is for situations where you’re self-checking data structures for consistency. If an ASSERT fires, it means you violated some fundamental assumptions, and you can’t afford the cost of checking in the release build.

    Error handling is for every day – ASSERTs are for special occasions. I often add an ASSERT to a case with error handling. Why? Because I want the debug build to tell me I messed up. But I want the release build to not crash.

    A lot of my case statements have ASSERT(FALSE) in the ‘default’ block. It catches situations where I think I’ve handled every possibility already – if I haven’t, the debug build tells me about it.

  11. cooney says:

    As Mike pointed out, Asserts dont show up in release builds, so unless you release debug builds, you will basically have no error checking for this GlobalAlloc hack.

    > IIRC, OS/2 shared memory was mapped to the same addresses in all processes. Made writing marshalling code trivial compared to NT. Of course, OS/2 limited processes to 512MB so there was plenty of address space to go around.

    Also makes your code hard to port from OS2, especially if you were lazy and didn’t isolate (or even use) marshalling.

    >Who would even need more than 512MB?

    Yeah, I think we’ve all heard that one before. My video card has more ram than my system did 5 years ago.

  12. cooney says:

    As Mike pointed out, Asserts dont show up in release builds, so unless you release debug builds, you will basically have no error checking for this GlobalAlloc hack.

  13. Dave says:

    Geez, no sense of humor around here. I agree that assert should be for debugging logic errors. But I do have to wonder what "fallback code" would do in the case where no address space is left, other than clean up and bail out of the operation.

  14. Memet says:

    Concurring with Mike and josh, I think ASSERTS are for checking invariants. OOM situations are clearly not invariants.

    (thanks for listening to me beat the dead assertion horse a little further)

  15. Skywing says:

    If you use the SEC_BASED flag with a section object (file mapping object in Win32 terms), then the section will always map at the same base address in every process that you try to map it in.

    Of course, it will fail to map if that address is already in use.

    In fact, CSRSS (the Win32 subsystem), WinSrv.dll, user32.dll, and Win32k.sys all require that certain shared memory regions be mapped at the same place in every process.

    Actually, the kernel itself requires that NTDLL be mapped at the same spot in every process as well.

    The reason these things can get away with such is they typically have a chance to set up their static allocations before any "end-user" code would have a chance to touch a new process’s address space.

  16. lowercase josh says:

    Eh, this isn’t a case where there is no address space left, nor is the system out of RAM. This would be a case where there’s no address space left IN A DIFFERENT PROCESS. It could be normal operating behavior for the other process to use its entire address space.

    Anyway, it’s very rare indeed that resources are so tight that you can’t even notify the user before dying.

  17. Fyi, there’s only a tiny bit of magic associated with how ntdll gets the same address in every process. The other ones have no magic; they have a preferred load address and they just fail if they have to get relocated.

    Ntdll’s magic is that the kernel opens it during the boot initialization sequence and it’s automatically mapped, at the same VA, in every process that’s created. Nothing really special; the kernel needs well known addresses to jump to in user mode to do things like initialize the process and start threads.

    User32’s magic is very similar. The kernel mode part of user32 needs to call back into user mode, and rather than having a per-process address to jump to (I’m pretty sure it’s about message dispatch…), there’s a single storage location in kernel mode for the trampolines. I’m not sure if it’s the right trade off but there you go.

    kernel32 is interesting because the start address for a process that’s created using the kernel32!CreateProcess* family of APIs is a function inside kernel32 (createremotethread also has this issue). We experimented with using a dynamic registration mechanism that jumped through ntdll to find the initialization entry point in kernel32 but it introduced new modes of failure and it wasn’t clear why it would be of much real benefit.

    ole32 used to have a similar issue but I’ve been told it’s been fixed and I couldn’t find what it was, so maybe it has been removed.

    fyi.

  18. Jeff says:

    I’m sorry, but those answers are quite contrived. Just document in MSDN under what conditions the function would work and when it wouldn’t. Programmers are used to writing fallback code, thats called error handling. I can quite easily imagine a framework where you could get a callback from the os each time a process started, with the process id and a bool as to whether or not the hooking worked. Of course you could always do the mapping BEFORE any of the processes modules are loaded, and that way you would ALWAYS get the address space you wanted. There are very simple ways to get this to work.

    -Jeff

  19. Jeff says:

    I’m sorry, but those answers are quite contrived. Just document in MSDN under what conditions the function would work and when it wouldn’t. Programmers are used to writing fallback code, thats called error handling. I can quite easily imagine a framework where you could get a callback from the os each time a process started, with the process id and a bool as to whether or not the hooking worked. Of course you could always do the mapping BEFORE any of the processes modules are loaded, and that way you would ALWAYS get the address space you wanted. There are very simple ways to get this to work.

    -Jeff

  20. Daveh says:

    "Well, first of all, there’s no guarantee that by the time you request your global address space, there will be any available addresses at all."

    While I agree it would certainly be a security risk, I disagree (like Jeff and others) on the other reasons. Its akin it saying you shouldn’t open files because by the time you request to open it the file might not be there or there might not be any available file handles.

  21. Jeff says:

    I disagree on the security risk. Only allow administrators to use this hook. Or make it a privilege that can be granted, like CreateGlobalObjects.

    -Jeff

  22. Jeff,

    If it’s an admin-only feature, then my application will use it, not caring that it locks the user into being an administrator. And it will fail when it gets run as a limited user.

    And we’ve got yet another reason why people surf the web as an administrator.

    Which is ALWAYS a bad thing. Administrators should NEVER be able to surf the web. It’s just too dangerous.

  23. Doug says:

    "Of course you could always do the mapping BEFORE any of the processes modules are loaded, and that way you would ALWAYS get the address space you wanted."

    And Raymond’s nasty program would prevent any other program from running at all, because it’s chewed up/reserved most of the address space of the new process.

  24. Matt Sayler says:

    Larry:

    > Which is ALWAYS a bad thing. Administrators should NEVER be able to surf the web. It’s just too dangerous.

    Isn’t that kind of backwards? I would like to think the correct assertion is "web browsers should be secure enough that Administrators can use them if they need to."

    After all, what’s fundimentally wrong with visiting a vendor web site and downloading a driver?

    Dave:

    > While I agree it would certainly be a security risk, I disagree (like Jeff and others) on the other reasons. Its akin it saying you shouldn’t open files because by the time you request to open it the file might not be there or there might not be any available file handles.

    I agree. It’s debatable whether or not this is a desirable feature (since it is more-or-less a shortcut and not "new functionality."). Programmers have to deal with globally limited resources all the time (port numbers would be another example).

  25. Dean Harding says:

    "Isn’t that kind of backwards? I would like to think the correct assertion is "web browsers should be secure enough that Administrators can use them if they need to."

    I don’t think so. If you need to install a driver you should download it as a normal user, and only run as an admin for exactly as long as you need to install the driver and no longer.

    It’s always been considered an extreme faux pas to run a web browser, email program, or irc client as root under unix. Why is Windows any different?

  26. AT says:

    P.S> BTW, Is it possible to allocate memory in address-space over 2Gb (3Gb for /3Gb tuning) ?

    One that reserved to use by system and mapped in all processes ?

  27. wd says:

    Congratulations Raymond on your 666th post! =)

  28. SHA-1 CRACKED says:

    Raymond, please publicize this. Apologies for offtopic, but this is VERY IMPORTANT.

    <a href="http://schneier.com/">Bruce Schneier</a> reports that SHA-1, a commonly used cryptographic hashing protocol, <a href="http://www.schneier.com/blog/archives/2005/02/sha1_broken.html">has reportedly been broken</a> by a prestigious research team from Shanghai University. Together with recent attacks on MD5, as <a href="http://developers.slashdot.org/developers/04/12/07/2019244.shtml?tid=93&tid=172&tid=8">previously covered by /.</a>, we need new hashing functions as a matter of urgency, and we need them now.

  29. Dean Harding says:

    "I’m sorry, but those answers are quite contrived. Just document in MSDN under what conditions the function would work and when it wouldn’t. Programmers are used to writing fallback code, thats called error handling."

    The problem is that (proper) fallback code would have to do what you do today anyway (i.e. using file-mapping objects or something), so what’s the point of a magic "GlobalVirtualAlloc" when you have to write the file-mapping code anyway?

    There’s two ways a "GlobalVirtualAlloc" could fail when it encounters a process that is unable to map the address.

    The first way it could fail is by not mapping any addresses in any processes. But this is no good, since if it fails for one process, you’d have to write your file-mapping fallback code anyway. And it’s also bad because what if it succeeds when your program starts up, but then another process starts that can’t map that address – do you then unload all other blocks and fail?

    The other way it might fail is when it can’t map an addresses in a given process, it just indicates failure *for that process* and continues to work in other processes. But again, you have to write your file-mapped fallback code, and in this case you’d have some processes with the memory mapped via this "GlobalVirtualAlloc" and some via a file-mapping.

    Personally, I don’t see why such a function would be useful anyway, surely any magic "GlobalVirtualAlloc" would be so limited in what it can do that it would only be useful in very limited circumstances, in which case you’re better off writing your own version which has the functionality you want. It’s not like it would do anything that you can’t do already, and surely you know better than Microsoft what you want to happen in the case of failure (maybe if it fails for a given process, you can live with just ignoring that process. A general solution couldn’t do that, however).

  30. AT says:

    ;-) This is possible to create memory mapped file and map it to same address in all processes you need it.

    This can be done after fact – it you will have access to it.

    As well – there were suggestion to create a DLL file – if you need this to be done in some specific processes only.

    Even more – this is possible to override CreateProcess system calls and map all pages you need – in kernel Ldr* function – even before first image will be loaded.

    Thus – even if user-binary will try to use your address as their base (no needs to reserve all memory for you to fail ;-) – it will be forced to relocate or die.

    Something not good – user process can remove your reserved region – if you will will not restrict it from doing so using some additional hacks in Mm* memory manager.

    There are amlost anything possible. The only question – is the development costs/burden.

  31. AT says:

    Larry Osterman:

    "Administrators should NEVER be able to surf the web. It’s just too dangerous. "

    Tell this to http://WindowsUpdate.MicroSoft.Com team ;-))

  32. Jeff says:

    "If it’s an admin-only feature, then my application will use it, not caring that it locks the user into being an administrator. And it will fail when it gets run as a limited user. "

    Oh come on Larry, what if I am writing an application that I know is ONLY ever going to run with privileges, and if it doesn’t it SHOULD fail.

    Your excuse doesn’t make any sense, what if I am writing a program that creates global objects, and it runs on Win2k sp4 as non-admin? It will FAIL, unless CGO privileges are setup. Now, I as a programmer need to do my job and correctly engineer my program to work right. Just because it MIGHT fail doesn’t mean we SHOULDN’T use it. If that were true I wouldn’t have even turned on Win9x!

    Remember that the use cases set the requirements. If my use case is to run a service with admin privileges and ONLY as that, then why can’t I call a function that works for admin only?

    "And Raymond’s nasty program would prevent any other program from running at all, because it’s chewed up/reserved most of the address space of the new process."

    And thats why I said Admin only. I was assuming that an admin would be smart enough only to buy from companies he trusted. Of course there are always bugs, but after the first time, I think I would boot into safe mode and uninstall rather quickly.

    "The problem is that (proper) fallback code would have to do what you do today anyway (i.e. using file-mapping objects or something), so what’s the point of a magic "GlobalVirtualAlloc" when you have to write the file-mapping code anyway? "

    That just doesn’t make sense. So lets see, I’m writing code that wants to know when a session starts on terminal services. If I’m on WinXP I can use the nice new callbacks that give me exactly what I want. If I’m on Win2k I have to use an old semi-polling method. So by your account, I should ALWAYS use the crappy old way that isn’t necessarily as reliable as the newer way. If that mentality were the case we would all still be using fprintf()!

    I’m not really arguing for GlobalVirtualAlloc() as much as a change in the mindset of Microsofties. Just because YOU think there are problems with it, doesn’t mean I’m not willing to use it in the cases where it DOES work, especially if it makes my life way easier. Sure, I understand I may have to write some old code too, but hopefully as time goes on I’ll get to remove the old code.

    I guess I really can’t get over the mindset of "well since it might not work all the time, I’ll just not use it at all." I’d rather analyze why and where it won’t work, and code up a backup for those areas.

    -Jeff

  33. Raymond Chen says:

    You can fix "out of memory" by buying more memory. You can’t "buy more address space".

  34. Dean Harding says:

    "So lets see, I’m writing code that wants to know when a session starts on terminal services. If I’m on WinXP I can use the nice new callbacks that give me exactly what I want. If I’m on Win2k I have to use an old semi-polling method."

    No, that’s a different issue. That is taking advantage of new features in a new platform and falling back to old features on an old platform. Seemingly when the old platform is retired, you no longer have to do the fallback.

    With the "GlobalVirtualAlloc" thing, it’s not a platform-dependent thing. You might have to do the fallback whether you’re on an old version of Windows, or whether you’re on the newest version (or even some hypothetical version that doesn’t exist yet). The need for the fallback is due to the fact that "GlobalVirtualAlloc" would be inheritly unreliable, not because it’s just not supported on some particular platform or configuration.

  35. Raymond Chen says:

    There’s a difference between code that fails due to design requirements (running as Admin, choosing a supported OS) and circumstances that are pseudo-random (fixed address space availability).

    Suppose the user buys your program *and* a program that requires a lot of virtual address space (say, Halo), or your program *and* another program that also uses global address space. The user can’t run both. Are you going to say on the box "Not compatible with Exchange Server, SQL Server, Halo, or any other address-space-intensive programs"?

    "Just because YOU think there are problems with it, doesn’t mean I’m not willing to use it in the cases where it DOES work." Feel free to use PulseEvent all you want. Even though it doesn’t work reliably.

  36. Jeff says:

    "Suppose the user buys your program *and* a program that requires a lot of virtual address space (say, Halo), or your program *and* another program that also uses global address space. The user can’t run both. Are you going to say on the box "Not compatible with Exchange Server, SQL Server, Halo, or any other address-space-intensive programs"?"

    If I remember correctly, almost every application on the planet comes with a recommendation for how much memory you need. Besides, you have that problem already when trying to run large programs… try running WinDbg with a few copies of VS .Net around… better have a gig of memory.

    "circumstances that are pseudo-random (fixed address space availability)"

    They aren’t pseudo-random at all. You listed them out. And you could give a callback that listed the process id (even better a handle to the process) and the reason the hooking didn’t occur. Problem solved.

    -Jeff

  37. AT says:

    Raymond, if your application requere so many memory – sell it in black-box. Bundle your application with hardware.

    This is very common mistake that all hardware in the world must run "Exchange Server, SQL Server, Halo…".

    There are exists markets for customised solutions. One that can potentialy requere this kind of memory allocation pattern.

    Allocating memory globaly is tradeoff similar to allocating it in every user process. You can get something in return for something else. There are nothing free in our world.

    You see problem. We see solution.

    Here is one more way to do this (in addition to replacing CreateProcess syscalls) :

    You can use custom kernel mode driver and map/unmap requered memory region using callback registered by

    PsSetCreateProcessNotifyRoutine routine

    (see

    http://msdn.microsoft.com/library/en-us/kmarch/hh/kmarch/k108_6ae7797a-ecbe-4665-85d5-e199f13613cd.xml.asp )

    As for administrator vs. regular user debate – not all hardware directly used by users.

    Some hardware can serve users using network-only or in some other way.

  38. Ray Trent says:

    You may not be able to reserve "user mode address space", but you can make a block of globally-addressed non-paged kernel-mode address space read/writable by user-mode applications.

    I’m not saying I *recommend* doing this, but it’s technically possible.

  39. Tim Carstens [carstens AT seattleu DOT edu] says:

    There sure seems to be a lot of desire to map into every process’ address space. Perhaps I’m just naive, but I fail to see why this is such a necessary feature.

    Presumably there is some problem that I’m trying to solve in which my process requires the ability to share a memory space withe very other process. I can only see one general reason to do this: for some reason, every process needs to be able to share large blocks of memory.

    This assumes that every process that is currently running knows what to -do- with this shared memory. So along comes my program, which allocates some of the global address space. Now what? Nobody else has any code that uses this space. So what have I accomplished?

    Ok, so suppose we add this global-alloc function to the platform. Perhaps then some component will get published that allows IPC via this shared memory space. Again, unless every process makes use of this functionality, there’s no reason to demand a block of every process’ address space.

    Now, suppose we design the global-alloc such that it asks every process whether or not it wants to opt-in to our scheme. Presumably, only the processes who know what to do with the shared space will opt-in. Now I’ve got a bunch of processes that use this space to share large blocks of memory. What if EvilCode.exe is running as an unpriviledged user, and chooses to opt-in. Since it’s shared memory he can trash it as he pleases. By trashing memory he can violate the design invariants of the protocol my legitimate processes use in the shared address space. Oh, and don’t forget: the global-alloc function can only be invoked by processes running as Administrator. And now look where we are: an under-priviledged process can manipulate the behavior of Administrator’s processes.

    Depending on the purpose of the shared address space, this could be seriously bad news. Suppose that only the invoking process had write access and that every other process involved had read-only access. We’re still in bad shape, since unauthenticated processes can monitor communications between other processes. This is still a security risk waiting to happen, since it allows potentially untrusted applications (spyware, anyone?) to tap the communications between Administrator’s processes.

    There are only two advantaged to a shared memory space:

    1. If you like using separate processes instead of separate threads (and there is a school of people who do this,) you don’t have to compromise the shared-memory capabilities of threads. Of course, if you use separate processes instead of threads, odds are you’re doing it so that one bad process doesn’t crash the others. Of course, memory-related bugs are often why threads crash each other, so only in some cases have we actually accomplished anything.

    2. If you have some processes (that couldn’t be consolodated into multiple threads) that need to share large amounts of data at high speeds. From this perspective, global-alloc is an optimization technique. Of course, this doesn’t legitimize taking some of -everyone’s- address space. And I don’t think that this optimization compensates for the deplorable security risks you’re inviting.

    Lastly, I respond to this type of statement (which I’ve seen many times in this thread): "Oh, I’m a good developer, I’ll use it responsibly! It’s the developer’s job to use the tools right! So you should open it up so I can use it!"

    In an ideal world, where nobody blamed MSFT for things that weren’t their fault, this argument might hold. However, it turns out that there’s a lot of people who hold MSFT accountable for the design of Windows (go figure). The more dangerous functions they have in the platform, the greater the risk that flaky developers will use the functions incorrectly, decrease the stability of the platform, and in turn get MSFT in more heat.

    This function is a disaster waiting to happen. How many malicious apps have to use this functionality to gain priviledges, crash applications, and collect data before someone asks "Why did MSFT introduce this functionality in the first place. What’s wrong with marshalled IPC?"

    Moral of the story: sometimes when you want to do something right, you have to put some time into it. Security, particularly w.r.t. IPC, is one of those things.

Comments are closed.


*DISCLAIMER: I DO NOT OWN THIS CONTENT. If you are the owner and would like it removed, please contact me. The content herein is an archived reproduction of entries from Raymond Chen's "Old New Thing" Blog (most recent link is here). It may have slight formatting modifications for consistency and to improve readability.

WHY DID I DUPLICATE THIS CONTENT HERE? Let me first say this site has never had anything to sell and has never shown ads of any kind. I have nothing monetarily to gain by duplicating content here. Because I had made my own local copy of this content throughout the years, for ease of using tools like grep, I decided to put it online after I discovered some of the original content previously and publicly available, had disappeared approximately early to mid 2019. At the same time, I present the content in an easily accessible theme-agnostic way.

The information provided by Raymond's blog is, for all practical purposes, more authoritative on Windows Development than Microsoft's own MSDN documentation and should be considered supplemental reading to that documentation. The wealth of missing details provided by this blog that Microsoft could not or did not document about Windows over the years is vital enough, many would agree an online "backup" of these details is a necessary endeavor. Specifics include:

<-- Back to Old New Thing Archive Index