Buffer probe vs capture

Microsoft’s regfltr example uses the terminology of “Probed Parameters” and “Captured Parameters”, which is documented here:
https://github.com/microsoft/Windows-driver-samples/blob/0e92e35395ee3dbc2852f3ab6d9c1f7350c2bd85/general/registry/regfltr/sys/capture.c#L33

As far as I understand, a probed buffer is a buffer which was verified for validity and access rights, and a a captured buffer is just a buffer which was copied to a system allocated memory.

If my understanding is correct, I’m not sure I understand the motivation for mentioning the probed parameters at all - while the buffer was accessible at the time of the check, it wasn’t e.g. locked or something, so it might be invalid just a moment afterwards. And indeed the code probes the buffer on access (if I understand the term “probe” at all):
https://github.com/microsoft/Windows-driver-samples/blob/0e92e35395ee3dbc2852f3ab6d9c1f7350c2bd85/general/registry/regfltr/sys/capture.c#L459

Can you please clarify it for me? Thanks.

Well, the copy in the snippet you cited on GitHub accesses the buffer within a try/except… which is required.

I think your understanding is correct. However… capturing the contents of a user data buffer requires that we first probe that buffer for accessibility (within a try/except block) and then access the user data buffer in a manner that matches what we probed for… and that access also needs to be done in a try/except block.

So, doing the capture implies that SOMEbody has to do the probe and deref the user virtual address.

You can avoid your DRIVER doing the probe by using an access method other than Neither I/O (so Buffered I/O or Direct I/O… both of which involve the I/O Manager doing the probe for you… and Buffered I/O also capturing the users data buffer for you).

We typically “capture” user data buffers when it matters if the contents of the buffer changes during our processing of a request. If the data buffer contains “just data” (the driver driver is going to write to disk, for example) it doesn’t really matter (to the driver) if that data is the same or different between the time the user’s data buffer is probed and the time the data is written. It might matter to the user, but it doesn’t to the driver.

OTOH, if the driver is uses the contents of the data buffer in some meaningful way – consider a buffer that comprises a description of a series of strings… with the offset and length to each one passed a sequence of ULONG_PTRs in the buffer – we will want to capture the whole buffer and validate it once it’s inaccessible to the user. So we know when we go back to process the data in the buffer the user can’t have changed it.

Does that answer your question??

Peter

There are a dizzying number of attacks that malicious usermode can spring on a driver. Probing protects against some of them, although as you’ve observed, probing is certainly not going to protect against every sort of attack.

Probing protects against:

  • Usermode passing a kernel address to you, in the hopes that your driver will access the memory there.
  • Usermode passing a bogus buffer length, i.e., one that would cause the buffer to spill over into kernel VA space or even wrap around the entire address space.
  • Usermode passing unaligned data, possibly causing your code to fault and bring down the system (Windows does not currently ship on any processor architectures that are configured to bugcheck on alignment faults, but we could theoretically return to such a configuration).

But as you’ve observed, the probe doesn’t really mean very much, since usermode can change out the page protections on its own virtual addresses at any time. So your driver still needs to access the buffer under __try / __except. (Or as this sample spells them, try / except, which is equivalent, but doesn’t work in C++ code, so is discouraged.)

When the docs say that a buffer is captured, that gives you a much stronger guarantee. That protects against the attacks listed above, as well as:

  • Usermode changing the contents of the buffer while your code is trying to read/write from it
  • Usermode sniffing out intermediate values that you “temporarily” wrote to the buffer, with the intent to scrub it out before returning the request to usermode

My misunderstanding was in the difference between probing and accessing the data withing try/except. The short answer is: a check for kernel addresses and alignment. Thank you for the elaborate answers, in addition to answering my question I got better understanding overall.

Just in case your head doesn’t hurt enough, note that capturing can actually be a lot harder than it seems. Classic guest article about the problems with capturing here:

https://www.osronline.com/article.cfm^article=514.htm

@“Jeffrey_Tippet_[MSFT]” said:
When the docs say that a buffer is captured, that gives you a much stronger guarantee. That protects against the attacks listed above, as well as:

  • Usermode changing the contents of the buffer while your code is trying to read/write from it
    Just to be sure - how does it prevent the user mode code from changing the contents while your code is reading/writing from it? I presume you mean the captured buffer, but user mode can still change the original buffer while you are writing to it (the original buffer).

@Dejan_Maksimovic said:
Just to be sure - how does it prevent the user mode code from changing the contents while your code is reading/writing from it? I presume you mean the captured buffer, but user mode can still change the original buffer while you are writing to it (the original buffer).

Yes, sorry about the confusion; that’s what I meant. Usermode can change its buffers as much as it wants. But when a lower layer captures the buffer, you get a single, unchanging snapshot of the contents of the buffer. Usermode can continue to scribble on its own buffers, but it won’t be reflected in the kernel’s captured buffer.

This general definition of “capture” applies to the registry parameters discussed at the start of this thread, and also METHOD_BUFFERED ioctls captured by the I/O manager.

And you can capture values yourself. Capturing basically amounts to small amounts of compiler magic, wrapped around a simple memcpy:

struct MY_DATA {
    USHORT Index;
    USHORT Value;
};

USHORT GlobalData[42]{};

NTSTATUS MySetValueAtIndex(
    _In_reads_bytes_(inputLength) void *userBuffer,
    ULONG inputLength)
{
    // We're going to store a copy of the data on the stack here
    MY_DATA captured;

    if (inputLength < sizeof(captured)) {
        return STATUS_INVALID_PARAMETER;
    }

    __try {
        // This is the probe
        ProbeForRead(userBuffer, sizeof(captured), __alignof(captured));

        // And this is the capture
        RtlCopyMemory(&captured, userBuffer, sizeof(captured));

    __except(EXCEPTION_EXECUTE_HANDLER) {
        return GetExceptionCode();
    }

    // This would be a big security issue if we were reading the data
    // directly from user buffers, since the user program could change
    // the Index after we validated it.  But it's safe with a captured
    // buffer, since usermode can't reach into our own copy of the data.
    if (captured.Index < ARRAYSIZE(GlobalData)) {
         GlobalData[captured.Index] = captured.Value;
    }
    return STATUS_SUCCESS;
}