Windows System Software -- Consulting, Training, Development -- Unique Expertise, Guaranteed Results

Home NTDEV

Before Posting...

Please check out the Community Guidelines in the Announcements and Administration Category.

More Info on Driver Writing and Debugging


The free OSR Learning Library has more than 50 articles on a wide variety of topics about writing and debugging device drivers and Minifilters. From introductory level to advanced. All the articles have been recently reviewed and updated, and are written using the clear and definitive style you've come to expect from OSR over the years.


Check out The OSR Learning Library at: https://www.osr.com/osr-learning-library/


ProbeForWrite vs ProbeForRead

0xrepnz0xrepnz Member Posts: 88

This question is mainly out of curiosity.

This is the implementation of ProbeForRead:

VOID
ProbeForRead(
    IN CONST VOID *Address,
    IN SIZE_T Length,
    IN ULONG Alignment
    )
{
    PAGED_CODE();

    ASSERT(((Alignment) == 1) || ((Alignment) == 2) ||
           ((Alignment) == 4) || ((Alignment) == 8) ||
           ((Alignment) == 16));

    if ((Length) != 0) {
        if (((ULONG_PTR)(Address) & ((Alignment) - 1)) != 0) {
            ExRaiseDatatypeMisalignment();

        } else if ((((ULONG_PTR)(Address) + (Length)) < (ULONG_PTR)(Address)) ||
                   (((ULONG_PTR)(Address) + (Length)) > (ULONG_PTR)MM_USER_PROBE_ADDRESS)) {
            ExRaiseAccessViolation();
        }
    }
}

This is the implementation for ProbeForWrite:

VOID
ProbeForWrite (
    IN PVOID Address,
    IN SIZE_T Length,
    IN ULONG Alignment
    )
{

    ULONG_PTR EndAddress;
    ULONG_PTR StartAddress;

    if (Length != 0) {
        ASSERT((Alignment == 1) || (Alignment == 2) ||
               (Alignment == 4) || (Alignment == 8) ||
               (Alignment == 16));

        StartAddress = (ULONG_PTR)Address;
        if ((StartAddress & (Alignment - 1)) == 0) {

            EndAddress = StartAddress + Length - 1;
            if ((StartAddress <= EndAddress) &&
                (EndAddress < MM_USER_PROBE_ADDRESS)) {

                EndAddress = (EndAddress & ~(PAGE_SIZE - 1)) + PAGE_SIZE;
                do {
                    *(volatile CHAR *)StartAddress = *(volatile CHAR *)StartAddress;

                    StartAddress = (StartAddress & ~(PAGE_SIZE - 1)) + PAGE_SIZE;
                } while (StartAddress != EndAddress);

                return;

            } else {
                ExRaiseAccessViolation();
            }

        } else {
            ExRaiseDatatypeMisalignment();
        }
    }

    return;
}

So the implementation of ProbeForRead doesn't even dereference the provided buffer (it only checks it resides in the user mode portion) but ProbeForWrite tries to perform memory writes in loops. My question is - Why is ProbeForWrite implemented like this? Why do we need to perform these writes for each page? Can't we just test it resides in the user mode portion as well?

- Ori Damari

Comments

  • Tim_RobertsTim_Roberts Member - All Emails Posts: 14,373

    Because the pages could be mapped, but mapped as read-only.

    Tim Roberts, [email protected]
    Providenza & Boekelheide, Inc.

  • 0xrepnz0xrepnz Member Posts: 88

    Yeah but if the page is mapped as read-only an exception will be thrown and we catch it so it's ok.. We need to use __try anyway when writing to user memory. Moreover ProbeForWrite is not safe by design because it does not lock the pages so the user code can unmap or change the page protection in between so I don't understand what is the point of performing a security check that's not safe by design..

    - Ori Damari
  • Jason_T.Jason_T. Member Posts: 98

    The main value (IMO) of these APIs is to determine if the specified address is within the user mode range. Read/write checks are of limited usage since the app could change the page protections a moment after this call is made. Keep in mind that __try/__except do NOT catch invalid kernel mode accesses, so this is the big gain from having called ProbeForXxx ahead of time. In other words, if you took the position that you'd just let the exception handler deal with lack of write access, but the user actually supplied a kernel address, this would either be a massive security hole (valid kernel address) or crash the system (invalid kernel address).

  • 0xrepnz0xrepnz Member Posts: 88

    I know that dereferencing arbitrary kernel addresses can cause an immediate blue-screen. My question is: Why do we need to try to write to the pages in ProbeForWrite? As far as I understand checking for the user mode range is enough..

    - Ori Damari
  • Jason_T.Jason_T. Member Posts: 98
    edited July 19

    You certainly don't have to call this function. But given that it's called ProbeForWrite it makes sense that it would check writability, no? It also provides an alignment check which the user can't change after the fact. But if you just want to see if it's a user mode address you can directly check the pointer against MmUserProbeAddress which is a public symbol.

  • Peter_Viscarola_(OSR)Peter_Viscarola_(OSR) Administrator Posts: 9,025

    ProbeForRead and ProbeForWrite are old and venerable functions. As such, I'd suggest that actually writing to the memory made sense back in a "simpler time" -- it also has the advantage of checking the state of the pages prior to your attempt to USE the pages, and could (sort of) allow you to differentiate different causes for the failure.

    In short, I don't think we gain anything (from a security perspective) in 2022 by writing to each page. Of course, I'll be happy to be proven wrong by somebody who has more insight into this particular design point that me.

    Peter

    Peter Viscarola
    OSR
    @OSRDrivers

  • MBond2MBond2 Member Posts: 477

    Presumably, buffers that a driver cares about reading from represent input of some kind. Most input is read somewhere near the start of whatever a specific request is supposed to do, so if the pages in question can't be read, the operation will fail quickly - shortly after the ProbeForRead call and before some expensive hardware operation has taken place. But the buffers that a driver cares about writing to represent output of some kind, and the data to be written into those buffers won't be available until it is collected from the hardware in some way. Verifying that the output range is actually writable before initiating the hardware operation allows the call to fail quickly with invalid input and avoids work that would be useless.

    This is a high level description of my understanding of the rationale. I have no direct knowledge, but this is how is makes sense to me.

Sign In or Register to comment.

Howdy, Stranger!

It looks like you're new here. Sign in or register to get started.

Upcoming OSR Seminars
OSR has suspended in-person seminars due to the Covid-19 outbreak. But, don't miss your training! Attend via the internet instead!
Writing WDF Drivers 12 September 2022 Live, Online
Internals & Software Drivers 23 October 2022 Live, Online
Kernel Debugging 14 November 2022 Live, Online
Developing Minifilters 5 December 2022 Live, Online