Why is there no way to add a permission to a page with VirtualProtect instead of replacing it?


The Virtual­Protect function lets you change the protection of a page, but all it can do is replace the current protections with the protections you specify, returning the old protections. There is no way to add a protection to a page. In other words, there is an Interlocked­Exchange for page protections, but no Interlocked­Or. Why not?

The theory behind page protections is that you should be modifying protections of pages that you have control over, and not messing with protections of pages that are not yours. If you stick to that theory, then if you have to synchronize access to the protections for a page, you can impose your own locking policy to avoid thread races.

In other words, the code that allocated the pages can also set up a policy that any time you want to change the protection of the page, you have to take a particular critical section so you can change the protection and update whatever auxiliary data structures are necessary to keep track of the state of the page. Anybody who changes the protection of the page without taking the lock is violating the principle that you should not be modifying protections of pages that are not yours.

This principle of "Don't mess with it if it's not yours" means that if you want to have pages whose protections you want to manipulate, you should allocate them yourselves so that you are the one in control of their protections.

One might argue whether this principle is still valid, but that's the principle that went into the original design, so you'll just have to deal with it.

Even if there were some sort of Interlocked­Or-like function that added a permission to a page, you still have a problem if the various pieces of code that update the protections of a page are not coördinating with each other.

Consider a page that starts out with PAGE_READ­ONLY permission. Function 1 uses this imaginary Virtual­Protect­Or function to add write permssion, bringing the permission to PAGE_READ­WRITE. Function 2 does the same thing, but since the page already has write permission, the request to add write permission is a nop.

Next, function 1 is finished writing to the page, so it uses the imaginary Virtual­Protect­And function to remove write permission. Oops, function 2 just lost write permission to the page even though it had added it just moments ago.

In order for this to work, somebody somewhere would have to keep track of how many times somebody wanted to add write permission to the page and remove write permission only when the count drops to zero. And even then, that might not be the right thing. Maybe function 1 is removing write permission for some security purpose. If function 2 still had an outstanding "add write permission" and the page remained write-enabled, well, that's good for function 2, but it leaves function 1 vulnerable.

Since it's not clear what the correct behavior is, there's not much point in the kernel keeping a history of all the protection changes for every page in the system. If you want to have a policy for what should happen if two pieces of code change the protections of the same page, then you are welcome to write code that enforces that policy and make everybody go through you when they want to change the protection.

Bonus chatter: Note also that page permissions cannot be arbitrarily mixed and matched. For example, there are no write-only pages. This makes reconciling multiple page protections even more difficult when the result is something that doesn't exist.

Comments (12)
  1. Koro says:

    The only obvious use for such complex machinery would be for hot-patching code, which is not recommended anyway.

    1. ZLB says:

      AFAIK, there is no safe way to patch code in a running process from user mode anyway, so you still have other issues.

      1. Neil says:

        Why would the code be running? (I assume here you would want to act like a debugger.)

        1. Zak Larue-Buckley says:

          If no code is running in the process then you can fiddle with page protections as you wish because you know nobody else is.

          You can use VirtualQuery to get the current protection, then restore the page protection after you are done. The whole discussion is moot.

          The scenario I was thinking of was an injected dll detouring functions on the fly.

  2. Kevin says:

    In the old days…

    Well, in the old days, there was no memory protection, and if you wrote to the wrong address, the computer crashed. But in the semi-old days, you allocated the memory, then you set the protection on it (if desired), then you used it, then you deallocated it. At no point did you need to fiddle with protection from multiple threads, mess with anyone else’s memory, or do any other “clever” stuff that would require atomically adding or removing permissions.

    I am rather curious about what has changed since then.

      1. Someone says:

        and?

        1. Joshua says:

          And I remember having to set dynamic library load addresses at compile time so there weren’t any fixups.

    1. Voo says:

      Any kind of dynamic compilation will want to use read-write while generating code and then set it to read-execute.

      Why you’d want multiple threads to fight over the correct setting is anyone’s guess though

      1. henke37 says:

        Of course, less careful implementations just ask for RWE pages and don’t bother changing the permissions after generating the code. It works but…

    2. You might have heard of a little thing called the Java runtime. Hotspot allocates pages as read-write, then has to mark pages read-execute afterward.

  3. florian says:

    So co-ör-or-dinate is the new microspeak to perform a bitwise-inclusive-OR operation that keeps blocking until undone?

Comments are closed.

Skip to main content