IoGetDeviceObjectPointer引起的引用计数改变

45 篇文章 7 订阅
20 篇文章 1 订阅

    上周同事的驱动遇到HLK测试失败:HLK测试项检测到传感器设备驱动在响应IRP_MJ_PNP/IRP_MN_REMOVEDEVICE时,有句柄扔打开设备,于是我也帮着一起查找原因。这个驱动的架构如下设计:上层App负责通知驱动,驱动响应这个过程时,用IoGetDeviceObjectPointer获得并保持(IoGetDeviceObjectPointer后没有对返回的文件对象调用ObDerefernceObject)传感器设备对象;直到App通知驱动停止时,驱动才对文件对象进行解引用。看到如此流程,又因为这份驱动年代久远,期间又经由多人之手(简单的说就是没有spec),我初步断定就是因为文件对象一直没有解引用引起的HLK测试项失败。后来,经另一位同事提醒,当时的设计背景可能是考虑到传感器在使用过程中可能会被停用,为了保证传感器对象的有效性,原作者在驱动工作期间保持该对象的引用计数,这样,传感器对象至少在我们的驱动主动停用前不会被卸载。

    不太相关的背景交代完毕,现在回到题目。开始时,我看 IoGetDeviceObjectPointer返回设备对象和文件对象,以为这个函数会使目标设备的引用计数+2,调用ObDereferenceObject(FileObject);后,目标设备上的引用计数为1,所以总觉得还需要对设备对象进行一次解引用才能彻底释放。因为有这样的疑惑,我没有马上动手,而是写了一份测试代码调试了IoGetDeviceObjectPointer对指定设备的影响。我用的测试设备是DDK源码src\general\ioctl编译生成的sioctl设备;测试IoGetDeviceObjectPointer对sioctl的引用的源码如下:
#define NT_DEVICE_NAME      L"\\Device\\SIOCTL"
#define DOS_DEVICE_NAME     L"\\DosDevices\\IoctlTest"

NTSTATUS
DriverEntry(
    __in PDRIVER_OBJECT   DriverObject,
    __in PUNICODE_STRING      RegistryPath
    )
{
	UNICODE_STRING devStr;
	NTSTATUS ntStatus;
	FILE_OBJECT* fileObj;
	DEVICE_OBJECT* devObj;
	
	RtlInitUnicodeString(&devStr, DOS_DEVICE_NAME);
	ntStatus = IoGetDeviceObjectPointer(&devStr, FILE_ALL_ACCESS, &fileObj, &devObj);
	
	if(ntStatus == STATUS_SUCCESS)
		ObDereferenceObject(fileObj);
	
	return ntStatus;
}

下面是我的调试过程以我对调试输出的个人理解:

kd> g
KDTARGET: Refreshing KD connection
Breakpoint 0 hit
getdev!DriverEntry:
92ee1010 8bff            mov     edi,edi

驱动编译后模块名为getdev,我对DriverEntry下了延迟断点,这就可以观察调用IoGetDeviceObjectPointer前sioctl的引用计数:

kd> t
nt!IoGetDeviceObjectPointer:
82ca2e98 8bff            mov     edi,edi
kd> !drvobj sioctl
Driver object (850ff468) is for:
 \Driver\sioctl
Driver Extension List: (id , addr)

Device Object list:
850ff340  
kd> !devobj 850ff340  
Device object (850ff340) is for:
 SIOCTL \Driver\sioctl DriverObject 850ff468
Current Irp 00000000 RefCount 0 <------引用计数为0 Type 00000022 Flags 00000040
...
kd> !object 850ff340  
Object: 850ff340  Type: (84ff7bc8) Device
    ObjectHeader: 850ff328 (new version)
    HandleCount: 0  PointerCount: 2 ;<-------对象指针计数==2
    Directory Object: 88a0f710  Name: SIOCTL

刚进入IoGetDeviceObjectPointer时,sioctl引用计数等于0,另外它的对象指针计数为2,这是初始值。

