How to check for a valid PDO?
Let’s first define what a PDO is. PDO is an acronym for Physical Device Object. A PDO is a kernel object to describe a real physical device (contrary of logical or virtual device). It’s represented in kernel space by a structure called DEVICE_OBJECT (because it applies to all device objects). And then this structure is shared between kernel and drivers. Here is the structure
typedef struct _DEVICE_OBJECT {
CSHORT Type;
USHORT Size;
LONG ReferenceCount;
PDRIVER_OBJECT DriverObject;
struct _DEVICE_OBJECT* NextDevice;
struct _DEVICE_OBJECT* AttachedDevice;
PIRP CurrentIrp;
PIO_TIMER Timer;
ULONG Flags;
ULONG Characteristics;
volatile PVPB Vpb;
PVOID DeviceExtension;
DEVICE_TYPE DeviceType;
CCHAR StackSize;
union {
LIST_ENTRY ListEntry;
WAIT_CONTEXT_BLOCK Wcb;
} Queue;
ULONG AlignmentRequirement;
KDEVICE_QUEUE DeviceQueue;
KDPC Dpc;
ULONG ActiveThreadCount;
PSECURITY_DESCRIPTOR SecurityDescriptor;
KEVENT DeviceLock;
USHORT SectorSize;
USHORT Spare1;
PDEVOBJ_EXTENSION DeviceObjectExtension;
PVOID Reserved;
} DEVICE_OBJECT, *PDEVICE_OBJECT;
But before using it, kernel must check whether it received a good PDO. Why kernel? Because most generally PDO is allocated by a driver using IoCreateDevice, which fails in case of error and driver can stop properly. If it doesn’t fail, driver keeps pointer in memory and doesn’t need to check it further. But kernel is receiving such pointers from drivers and must ensure they are good (for example, checking they are not random memory addresses). So, what does kernel do to check those pointers? First, it checks for a non-null pointer. No need to continue if pointer is null. It doesn’t check whether PDO pointer is valid, it checks one of it’s member. As you can see upper, there’s a pointer in DEVICE_OBJECT to a DEVOBJ_EXTENSION structure. Internally, an other structure is used: EXTENDED_DEVOBJ_EXTENSION. It extents the “normal” structure, giving more informations, used by the kernel.
This structure looks like that
typedef struct _EXTENDED_DEVOBJ_EXTENSION
{
// …
ULONG ExtensionFlags;
PVOID DeviceNode;
struct _DEVICE_OBJECT* AttachedTo;
// …
} EXTENDED_DEVOBJ_EXTENSION, *PEXTENDED_DEVOBJ_EXTENSION;
This structure provides an interesting opaque pointer to a DEVICE_NODE structure. This structure, that driver should never change, is the one used by kernel to check the validity of the PDO. So, it first checks if the DeviceNode pointer is null. If not, it checks the Flags member of the structure to find if the flag DNF_ENUMERATED is set (which ensure that the PDO has been correctly initialised). When those two conditions are verified, the PDO is considered as valid.
In case of a failed test, what does kernel do? In fact, there a two different way to handle that. A cool one, for none important cases, and a more drastic one for cases where PDO must be valid. When a non valid PDO is received in a not “important” function, it can just leave with a STATUS_INVALID_DEVICE_REQUEST status and let called handle this case. In important cases, kernel must stop Windows execution to prevent any problems. Only one solution, the call to KeBugCheckEx function (producing BSOD…). KeBugCheckEx is called with specific parameters to help debugging. First parameter, the BugCheck code is set to 0xCA. It’s to indicate that it’s the PNP manager (part of the IO branch) that encounters an error and that it can’t recover it. Then come the first BSOD parameter, 0×2 it’s to indicate that PNP manager received an invalid PDO. And finally, we put the PDO address (to be able to have more informations using WinDbg). Then MSDN speaks about a third parameter. The experience shows that Windows (at least XP) doesn’t fill it.
Here is the way we could see that in C. First, we’ll define a helper macro to check the PDO:
#define IopIsValidPhysicalDeviceObject(PhysicalDeviceObject)
((((PEXTENDED_DEVOBJ_EXTENSION)PhysicalDeviceObject->DeviceObjectExtension)->DeviceNode) && (((PEXTENDED_DEVOBJ_EXTENSION)PhysicalDeviceObject->DeviceObjectExtension)->DeviceNode->Flags & DNF_ENUMERATED))
In this macro, we check the PDO as Windows kernel does. Why Iop prefix? Io because it’s part of the IO branch, and p because it’s a private function. And then let’s see the two cases, first one with return:
NTSTATUS IoSomeFunction(PDEVICE_OBJECT PhysicalDeviceObject)
{
if (!IopIsValidPDO(PhysicalDeviceObject))
{
return STATUS_INVALID_DEVICE_REQUEST;
}
// …
}
And the second case:
VOID IoSomeFunction(PDEVICE_OBJECT PhysicalDeviceObject)
{
if (!IopIsValidPDO(PhysicalDeviceObject))
{
KeBugCheckEx(PNP_DETECTED_FATAL_ERROR, 0×2, PhysicalDeviceObject, 0, 0);
}
// …
}