Rich (BB code):
REFERENCE_BY_POINTER (18)
Arguments:
Arg1: 0000000000000000, Object type of the object whose reference count is being lowered
Arg2: ffffc78367ae4050, Object whose reference count is being lowered
Arg3: 0000000000000010, Reserved
Arg4: 0000000000000001, Reserved
The reference count of an object is illegal for the current state of the object.
Each time a driver uses a pointer to an object the driver calls a kernel routine
to increment the reference count of the object. When the driver is done with the
pointer the driver calls another kernel routine to decrement the reference count.
Drivers must match calls to the increment and decrement routines. This bugcheck
can occur because an object's reference count goes to zero while there are still
open handles to the object, in which case the fourth parameter indicates the number
of opened handles. It may also occur when the object's reference count drops below zero
whether or not there are open handles to the object, and in that case the fourth parameter
contains the actual value of the pointer references count.
Here's another interesting example of a Stop 0x18 which I thought would be worthwhile sharing. As usual we can dump the object in the second parameter using
!object, but unfortunately, since this is a Minidump the command didn't work. Fortunately, I was able to infer the object type from the call stack - this is where things get interesting - and used the
!devobj command to dump the object instead.
Rich (BB code):
1: kd> !devobj ffffc78367ae4050
Device object (ffffc78367ae4050) is for:
Cannot read info offset from nt!ObpInfoMaskToOffset
\Driver\DDDriver DriverObject ffffc783680827b0
Current Irp 00000000 RefCount 0 Type 00000022 Flags 00002040
SecurityDescriptor ffff800598be8d60 DevExt ffffc78367ae41a0 DevObjExt ffffc78367aec590
ExtensionFlags (0x00000012) DOE_DELETE_PENDING, DOE_START_PENDING
Characteristics (0x00000100) FILE_DEVICE_SECURE_OPEN
Device queue is not busy.
So, we know that we're dealing with a device object here. Let's dump the call stack using the
!stack -p command and examine the call stack.
Rich (BB code):
1: kd> !stack -p
Call Stack : 13 frames
## Stack-Pointer Return-Address Call-Site
00 ffffb409b3a774e8 fffff8001da14d34 nt!KeBugCheckEx+0
Parameter[0] = (unknown)
Parameter[1] = (unknown)
Parameter[2] = (unknown)
Parameter[3] = (unknown)
01 ffffb409b3a774f0 fffff8001d871886 nt!ObfReferenceObjectWithTag+17e2c4 (perf)
Parameter[0] = ffffc78367ae4050
Parameter[1] = 0000000069706e50
Parameter[2] = (unknown)
Parameter[3] = (unknown)
02 ffffb409b3a77530 fffff8001dc3a04b nt!IoGetAttachedDeviceReferenceWithTag+36
Parameter[0] = (unknown)
Parameter[1] = 0000000069706e50
Parameter[2] = (unknown)
Parameter[3] = (unknown)
03 ffffb409b3a77560 fffff8001dd50c26 nt!IopSynchronousCall+3f
Parameter[0] = ffffc783604e7390 << Device Object
Parameter[1] = ffffb409b3a77600 << I/O Stack Location
Parameter[2] = 00000000c00000bb << I/O Status
Parameter[3] = 0000000000000000
04 ffffb409b3a775d0 fffff8001dd50b04 nt!PnpIrpQueryID+56
Parameter[0] = ffffc783604e7390 << Device Object
Parameter[1] = (unknown)
Parameter[2] = ffffb409b3a777a8
Parameter[3] = (unknown)
05 ffffb409b3a77660 fffff8001dd2495d nt!PnpQueryID+34
Parameter[0] = ffffc783604e7580
Parameter[1] = 0000000000000000
Parameter[2] = ffffb409b3a777a8
Parameter[3] = ffffb409b3a77790
06 ffffb409b3a776c0 fffff8001dd27220 nt!PiProcessNewDeviceNode+11d
Parameter[0] = ffffc783604e7580 << Device Node
Parameter[1] = (unknown)
Parameter[2] = (unknown)
Parameter[3] = (unknown)
[...]
Wait! The first parameter of the bugcheck infers that the reference count for our object was being lowered, however, we can see clearly see in the call stack that the object's reference count was being incremented by the call to
nt!ObfReferenceObjectWithTag, what has happened here? Let's take a step back and examine each call stack frame in turn.
The
nt!PiProcessNewDeviceNode indicates that we're setting up a new device node, the first parameter to this function is a pointer to the device node object in question. We can dump it using the
!devnode command to verify that this is the case.
Rich (BB code):
1: kd> !devnode ffffc783604e7580
DevNode 0xffffc783604e7580 for PDO 0xffffc783604e7390
Parent 0xffffc7836029a010 Sibling 0xffffc783604e7af0 Child 0000000000
InstancePath is "ROOT\SYSTEM\0001"
State = DeviceNodeUninitialized (0x301)
Previous State = DeviceNodeRemoved (0x312)
StateHistory[12] = DeviceNodeRemoved (0x312)
StateHistory[11] = DeviceNodeQueryRemoved (0x310)
StateHistory[10] = DeviceNodeStarted (0x308)
StateHistory[09] = DeviceNodeEnumerateCompletion (0x30d)
StateHistory[08] = DeviceNodeEnumeratePending (0x30c)
StateHistory[07] = DeviceNodeStarted (0x308)
StateHistory[06] = DeviceNodeStartPostWork (0x307)
StateHistory[05] = DeviceNodeStartCompletion (0x306)
StateHistory[04] = DeviceNodeStartPending (0x305)
StateHistory[03] = DeviceNodeResourcesAssigned (0x304)
StateHistory[02] = DeviceNodeDriversAdded (0x303)
StateHistory[01] = DeviceNodeInitialized (0x302)
StateHistory[00] = DeviceNodeUninitialized (0x301)
StateHistory[19] = Unknown State (0x0)
StateHistory[18] = Unknown State (0x0)
StateHistory[17] = Unknown State (0x0)
StateHistory[16] = Unknown State (0x0)
StateHistory[15] = Unknown State (0x0)
StateHistory[14] = Unknown State (0x0)
StateHistory[13] = Unknown State (0x0)
Flags (0x00000031) DNF_MADEUP, DNF_ENUMERATED,
DNF_IDS_QUERIED
We then send a PnP IRP to the physical device object associated to the device node. From what I can find, the
nt!PnpIrpQueryID function is a wrapper which simply builds and sends an
IRP_MN_QUERY_ID to the device object. Let's verify this by examining the I/O status block shown in the second parameter of the
nt!IopSynchronousCall function.
Rich (BB code):
1: kd> dt _IO_STACK_LOCATION ffffb409b3a77600
nt!_IO_STACK_LOCATION
+0x000 MajorFunction : 0x1b '' << IRP_MJ_PNP
+0x001 MinorFunction : 0x13 '' << IRP_MN_QUERY_ID
+0x002 Flags : 0 ''
+0x003 Control : 0 ''
+0x008 Parameters : <anonymous-tag>
+0x028 DeviceObject : (null)
+0x030 FileObject : (null)
+0x038 CompletionRoutine : (null)
+0x040 Context : (null)
You can look up the minor codes for a given PnP using the following
page, and as we can see, the minor code of 0x13 corresponds to IRP_MN_QUERY_ID. This is what our wrapper function was sending earlier. This IRP can be used to enumerate the devices associated to a particular bus or to retrieve the unique device Id associated to a device. In either case, we can see a call gets made to the
nt!IoGetAttachedDeviceReferenceWithTag function which returns a pointer to the highest attached driver in a given device stack. The function is also responsible for incrementing the reference count of the said device object.
The function takes a single parameter which is a pointer to a device object in which to find highest most device in the device stack from. We'll dump it using the usual command of
!devobj.
Rich (BB code):
1: kd> !devobj ffffc783604e7390
Device object (ffffc783604e7390) is for:
Cannot read info offset from nt!ObpInfoMaskToOffset
\Driver\PnpManager DriverObject ffffc7836031fe50
Current Irp 00000000 RefCount 0 Type 00000004 Flags 00001040
SecurityDescriptor ffff800598be8d60 DevExt ffffc783604e74e0 DevObjExt ffffc783604e74e8 DevNode ffffc783604e7580
ExtensionFlags (0x00000010) DOE_START_PENDING
Characteristics (0x00000180) FILE_AUTOGENERATED_DEVICE_NAME, FILE_DEVICE_SECURE_OPEN
AttachedDevice (Upper) ffffc78367ae4050 \Driver\DDDriver
Device queue is not busy.
Notice the
AttachedDevice field is set to the device object which caused the crash? We should now have a good understanding of the steps which have lead up to the crash and why our device object reference count was being incremented. However, we still don't particularly understand why it crashed and to do that, we need to dive into the assembly of the function
nt!ObfReferenceObjectWithTag itself
. The function uses the
fast call calling convention which is only applicable on x86 and ignored on other architectures.
Rich (BB code):
1: kd> ub fffff800`1d871886
nt!IoGetAttachedDeviceReferenceWithTag+0x19:
fffff800`1d871869 e892cc0400 call nt!KeAcquireQueuedSpinLock (fffff800`1d8be500)
fffff800`1d87186e 488bcb mov rcx,rbx
fffff800`1d871871 408af8 mov dil,al
fffff800`1d871874 e8d7f20100 call nt!IoGetAttachedDevice (fffff800`1d890b50)
fffff800`1d871879 8bd6 mov edx,esi << Tag value
fffff800`1d87187b 488bc8 mov rcx,rax << Pointer to the device object
fffff800`1d87187e 488bd8 mov rbx,rax
fffff800`1d871881 e8ea510200 call nt!ObfReferenceObjectWithTag (fffff800`1d896a70)
Rich (BB code):
1: kd> u nt!ObfReferenceObjectWithTag
nt!ObfReferenceObjectWithTag:
fffff800`1d896a70 48895c2408 mov qword ptr [rsp+8],rbx << Push the object pointer onto the stack
fffff800`1d896a75 4889742410 mov qword ptr [rsp+10h],rsi << Push the tag value onto the stack
fffff800`1d896a7a 57 push rdi
fffff800`1d896a7b 4883ec30 sub rsp,30h << Adjust stack pointer to the object header
fffff800`1d896a7f 833d8a45a60000 cmp dword ptr [nt!ObpTraceFlags (fffff800`1e2fb010)],0
fffff800`1d896a86 488bf1 mov rsi,rcx
fffff800`1d896a89 bb01000000 mov ebx,1
fffff800`1d896a8e 0f8570e21700 jne nt!ObfReferenceObjectWithTag+0x17e294 (fffff800`1da14d04)
Rich (BB code):
1: kd> u fffff800`1da14d04
nt!ObfReferenceObjectWithTag+0x17e294:
fffff800`1da14d04 448bca mov r9d,edx
fffff800`1da14d07 448bc3 mov r8d,ebx
fffff800`1da14d0a 0fb6d3 movzx edx,bl
fffff800`1da14d0d 4883c1d0 add rcx,0FFFFFFFFFFFFFFD0h
fffff800`1da14d11 e8c2ac1400 call nt!ObpPushStackInfo (fffff800`1db5f9d8) << Check if the object is being traced
fffff800`1da14d16 90 nop
fffff800`1da14d17 e9781de8ff jmp nt!ObfReferenceObjectWithTag+0x24 (fffff800`1d896a94)
fffff800`1da14d1c 33d2 xor edx,ed
Rich (BB code):
1: kd> u fffff800`1d896a94
nt!ObfReferenceObjectWithTag+0x24:
fffff800`1d896a94 f0480fc15ed0 lock xadd qword ptr [rsi-30h],rbx (1) << Perform an atomic exchange operation, this is the object header
fffff800`1d896a9a 48ffc3 inc rbx (2)
fffff800`1d896a9d 4883fb01 cmp rbx,1 (3)
fffff800`1d896aa1 0f8e75e21700 jle nt!ObfReferenceObjectWithTag+0x17e2ac (fffff800`1da14d1c) (4)
fffff800`1d896aa7 488b742448 mov rsi,qword ptr [rsp+48h]
fffff800`1d896aac 488bc3 mov rax,rbx
fffff800`1d896aaf 488b5c2440 mov rbx,qword ptr [rsp+40h]
fffff800`1d896ab4 4883c430 add rsp,30h
The most important parts are listed from 1 to 4. The xadd instruction sums the two operands and then exchanges the destination register with the source. In this case, object header is set to the rbx register (1). Next, the pointer count is then incremented by 1; remember, the offset for the pointer count is 0x0 (2). We then compare the pointer count - which is now 1 - with the value of 1 (3). Since the pointer count is equal to 1, we then jump to another segment of code which sets up the bugcheck parameters and subsequently crashes the system.
Rich (BB code):
1: kd> u fffff800`1da14d1c
nt!ObfReferenceObjectWithTag+0x17e2ac:
fffff800`1da14d1c 33d2 xor edx,edx
fffff800`1da14d1e 48895c2420 mov qword ptr [rsp+20h],rbx
fffff800`1da14d23 41b910000000 mov r9d,10h
fffff800`1da14d29 4c8bc6 mov r8,rsi
fffff800`1da14d2c 8d4a18 lea ecx,[rdx+18h]
fffff800`1da14d2f e87c24feff call nt!KeBugCheckEx (fffff800`1d9f71b0)
Now, I believe the system calls the bugcheck on the previously described condition since you can't reference an object which has been marked for deletion. This is evident in the object header itself. As we can see, the
DeletedInline flag has been set, which indicates that this particular object has been queued for deletion by a worker thread.
Rich (BB code):
1: kd> dt _OBJECT_HEADER ffffc78367ae4050-0x30
nt!_OBJECT_HEADER
+0x000 PointerCount : 0n1
+0x008 HandleCount : 0n0
+0x008 NextToFree : (null)
+0x010 Lock : _EX_PUSH_LOCK
+0x018 TypeIndex : 0xb1 ''
+0x019 TraceFlags : 0 ''
+0x019 DbgRefTrace : 0y0
+0x019 DbgTracePermanent : 0y0
+0x01a InfoMask : 0x2 ''
+0x01b Flags : 0x82 ''
+0x01b NewObject : 0y0
+0x01b KernelObject : 0y1
+0x01b KernelOnlyAccess : 0y0
+0x01b ExclusiveObject : 0y0
+0x01b PermanentObject : 0y0
+0x01b DefaultSecurityQuota : 0y0
+0x01b SingleHandleEntry : 0y0
+0x01b DeletedInline : 0y1
+0x01c Reserved : 0xb886
+0x020 ObjectCreateInfo : (null)
+0x020 QuotaBlockCharged : (null)
+0x028 SecurityDescriptor : (null)
+0x030 Body : _QUAD
References:
Plug and Play Minor IRPs - Windows drivers
Journey Into the Object Manager Executive Subsystem: Object Header and Object Type
IoGetAttachedDeviceReference function (ntifs.h) - Windows drivers
ReactOS: ntoskrnl/io/pnpmgr/pnpirp.c File Reference
WRK/obref.c at master ยท bigzz/WRK
https://github.com/AdaCore/gsh/blob/master/os/src/ddk/wdm.h