查看 IoGetDeviceObjectPointer的源码,第一印象有4个函数可能会影响sioctl的引用计数,他们分别是:ZwOpenFile,ObReferenceObjectByHandle,IoGetRelatedDeviceObject,ZwClose.
NTSTATUS
NTAPI
IopGetDeviceObjectPointer(IN PUNICODE_STRING ObjectName,
                          IN ACCESS_MASK DesiredAccess,
                          OUT PFILE_OBJECT *FileObject,
                          OUT PDEVICE_OBJECT *DeviceObject,
                          IN ULONG AttachFlag)
{
    InitializeObjectAttributes(&ObjectAttributes,
                               ObjectName,
                               OBJ_KERNEL_HANDLE,
                               NULL,
                               NULL);
    Status = ZwOpenFile(&FileHandle,
                        DesiredAccess,
                        &ObjectAttributes,
                        &StatusBlock,
                        0,
                        FILE_NON_DIRECTORY_FILE | AttachFlag);
    if (!NT_SUCCESS(Status)) return Status;

    Status = ObReferenceObjectByHandle(FileHandle,
                                       0,
                                       IoFileObjectType,
                                       KernelMode,
                                       (PVOID*)&LocalFileObject,
                                       NULL);
    if (NT_SUCCESS(Status))
    {
        *DeviceObject = IoGetRelatedDeviceObject(LocalFileObject);
        *FileObject = LocalFileObject;
        ZwClose(FileHandle);
    }

    return Status;
}
我要做的就是在进出这些函数前后查看sioctl的对象指针及引用计数的变化,所以要在这些入口下断点。另外,在x86的机器上,设备对象的RefCount字段距对象头的偏移是4B,可以在这个位置下访问断点:
kd> u 82ca2eec
82ca2eec e89fa8dbff      call    nt!ZwOpenFile (82a5d790)
kd> u 82ca2f09 
82ca2f09 e8f7d1f9ff      call    nt!ObReferenceObjectByHandle (82c40105)
kd> u 82ca2f1e 
82ca2f1e e837c1e2ff      call    nt!IoGetRelatedDeviceObject (82acf05a)
kd> u 82ca2f2c 
82ca2f2c e84b9edbff      call    nt!ZwClose (82a5cd7c)
;普通断点
kd> bp 82ca2eec
kd> bp 82ca2f09 
kd> bp 82ca2f1e 
kd> bp 82ca2f2c 
;访问断点
kd> ba w 4 850ff340 +4
kd> bl
     0 e Disable Clear  92ee1010  [c:\users\eugene\desktop\studio\ioget\getdev.c @ 12]     0001 (0001) getdev!DriverEntry
     1 e Disable Clear  92ee1041  [c:\users\eugene\desktop\studio\ioget\getdev.c @ 21]     0001 (0001) getdev!DriverEntry+0x31
     2 e Disable Clear  82ca2eec     0001 (0001) nt!IoGetDeviceObjectPointer+0x54
     3 e Disable Clear  82ca2f09     0001 (0001) nt!IoGetDeviceObjectPointer+0x71
     4 e Disable Clear  82ca2f1e     0001 (0001) nt!IoGetDeviceObjectPointer+0x86
     5 e Disable Clear  82ca2f2c     0001 (0001) nt!IoGetDeviceObjectPointer+0x94
     6 e Disable Clear  850ff344 w 4 0001 (0001) 
好了,准备工作做好了,开始我的探索之旅:
kd> g
Breakpoint 6 hit
nt!IopCheckDeviceAndDriver+0x45:
82ab8393 33f6            xor     esi,esi
kd> kb
 # ChildEBP RetAddr  Args to Child              
