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).