When is string constant not really constant?

While this is not necessarily specific to a driver, the affects can be, so read on. 

A string constant (e.g. "Foo") all by itself is harmless, but if you use the string constant when initializing a variable, it depends on how you declar the variable and then use it.  If you intialize the variable this way:

PSTR pStr = "Foo";

you are declaring a pointer which is assigned the address of the global string constant.  If you initialize the variable this way:

CHAR pStr[] = "Foo";

you are declaring an array whose size is sizeof("Foo")/sizeof(char) (and remember from yesterday's topic, sizeof("Foo") includes the NULL in the count of bytes).  Essentially the declaration evaluates to CHAR pStr[sizeof("Foo")/sizeof(char)]; RtlCopyMemory(pStr, "Foo", sizeof("Foo")/sizeof(char));

The first pStr is constant and can't be modified, while the second pStr is a copy of the string and can be modified.  Let's now look at this code snippet which tries to create a named device object (and remember that this is example code and is not the best way to implement this functionality, it is just demonstrating the problem).

#define DEVICE_NAME L"\\Device\\Foo" // <= this not a link, my editor is lame!

//
// Create a buffer which can hold the device name and up to 5 digits
// (actually it's six becuase we can use the space allocated for the
// NULL as a non NULL character
//
WCHAR buffer[] = DEVICE_NAME L"XXXXX";

UNICODE_STRING string;
NTSTATUS status;
ULONG i = 0;

RtlZeroMemory(&string, sizeof(string));
string.Buffer = buffer;
string.Length = 0x0;
string.MaximumLength = sizeof(buffer);

do {
status = RtlUnicodeStringPrintf(&string, L"%s%d", DEVICE_NAME, i++);
if (!NT_SUCCESS(status) { break; }

    status = IoCreateDevicE(DriverObject,
0,
&string,
...
);

} while (status == STATUS_OBJECT_NAME_COLLISION);

By using the 2nd way of declaring buffer, we have created a writable buffer that is based on the stack.  If we had used the first way of declaring buffer, we would have been doing 2 things which were wrong

  1. We would be writing to read only memory
  2. We would be modifying a global with out synchronization. If we were to execute this device object creation code in 2 different threads simultaneously, we could easily trash the global string constant and end up with an incorrectly named device object.

Point 1) is what brings this back to drivers.  In kernel mode and on XP and later, the memory manager does its best to mark read only data as read only in the page table entry on the MMU (under some circumstances, it can't because of large pages and other issues).  That means that if you used the first way for declaring buffer, you could blue screen instaneously (as I know first hande when I had to debug a 3rd party input driver while we were developing XP).  I don't know if user mode also goes to great lengths to enforce the read only bit on the page, but that would be an interesting excercise to go through.