00 807e5750 82c5b889 19870092 807e58f8 00000000 nt!IopCheckDeviceAndDriver+0x45
01 807e5828 82c3d1d7 850ff340 84ff7970 87248008 nt!IopParseDevice+0x133
02 807e58a4 82c6324d 00000000 807e58f8 00000240 nt!ObpLookupObjectName+0x4fa
03 807e5904 82c5b5ab 807e5a90 84ff7970 00022000 nt!ObOpenObjectByName+0x159
04 807e5980 82c965ee 807e5a80 001f01ff 807e5a90 nt!IopCreateFile+0x673
05 807e59c8 82a5f42a 807e5a80 001f01ff 807e5a90 nt!NtOpenFile+0x2a
06 807e59c8 82a5d7a1 807e5a80 001f01ff 807e5a90 nt!KiFastCallEntry+0x12a
07 807e5a58 82ca2ef1 807e5a80 001f01ff 807e5a90 nt!ZwOpenFile+0x11
08 807e5aac 92ee103e 807e5acc 001f01ff 807e5ad4 nt!IoGetDeviceObjectPointer+0x59
09 807e5ad8 82bbf728 8514fea8 85141000 00000000 getdev!DriverEntry+0x2e [c:\users\eugene\desktop\studio\ioget\getdev.c @ 19] 
...
首次触发的断点是访问断点, ZwOpenFile打开sioctl设备的过程中,OS对sioctl的引用计数做了加1操作,我们可以在ZwOpenFile返回后验证:
kd> g
Breakpoint 3 hit
nt!IoGetDeviceObjectPointer+0x71:
82ca2f09 e8f7d1f9ff      call    nt!ObReferenceObjectByHandle (82c40105)
kd> kb
 # ChildEBP RetAddr  Args to Child              
00 807e5aac 92ee103e 807e5acc 001f01ff 807e5ad4 nt!IoGetDeviceObjectPointer+0x71
01 807e5ad8 82bbf728 8514fea8 85141000 00000000 getdev!DriverEntry+0x2e [c:\users\eugene\desktop\studio\ioget\getdev.c @ 19] 
...
kd> r esp
esp=807e5a60

kd> !handle poi(@esp) 7
PROCESS 84fe5a20  SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000
    DirBase: 00185000  ObjectTable: 88a01b40  HandleCount: 480.
    Image: System
Kernel handle table at 88a01b40 with 480 entries in use
800007f8: Object: 85153d58  GrantedAccess: 001f01ff Entry: 88a03ff0
Object: 85153d58  Type: (84ff7970) File
    ObjectHeader: 85153d40 (new version)
        HandleCount: 1  PointerCount: 1

kd> !fileobj 85153d58  
Device Object: 0x850ff340   \Driver\sioctl
Vpb is NULL
Event signalled

Flags:  0x40000
	Handle Created

CurrentByteOffset: 0
ZwOpenFile打开sioctl的文件对象,并返回文件对象的句柄。我们可以简单的认为句柄直到ZwOpenFile函数返回才有效。当获得有效句柄后,就能用!handle调试命令获得句柄对应的对象及对象状态。现在的问题是从哪里能获得ZwOpenFile返回的句柄?查看IoGetDeviceObjectPointer的源码发现,ObReferenceObjectByHandle会用到这个句柄。问题变得很简单了,在执行
82ca2f09 e8f7d1f9ff      call    nt!ObReferenceObjectByHandle (82c40105)
前,OS已经准备好了必要的参数,所以,只要从堆栈上获得参数就行了。此时堆栈顶由esp指向,所以我从esp处取出了sioctl句柄,正是这句:
kd> !handle poi(@esp) 7
windbg分析认为传入的是指向文件对象的句柄,文件对象的句柄计数和对象计数分别是1。 结合<深入解析Windows操作系统>知,句柄数增加和对象数增加是一一对应的,每次OS打开返回一个句柄,必然在内核中准备了一个对象。

    前文曽怀疑调用ObReferenceObjectByHandle后,会影响sioctl的引用计数。现在来验证猜想是否正确。
kd> p
nt!IoGetDeviceObjectPointer+0x76:
82ca2f0e 8bf8            mov     edi,eax
kd> ub . L2
nt!IoGetDeviceObjectPointer+0x6d:
82ca2f05 ff74241c        push    dword ptr [esp+1Ch]
82ca2f09 e8f7d1f9ff      call    nt!ObReferenceObjectByHandle (82c40105)

