mcot mcot - 10 months ago 47
C Question

Windows Driver - Passing strings between user mode and kernel mode. Dynamically sized struct

I am looking at the File System Filter driver example in the WDK called minispy... specially how they are passing data between userspace and the kernel using the FilterSendMessage and related functions:

The code I am looking at and will be referring to should be located here if you install the WDK:

WinDDK Root Dir\version\src\filesys\miniFilter\minispy

So basically the first thing I am looking at is the shared header file in inc\minispy.h:

#pragma warning(push)
#pragma warning(disable:4200) // disable warnings for structures with zero length arrays.

typedef struct _LOG_RECORD {

ULONG Length; // Length of log record. This Does not include
ULONG SequenceNumber; // space used by other members of RECORD_LIST

ULONG RecordType; // The type of log record this is.
ULONG Reserved; // For alignment on IA64

WCHAR Name[]; // This is a null terminated string


Here we have Name which is not given an explicit size and it looks like they are using some options to disable warnings for this.

Now I am looking at where this is filled in, filter\mspyLib.c:

(I only copied lines I thought were relevant...)

VOID SpySetRecordName (__inout PLOG_RECORD LogRecord, __in PUNICODE_STRING Name)

ULONG nameCopyLength;
PCHAR copyPointer = (PCHAR)LogRecord->Name;
// A bunch of code for getting nameCopyLength from UNICODE_STRING -- I understand this.

// comment about adding sizeof(PVOID) for IA64 alignment -- I understand this.
LogRecord->Length = ROUND_TO_SIZE( (LogRecord->Length + nameCopyLength + sizeof( UNICODE_NULL )), sizeof( PVOID ) );

RtlCopyMemory( copyPointer, Name->Buffer, nameCopyLength );

copyPointer += nameCopyLength;

*((PWCHAR) copyPointer) = UNICODE_NULL;

So my question is basically is this the best method for passing strings inside of a struct for user-kernel communication using FilterSendMessage? I'm having trouble picturing the layout of these structs and what happens if the name string turns out to be too big. Also, allocation for the struct happens in the userspace component on it's stack but the resize happens in kernel space component acting on a passed pointer to the struct. I think this is more a problem of me not understanding zero length arrays but how does the userspace component know how much space to reserve for the Name field before its resized?

This essentially seems to be a form of dynamically sized arrays which is discussed in several threads such as:

C: Recommended style for dynamically sized structs

Answer Source

First of all, yes, it appears to be a form of dynamically sized array. Basically, the Name string is placed right after the end of structure. When passing data between user- and kernel-mode it is very often used trick. There are several advantages of this approach:

  • It does not require several memory allocations.
  • When passing data from user mode to kernel mode (and vice versa) you often need to copy memory from one buffer to another (I believe FilterSendMessage does that). If we use pointer to a different memory location for storing string we will need several calls to copy (or lock) memory. When dealing with user-supplied messages it is impossible to OS to know the layout of your data structure. The key to solving this problem is to use plain structure as the one described above.

It is very useful approach. I personally use such trick to avoid multiples calls to memory functions (allocate, copy, move etc.) even in simple applications. When developing Windows drivers I see such structures everywhere (i.e. ZwQueryInformationFile, ZwQueryDirectoryFile etc.)