Hi all,
I was having a look at a Windows third-party driver and I found an issue that results into kernel memory leak back to userland, but I believe there could be something more.
To be more specific, the driver does the following in those precise steps:
- Get a non-paged pool allocation via AllocateContiguousMemorySpecifyCache with MmCache flag
- Call IoAllocateMdl/MmBuildMdlForNonPagedPool to build an MDL for the next step
- Call MmMapLockedPagesSpecifyCache to map the non-paged pool page in userland
- Call MmFreeContiguousMemory to release the physical page(s)
Because the driver does not correctly initialize the non-paged pool page, this allows to leak memory to the mapped userland page.
Here starts the interesting part:
If someone requests 1 byte of allocation, the driver will only initialize 1 byte.
However, step1 will allocate an entire non-paged pool page, so the rest of the data can be leaked to userland.
Now, I can see that the non-initialized non-paged pool allocation contains copy of objects that are allocated in paged pool. My first impression is that calling AllocateContiguousMemorySpecifyCache causes that physical page to be paged out, or data to be moved to a different physical page, and then it is used to be mapped to non-paged pool which leaks that data.
In all my tests, I was able to allocate multiple objects that reside in paged pool and then abuse this flaw to leak them in userland via that non-paged pool allocation.
Trying to do anything with those objects in the userland mapping won’t have any effect in this case because it looks like this is just a copy of data previously resided in a physical page that is now paged-out or possible moved to a different page and now used by the non-paged pool allocation created through the AllocateContiguousMemorySpecifyCache . This is what I experienced by modifying those leaked objects. The original objects remained intact. I also checked the PFNs of the objects and I was able verify that the original objects were in a different physical page.
The driver will later call MmFreeContiguousMemory to release that physical page, assigned to the non-paged pool allocation, back to the system, but it never calls MmUnmapLockedPages to remove the mapping of those physical pages from userland.
So, my thinking was that those physical pages even though still ‘mapped’ in userland, they are now free to be reused by other processes or the kernel itself at will, which in that case we could actually modify interesting data that doesn’t necessarily belong to our process since we still hold the mapping to those physical pages.
However, during all my tests and after having mapped thousands of physical pages into my process, I never saw anything else reusing them while my process holds those mappings that occurred through the driver.
By looking at the process memory usage, I can tell that those are just mappings to the underlying physical pages because even after thousands and thousands of allocations my process will use very little memory which means those are not part of my process committed memory.
However, if someone attempts to do a memory dump of the process with process explorer or task manager, because those tools will dump also the memory viewed by those mappings, it results in a few GBs of dumped memory.
Here comes the 1 million dollars question
Since those physical pages appear to be only mapped and not committed, why don’t I see them ever being re-used by other tasks while my process is running?
Thank you all
PS: This is my first post here, so please don’t be too harsh.