kd> !object 85153d58  
Object: 85153d58  Type: (84ff7970) File
    ObjectHeader: 85153d40 (new version)
    HandleCount: 1  PointerCount: 2
上面的调试记录,我用p单步越过ObReferenceObjectByHandle,然后再查看对象状态(!object 85153d58查看的就是ZwOpenFile返回的句柄所指向的文件对象的状态)。windbg分析出,自调用ObReferenceObjectByHandle后,对象的句柄计数保持为1,而指针对象增加到2。 由于,内核除了可以使用句柄,还能单独打开并使用句柄背后的对象。所以在内核中,同一个对象的记录的PointerCount>=HandlerCount。

    前面执行ZwOpenFile,返回了sioctl设备对应的文件对象,所以暂时没有影响设备对象的PointerCount。但接下来马上会调用IoGetRelatedDeviceObject----从文件对象获得设备对象----这是不是会增加设备对象的PointerCount?继续调试并验证这个猜想。先看下调用IoGetRelatedDeviceObject前设备对象的各种计数:
kd> g
Breakpoint 4 hit
nt!IoGetDeviceObjectPointer+0x86:
82ca2f1e e837c1e2ff      call    nt!IoGetRelatedDeviceObject (82acf05a)
kd> ub . L2
nt!IoGetDeviceObjectPointer+0x83: ;IoGetRelatedDeivceObject的参数是FileObject,结合反汇编可知,eax中保存了FileObject
82ca2f1b 50              push    eax
82ca2f1c 8901            mov     dword ptr [ecx],eax
kd> r eax
eax=85153d58
kd> !object 850ff340  
Object: 850ff340  Type: (84ff7bc8) Device
    ObjectHeader: 850ff328 (new version)
    HandleCount: 0  PointerCount: 2
    Directory Object: 88a0f710  Name: SIOCTL
!object 850ff340
上面命令用于查看设备对象(0x850ff340是设备对象的地址)状态的命令。
1).刚进入IoGetDeviceObjectPointer时执行过这条命令;
2).调用了IoGetRelatedDeviceObject,并从FileObject成功获得DeviceObject后,再次执行这条命令;
对比1),2)处两次命令执行,除了设备对象的RefCount不同以外,其他输出一致。侧面反映了在IoGetDeviceObjectPointer中执行ZwOpenFile和ObReferenceObjectByHandle以及IoGetRelatedDeviceObject函数并不会影响设备对象的HandleCount和PointerCount的值。
有点奇怪,IoGetRelatedDeviceObject明明用于获得设备对象,可是却没有修改PointerCount的值,为什么会这样?答案在源码中:
PDEVICE_OBJECT
NTAPI
IoGetRelatedDeviceObject(IN PFILE_OBJECT FileObject)
{
    PDEVICE_OBJECT DeviceObject = FileObject->DeviceObject;
    ...
    else
    {
        /* Otherwise, this was a direct open */
        DeviceObject = FileObject->DeviceObject;
    }
    ...
    /* Check if we were attached */
    if (DeviceObject->AttachedDevice)
    {
        /* Check if the file object has an extension present */
        if (FileObject->Flags & FO_FILE_OBJECT_HAS_EXTENSION)
        {
            /* Sanity check, direct open files can't have this */
            ASSERT(!(FileObject->Flags & FO_DIRECT_DEVICE_OPEN));

            /* Check if the extension is really present */
            if (FileObject->FileObjectExtension)
            {
                /* FIXME: Unhandled yet */
                DPRINT1("FOEs not supported\n");
                KEBUGCHECK(0);
            }
        }

        /* Return the highest attached device */
        DeviceObject = IoGetAttachedDevice(DeviceObject);
    }

    /* Return the DO we found */
    return DeviceObject;
}
因为FileObject中存有DeviceObject的指针,并没有经过对象管理器分配DeviceObject对象,所以不会增加sioctl的PointerCount。
  在IoGetDeviceObjectPointer的尾部,执行ZwClose关闭句柄。毋庸置疑,这一定会减少FileObject的HandleCount。前面说过,HandleCount和PoniterCount是一一对应的关系,HandleCount减少,必然是PointerCount减少引起的。所以,可以确定FileObject的PointerCount也减少了。有调试输出为证:
