Freigeben über


What's So Special About The Pool?

One of the tools we use to troubleshoot pool memory corruption is the gflags.exe option "special pool", but what exactly does it do?

In order to get to this, let's first look at what pool memory is - specifically looking here at versions of Windows before Vista (as this changed dramatically with the new memory management improvements in the NT 6.0 kernel).

Pool memory comes in 2 flavours; paged and nonpaged - the only difference being that the first type can be paged out to disk if the physical memory it is using is needed and has not been touched in a while.

The following examples are all based on the x86 architecture using system defaults (i.e. no /3GB switch)...

The regions of pool memory are subsets of the kernel virtual address space (0x80000000-0xFFFFFFFF) and their sizes are calculated at boot time as they are derived from the physical memory present and registry values that can influence or dictate how they should be.

The absolute maximum size of nonpaged pool is 256MB, and the maximum for paged pool is ~650MB.

The pools are used for dynamic memory allocations by threads running in kernel mode - nonpaged pool is used typically by drivers, as they need to be guaranteed their buffers will be instantly available because we can't incur a page fault (read from disk to get virtual memory contents back from the pagefile) as this would bugcheck the machine (IRQL_NOT_LESS_THAN_OR_EQUAL).

Small memory pages are 4K in size, but pool allocations are very often not a full page - so different drivers can have neighbouring allocations in the same page.

pool_memory_page

In the above illustration, 3 drivers have made pool allocation requests of ~500 bytes in the order A,B,C,A,C.

For a pool request of X bytes, X+8 bytes are actually required as we need to append a header before the data - this allows us to see what areas are freed later for re-use, and by keeping tabs on the previous allocation size we have a basic form of integrity checking that the list is good.

The last 4 bytes of the 8-byte header are for the pooltag - from Windows Server 2003 onwards this is enabled by default and gives us some way to see who made the pool allocation requests.
For example, the pooltags here could be something like DrvA, BBBB and CcCc

So, Driver A makes a pool allocation request and it is granted, it is given the address of where it can write to its buffer of, say, 500 bytes (technically, through rounding up this would be 504 bytes).

What is in place to make sure the driver doesn't write more than its buffer will hold?
Nothing.

Code running in kernel mode is already privileged enough to do practically anything, so the drivers are trusted to be tidy and play with with the other children.

But buffer overruns occur - code is written by humans, and humans are prone to errors.

If Driver A wrote 600 bytes in its buffer, at the 505th byte it would start to overwrite the pool header of Driver B's allocation, corrupting it.

This will not bugcheck the machine when this write is done - the only time we can get a problem is if the data Driver B relies on in its allocation (which it assumes to be good, as it put it there) causes it to do something unexpected.
Or, if Driver B says it is done with its allocation and the memory manager can free it.

These situations may occur AGES after Driver A corrupted the memory, or maybe it could be milliseconds, who knows.

The point is, if the machine bugchecks then as it lies dying and spasming on the ground it will muster its last energy to point at the guy who killed it... Driver B, the innocent one (just another victim).

So, we load up the debugger and look at the memory dump once the system is back up - it lets us know that its opinion is a pool corruption "probably" caused by Driver B, or maybe ntoskrnl.exe, or perhaps win32k.sys (the last 2 are VERY unlikely to be the real cause, but the debugger is a simple beast and doesn't know any better).

It will likely report that this looks like pool corruption, so all bets are off - no way to be certain who made this mess.

So, in comes our friend Special Pool.

When we enable special pool on specific drivers, any pool allocation requests they make are treated differently - by default we assume they could be overrunning their buffers (we can select to check for underruns instead if we believe this to be the case, but these are rare).

Pool allocations made by the marked drivers are now made from a separate, smaller, "special" pool region and they have an entire 4K page all to themselves, regardless of how much they requested.

The pool allocation is made at the end of the page, so the last byte of the buffer is the very last byte of the 4K page.
The next page is marked as a "guard" page - non-writeable - and through the default behaviour of the memory manager if it receives a request to write here, it will bugcheck the machine.

So now, if Driver A writes more than it was allowed to, the machine bugchecks immediately and the OS & debugger would point directly to the real culprit, still holding the smoking gun.

special_pool

Consider how wasteful this is; a 500-byte pool allocation request has just consumed 2 pages - 8K.
This is the reason it's not enabled by default, and also why you should be selective with which drivers are tagged to use it when troubleshooting.

If the system runs out of special pool, then it will allocate from regular pool without the guard pages - so it really doesn't do any good to make all drivers use it on busy systems.

Typically we recommend using the custom option when enabling special pool, and turn it on for all drivers that are not from Microsoft or Microsoft Corp. – sort the list by the vendor column and this selection becomes a lot easier (just remember to check before and after “M” ;)

 

The memory manager was redesigned in Vista, so there are no fixed regions for pool memory any more and the limits don’t apply – memory pages are allocated for different uses as needed, on demand.