String buffers and IRQL


If you look at the docs for many Rtl string functions, you will see that they are callable only at IRQL == PASSIVE_LEVEL.  This applies to not only Rtl functions but also to CRT functions Why is that?  Well, there are a few resaons



  • The Rtl functions are marked PAGEable so you can’t even execute the function itself

  • The Buffer in a UNICODE_STRING is typically from PagedPool if it is not stack based

  • The string conversion (e.g. NLS) tables are PAGEable

You can overcome the 2nd bullet by making sure your buffer allocations come from NonPagedPool, but that doesn’t help you very much because the surrounding infrastructure.  But wait a minute…there are samples out there which use strlen or sprintf at DISPATCH_LEVEL or higher.  The most common use of string functions in a driver is to wrap DbgPrintf with more logic (like levels or component bits), something similar to


#define PRINT(level, x) if ((level) <= Globals.DebugLevel) { MyPrint x ; }


VOID MyPrint(PSTR Format, …)
{
    CHAR ach[128];
    va_list va;
    NTSTATUS status;


    va_start(va, Format);
    status = RtlStringCbVPrintfA(ach, sizeof(ach), Format, va);
    if (NT_SUCCESS(status)) { DbgPrint(ach); }
    va_end(va);
}


which is invoked like this PRINT(2, (“Number of instances %d\n”, DevExt->NumInstances”)); and this actually works.  Why?  Because the format string does not contain any %s format specifiers.  If you use %s, you will hit the aforementioned tables and potentially hit paged out pool (and bugcheck). 


What to do?  Well, don’t put strings in your debug print statements if it is callable at IRQL > PASSIVE.  Also, you have to consider why you are even dealing with a string at a higher IRQL, hopefully you are not comparing strings or other similar string operations.  Also, consider using WPP where you can use strings (because the string contents are copied directly and processed in user mode later).

Comments (13)

  1. Mark Steward says:

    Functions like RtlUpperString are documented on MSDN as requiring PASSIVE_LEVEL, but I can’t find anything on the page for RtlStringCbVPrintf.  RtlCopyUnicodeString says "the DestinationString and SourceString buffers must be resident if the caller is running at IRQL >= DISPATCH_LEVEL" – perhaps the equivalent should be added to RtlStringCbVPrintf, etc.?

    Great blog!

    Mark

  2. hanzhu says:

    In my opinion,  RtlStringCbVPrintf could hit the NLS table only when you use Unicode String format symbol((%C, %S, %lc, %ls, %wc, %ws, and %wZ) . If you use %s to convert or consturct string, it will not hit the PagedPool table. Tthe code snapsht mentioned above will not cause bugcheck on the higher IRQL given that the dest and src memory residents in the memory! Is that Right?

    Greate Article!

  3. doronh says:

    hanzhu, you are correct.  if should be a %S.  I tried modifying i8042prt’s keyboard ISR as followis on XP SP2

       deviceExtension->CurrentScanCode = scanCode;

    <test code>

       {

           WCHAR buf[] = L"0xXX"; // XX is a place holder for 2 digits

           if (TestIt &&

               NT_SUCCESS(RtlStringCbPrintfW(buf,

                                             sizeof(buf),

                                             L"0x%02x",

                                             scanCode))) {

               DbgPrint("scanCode %Sn", buf);

           }

       }

    </test code>

       IsrPrint(DBG_KBISR_SCODE, ("scanCode 0x%xn", scanCode));

    and did not hit a bugcheck, even with DV on.  When I have time, I will try to figure why it didn’t BSOD, but one guess is that the ntstrsafe functions do not hit the NLS tables at all b/c it uses _vsnwprintf (but that is pure speculation).

  4. hanzhu says:

    But even the ntstrasafe functions didn’t hit a BSOD, the DbgPrint should do that! Can you tell me why it will not?

  5. doronh says:

    Mark, I will talk about this in tomorrow’s entry (if I have time to give it justice).

  6. ndiamond says:

    > When I have time, I will try to figure why it didn’t BSOD, but one

    > guess is that the ntstrsafe functions do not hit the NLS tables at

    > all

    A different guess is this:

    Converting ANSI to Unicode hits the NLS tables.

    Converting Unicode to ANSI hits the NLS tables.

    Making a call that does everything in Unicode doesn’t hit the NLS tables.

    Making a call that does everything in ANSI doesn’t hit the NLS tables.

    Your sample code did everything in Unicode.

  7. doronh says:

    norman, last time i looked, DbgPrint is ANSI, so this

    DbgPrint("scanCode %Sn", buf);

    should incur a UNICODE->ANSI conversion.  

    I have not yet had time to investigate this, this week is busy with my mid year review and a 2 day conference on security (Blue Hat, search for it w/your fave search engine).

  8. ndiamond says:

    You’re right, I double-checked all the arguments of RtlStringCbPrintfW and forgot to single-check DbgPrint.  Yeah I wonder why DbgPrint didn’t BSOD.

  9. kstarsmeare says:

    Why do the docs, and your blog, state that the IRQL must be equal to PASSIVE_LEVEL? What about APC_LEVEL? I can think of no reason why I can’t call these functions from APC_LEVEL and I certainly would like to be able to use these Rtl functions in my minifilter pre-op callback routines (which will be called at either PASSIVE or APC level).

    Most of the DDK is documented to require IRQL < DISPATCH_LEVEL if paging is, or might be, required.

  10. doronh says:

    APC_LEVEL is probably OK since you can handle paging i/o at that IRQL, i don’t think there is anything special in the NLS tables that would preclude APC_LEVEL.  I don’t mention APC_LEVEL much because for a non FS related driver, you don’t deal with APC_LEVEL much, so talking about it leads to more confusion then if we just talk about IRQL as PASSIVE_LEVEL, DISPATCH_LEVEL, or DIRQL

    d