kd> g
Breakpoint 5 hit
nt!IoGetDeviceObjectPointer+0x94:
82ca2f2c e84b9edbff      call    nt!ZwClose (82a5cd7c)
kd> r esp
esp=807e5a74 ;调用ZwClose前,栈顶保存了ZwClose的参数,也就是句柄
kd> !handle poi(@esp)
...
800007f8: Object: 85153d58  GrantedAccess: 001f01ff Entry: 88a03ff0
Object: 85153d58  Type: (84ff7970) File
    ObjectHeader: 85153d40 (new version)
        HandleCount: 1  PointerCount: 2
;调用ZwClose前HandleCount=1,PointerCount=2
kd> p ;Step Over ZwClose
nt!IoGetDeviceObjectPointer+0x99:
82ca2f31 8bc7            mov     eax,edi
;调用ZwClose后,句柄无效,所以不再使用!handle命令;但是FileObject仍然存在,因此还能用!object来查看FileObject状态
kd> !object 85153d58  
Object: 85153d58  Type: (84ff7970) File
    ObjectHeader: 85153d40 (new version)
    HandleCount: 0  PointerCount: 1
    当IoGetDeviceObjectPointer返回到示例驱动中,sioctl对应的FileObject!pointerCount保持为1,sioctl的RefCount同样为1:
kd> !devobj 850ff340  ;850ff340是sioctl的设备对象地址
Device object (850ff340) is for:
 SIOCTL \Driver\sioctl DriverObject 850ff468
Current Irp 00000000 RefCount 1 ;<----------------------引用计数仍为1
SecurityDescriptor 88a51678 DevExt 00000000 DevObjExt 850ff3f8 
ExtensionFlags (0x00000800)  DOE_DEFAULT_SD_PRESENT
Characteristics (0x00000100)  FILE_DEVICE_SECURE_OPEN
Device queue is not busy.
kd> !object 850ff340  
Object: 850ff340  Type: (84ff7bc8) Device
    ObjectHeader: 850ff328 (new version)
    HandleCount: 0  PointerCount: 2 ; <-----------HandleCount/PointerCount稳定保持不变,呵呵,真是波澜不惊
    Directory Object: 88a0f710  Name: SIOCTL
    难道sioctl的引用计数永远锁定在1了?岂不是泄露了?倒也不至于,只要对FileObject执行一次解引用(ObDereferenceObject),sioctl!RefCount就归零了:
kd> g
Breakpoint 1 hit ;断点1是测试代码中的ObDereferenceObject语句
getdev!DriverEntry+0x31:
92ee1041 837dec00        cmp     dword ptr [ebp-14h],0
kd> p 
getdev!DriverEntry+0x37:
92ee1047 8b4dfc          mov     ecx,dword ptr [ebp-4]
kd> p ;单步越过ObDereferenceObject后,还会触发访问断点,即sioctl设备对象的引用计数被清零了
Breakpoint 6 hit
nt!IopDecrementDeviceObjectRef+0x12:
82ac39d2 8ad0            mov     dl,al
kd> !object 0x85153d58 ;再次查看FileObject,发现pointerCount终于清零了
Object: 85153d58  Type: (84ff7970) File
    ObjectHeader: 85153d40 (new version)
    HandleCount: 0  PointerCount: 0
kd> g
Breakpoint 2 hit

    最后总结一下:在驱动中对某个设备调用IoGetDeviceObjectPointer后,OS对象管理器中新生了该设备对应的文件对象。在文件对象诞生过程中,引发了设备引用计数的增加。另外,由于文件对象的存在,该设备就一直处于被占用状态,直到文件对象被解引用。需要特别注意的,文件对象虽然被解引用了----设备对象的引用计数也减少了----IoGetDeviceObjectPointer返回的设备对象仍然可以在接下去的代码中使用。

参考资料:<深入理解Windows操作系统>P141-142 对象保持力
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值