我目前正在使用liveKD执行内核调试。
在我发生阻塞的所有情况下(一个永远不会返回的::CloseHandle()
函数调用)我碰巧有一个堆栈跟踪,它在synchronisationEvent
的内核中阻塞。
但如果我在!object 12345678
进行了synchronisationEvent
,如果该流程的线程信息中报告了我的Not a valid object (ObjectType invalid).
,那么synchronisationEvent
我担心在用户模式下我们的应用程序级别的损坏是否会破坏内核? Windows是否保证了内存空间的分离,以防止出现类似内容?
应用程序的代码密集使用C ++,COM / DCOM和Win32。
<小时/> 一条评论要求提供有关stacktrace和句柄类型的更多信息。 在这种情况下,它与串行COM端口有关。但我想我也有文件句柄(尚未调试那些案例) 这是我的堆栈跟踪:
THREAD 856a2d48 Cid 0660.0350 Teb: 7ff25000 Win32Thread: ffaaedd8 WAIT: (Executive) KernelMode Non-Alertable 860c6f9c SynchronizationEvent IRP List: babea5d8: (0006,01d8) Flags: 00000404 Mdl: 00000000 Not impersonating DeviceMap 89809fc8 Owning Process 86212d40 Image: DataCaptorIS.exe Attached Process N/A Image: N/A Wait Start TickCount 27315407 Ticks: 6067021 (1:02:17:26.134) Context Switch Count 2259 IdealProcessor: 0 UserTime 00:00:04.976 KernelTime 00:00:02.184 Win32 Start Address 0x775c03e9 Stack Init 8aa0dfd0 Current 8aa0da98 Base 8aa0e000 Limit 8aa0b000 Call 0 Priority 9 BasePriority 8 UnusualBoost 0 ForegroundBoost 0 IoPriority 2 PagePriority 5 ChildEBP RetAddr Args to Child 8aa0dab0 824bfced 856a2d48 00000000 8ab00120 nt!KiSwapContext+0x26 (FPO: [Uses EBP] [0,0,4]) 8aa0dae8 824beb4b 856a2e08 856a2d48 860c6f9c nt!KiSwapThread+0x266 8aa0db10 824b856f 856a2d48 856a2e08 00000000 nt!KiCommitThreadWait+0x1df 8aa0db88 914539fb 860c6f9c 00000000 00000000 nt!KeWaitForSingleObject+0x393 8aa0dbcc 82478c1e 860c6f98 babea5d8 babea73c serial!SerialClose+0x332 (FPO: [Non-Fpo]) 8aa0dbe4 886206b9 babea5d8 861986d0 bc859308 nt!IofCallDriver+0x63 8aa0dc08 82478c1e 861986d0 860c6890 00000800 serenum!Serenum_CreateClose+0x77 (FPO: [Non-Fpo]) 8aa0dc20 82673be6 84aa7a00 bc8592f0 00000000 nt!IofCallDriver+0x63 8aa0dc64 826647c9 bc859308 bc859308 bc8592f0 nt!IopDeleteFile+0x10c 8aa0dc7c 824ba1e0 00000000 856a2d48 bc8592f0 nt!ObpRemoveObjectRoutine+0x59 8aa0dc90 824ba150 bc859308 82687556 960a5578 nt!ObfDereferenceObjectWithTag+0x88 (FPO: [0,0,3]) 8aa0dc98 82687556 960a5578 856a2d48 00000c80 nt!ObfDereferenceObject+0xd (FPO: [0,1,0]) 8aa0dcdc 8268727c 960a5578 a7d21900 86212d40 nt!ObpCloseHandleTableEntry+0x21d 8aa0dd0c 82687616 86212d40 856a2d01 0763f3b4 nt!ObpCloseHandle+0x7f 8aa0dd28 8247f8a6 00000c80 0763f3b8 775d7094 nt!NtClose+0x4e 8aa0dd28 775d7094 00000c80 0763f3b8 775d7094 nt!KiSystemServicePostCall (FPO: [0,3] TrapFrame @ 8aa0dd34) WARNING: Frame IP not in any known module. Following frames may be wrong. 0763f3b8 00000000 00000000 00000000 00000000 0x775d7094
此堆栈跟踪表明线程正在等待kd> !object 860c6f9c
860c6f9c。
命令Not a valid object (ObjectType invalid)
返回synchronisationEvent
。
我不知道这是否意味着内核中::CancelIoEx(m_handle_to_serial_port_com);
WaitForRequestToComplete(); // our function calls ::GetOverlappedResult(..., bWait) for any OVERLAPPED that was pending, with bWait == TRUE
::PurgeComm(m_handle_to_serial_port_com);
WaitForRequestToComplete(); // our function calls ::GetOverlappedResult(..., bWait) for any OVERLAPPED that was pending, with bWait == TRUE
::CloseHandle(m_handle_to_serial_port_com); // the closeHandle which never returns
已损坏。当我在该进程的其他synchronisationEvent上应用该命令时,我得到一个输出,如:
0: kd> !object 95369c68 Object: 95369c68 Type: (84aa6378) Event ObjectHeader: 95369c50 (new version) HandleCount: 1 PointerCount: 2
在应用程序级别,在usermode中,这种情况发生在应该取消并清除任何IRP的那种代码之后:
{{1}}
问题实际上是随机发生的。有时它需要几天才能复制。
<小时/> 显示synchronisationEvent对象的内存地址86184f9c(在另一台具有相同错误的计算机上):
0: kd> dp 86184f9c - @@c++(sizeof(nt!_object_header) - #RTL_FIELD_SIZE(nt!_object_header, Body)) 86184f84 00000006 00000000 00000000 00000000 86184f94 00000000 00000001 00040001 00000000 86184fa4 85917420 85917420 00000000 00000000 86184fb4 00000000 00000000 00000000 0000000d 86184fc4 86184890 00000040 00000000 00000800 86184fd4 00000000 85747a68 00000000 00000000 86184fe4 00000000 00000000 86184fec 86184fec 86184ff4 86184ff4 86184ff4 96d4c000 040d0000
并尝试显示对象标题:
0: kd> dt nt!_object_header 86184f9c - @@c++(sizeof(nt!_object_header) - #RTL_FIELD_SIZE(nt!_object_header, Body)) Cannot find specified field members.
答案 0 :(得分:5)
它绝对应该是。如果内核在用户模式下可以破坏,那么您将遇到可能会影响计算机上所有用户的安全漏洞。您可以通过使内核崩溃来拒绝服务。您可以通过利用内核缓冲区溢出来提升权限。您可以通过使用内核信息泄露漏洞来窃取他人的数据。
仅仅因为你输入内核不好的数据并不意味着它有这样的风俗。内核可能足够智能以检测和防止此类问题,或者它可能执行任何可能遭受用户空间中的用户态输入损坏的代码。
如果您确实发现了一个让您崩溃内核的错误,请阅读传递给内核的其他人的数据或类似内容,那么您应该将其报告给Microsoft。如果您怀疑自己找到了某些内容,请尝试与MS支持人员联系,看看他们是否可以提供帮助。他们是操作系统的专家,并且最有可能确定您的可疑缺陷是否是一个真正的缺陷。
答案 1 :(得分:3)
这只是对!object
无法处理指定地址的原因的跟进。
TL; DR:您的SynchronizationEvent未损坏。传递给!object
命令的地址不是内核对象,它只是一个内核结构。
kd !object
命令只是查找所有内核对象前面的特殊结构(即nt!_OBJECT_HEADER
)。更确切地说,一旦内核结构在其之前具有nt!_OBJECT_HEADER
,它就会成为对象。一旦结构以_OBJECT_HEADER
为前缀,它就变成了一个内核对象,然后由内核对象管理器处理(特别是所有那些以Ob
前缀开头的内核函数,但还涉及其他函数在内核中的对象管理中。)
如果内核想要创建一个Event,特别是如果这个对象不必跨越user-land / kernel-land边界(或者如果不需要引用计数)那么内核可能只创建一个{{1没有nt!_KEVENT
的结构。
检查地址是否是内核对象
查看堆栈跟踪,我们有以下两行:
nt!_OBJECT_HEADER
幸运的是, 8aa0db88 914539fb 860c6f9c 00000000 00000000 nt!KeWaitForSingleObject+0x393
8aa0dbcc 82478c1e 860c6f98 babea5d8 babea73c serial!SerialClose+0x332 (FPO: [Non-Fpo])
是Microsoft驱动程序,因此我们有符号信息。查看serial.sys
大约偏移0x332内的代码,我们有以下代码:
serial!SerialClose
等待的代码的事件(PAGESER:0001EEFC lea eax, [esi+654h]
PAGESER:0001EF02 push ebx ; Timeout
PAGESER:0001EF03 push ebx ; Alertable
PAGESER:0001EF04 push ebx ; WaitMode
PAGESER:0001EF05 push ebx ; WaitReason
PAGESER:0001EF06 push eax ; Object
PAGESER:0001EF07 call ds:__imp__KeWaitForSingleObject@20 ; KeWaitForSingleObject(x,x,x,x,x)
类型)来自KEVENT
...
在函数开始时回溯,我们有:
[esi+0x654]
所以esi(在PAGESER:0001EBD5 mov esi, [ebp+DeviceObject]
PAGESER:0001EBD8 push edi
PAGESER:0001EBD9 mov esi, [esi+_DEVICE_OBJECT.DeviceExtension]
中)是来自设备对象的设备扩展。
在整个串行驱动程序代码中搜索此偏移量会返回几个实例。事件初始化在[esi+0x654]
:
serial!SerialCreateDevObj
这告诉我们PAGESRP0:000194AF push esi ; State
PAGESRP0:000194B0 push 1 ; Type
PAGESRP0:000194B2 lea eax, [ebx+654h]
PAGESRP0:000194B8 push eax ; Event
PAGESRP0:000194B9 call edi ; KeInitializeEvent(x,x,x) ; KeInitializeEvent(x,x,x)
是内核结构,而不是内核对象,因为它使用KEVENT
。
关于KEVENTs的更多信息
假设我有一个带有SynchronizationEvent的线程:
KeInitializeEvent
内核(和kd)知道线程正在等待,因为这个线程有一个waitblocklist并且它不是空的:
kd> !thread ffffe001ef7a5400
THREAD ffffe001ef7a5400 Cid 0538.054c Teb: 00007ff7a9869000 Win32Thread: fffff901406825d0 WAIT: (Executive) KernelMode Non-Alertable
ffffd0006dba6278 SynchronizationEvent
...
等待是不可警告的,因为kd> dt _kthread ffffe001ef7a5400 waitblocklist
ntdll!_KTHREAD
+0x0d0 WaitBlockList : 0xffffe001`ef7a5540 _KWAIT_BLOCK
字段为0:
alertable
这是一个内核模式(!=用户模式)等待,因为线程kd> dt _kthread ffffe001ef7a5400 alertable
ntdll!_KTHREAD
+0x074 Alertable : 0y0
为0:
waitmode
线程kd> dt _kthread ffffe001ef7a5400 waitmode
ntdll!_KTHREAD
+0x187 WaitMode : 0 ''
是WaitBlockList
类型的结构:
_KWAIT_BLOCK
如果您查看上面的kd> dt _kwait_block 0xffffe001`ef7a5540
ntdll!_KWAIT_BLOCK
+0x000 WaitListEntry : _LIST_ENTRY [ 0xffffd000`6dba6280 - 0xffffd000`6dba6280 ]
+0x010 WaitType : 0x1 ''
+0x011 BlockState : 0x4 ''
+0x012 WaitKey : 0
+0x014 SpareLong : 0n1089
+0x018 Thread : 0xffffe001`ef7a5400 _KTHREAD
+0x018 NotificationQueue : 0xffffe001`ef7a5400 _KQUEUE
+0x020 Object : 0xffffd000`6dba6278 Void
+0x028 SparePtr : (null)
,您会看到有一个_KWAIT_BLOCK
字段,表示该线程正在等待的对象。
我们知道这是一个事件,但所有可调度对象都有一个调度标头,因此我们可以Object
结构帮助dt
Object
指针。
nt!_DISPATCHER_HEADER
可以作为由KEVENT
初始化的独立数据结构存在,也可以作为使用KeInitializeEvent()
创建的内核(事件)对象存在:如果事件使用NtCreateEvent()
初始化,那么它是不是内核对象,而如果用KeInitializeEvent()
初始化,那么它就是一个内核对象。
NtCreateEvent()
结构只是nt!_KEVENT
结构的包装。
nt!_DISPATCHER_HEADER
刚刚给出了一个内核事件,你可以知道哪些线程正在等待事件变为信号状态(事件被设置),但是最大的问题是你无法知道谁应该设置这个特定的事件。 / p>
在您的情况下,只有三个函数会在串行驱动程序中设置事件:0: kd> dt _KEVENT
nt!_KEVENT
+0x000 Header : _DISPATCHER_HEADER
kd> dt _dispatcher_header 0xffffd000`6dba6278
ntdll!_DISPATCHER_HEADER
+0x000 Lock : 0n1594228737
+0x000 LockNV : 0n1594228737
+0x000 Type : 0x1 ''
+0x001 Signalling : 0 ''
+0x002 Size : 0x6 ''
+0x003 Reserved1 : 0x5f '_'
+0x000 TimerType : 0x1 ''
+0x001 TimerControlFlags : 0 ''
+0x001 Absolute : 0y0
+0x001 Wake : 0y0
+0x001 EncodedTolerableDelay : 0y000000 (0)
+0x002 Hand : 0x6 ''
+0x003 TimerMiscFlags : 0x5f '_'
+0x003 Index : 0y011111 (0x1f)
+0x003 Inserted : 0y1
+0x003 Expired : 0y0
+0x000 Timer2Type : 0x1 ''
+0x001 Timer2Flags : 0 ''
+0x001 Timer2Inserted : 0y0
+0x001 Timer2Expiring : 0y0
+0x001 Timer2CancelPending : 0y0
+0x001 Timer2SetPending : 0y0
+0x001 Timer2Running : 0y0
+0x001 Timer2Disabled : 0y0
+0x001 Timer2ReservedFlags : 0y00
+0x002 Timer2Reserved1 : 0x6 ''
+0x003 Timer2Reserved2 : 0x5f '_'
+0x000 QueueType : 0x1 ''
+0x001 QueueControlFlags : 0 ''
+0x001 Abandoned : 0y0
+0x001 DisableIncrement : 0y0
+0x001 QueueReservedControlFlags : 0y000000 (0)
+0x002 QueueSize : 0x6 ''
+0x003 QueueReserved : 0x5f '_'
+0x000 ThreadType : 0x1 ''
+0x001 ThreadReserved : 0 ''
+0x002 ThreadControlFlags : 0x6 ''
+0x002 CycleProfiling : 0y0
+0x002 CounterProfiling : 0y1
+0x002 GroupScheduling : 0y1
+0x002 AffinitySet : 0y0
+0x002 ThreadReservedControlFlags : 0y0000
+0x003 DebugActive : 0x5f '_'
+0x003 ActiveDR7 : 0y1
+0x003 Instrumented : 0y1
+0x003 Minimal : 0y1
+0x003 Reserved4 : 0y011
+0x003 UmsScheduled : 0y1
+0x003 UmsPrimary : 0y0
+0x000 MutantType : 0x1 ''
+0x001 MutantSize : 0 ''
+0x002 DpcActive : 0x6 ''
+0x003 MutantReserved : 0x5f '_'
+0x004 SignalState : 0n0
+0x008 WaitListHead : _LIST_ENTRY [ 0xffffe001`ef7a5540 - 0xffffe001`ef7a5540 ]
,serial!SerialSetPendingDpcEvent
和serial!SerialDpcEpilogue
。获得这些功能肯定是另一个问题......
答案 2 :(得分:2)
假设在环0中运行的内核不能从在环3中运行的进程中更改.Windows从微处理器的硬件支持中继以实现内存和I / O隔离,因此任何进程都不能访问内存在内核空间中,也不在其他用户的内存空间中。
访问内核的唯一方法是通过系统调用。 Windows中的系统调用采用称为“本机API”的API的形式。一个例子是NtCreateFile
,它是代表对CreateFile
函数的调用而调用的函数。 NtCreateFile
必须检查所有参数的有效性。这个相同的函数可以从内核本身访问ZwCreateFile
。当从内核调用时,它不会进行检查,因为内核信任在内核模式下运行的任何代码。
答案 3 :(得分:2)
CloseHandle()仅在句柄的所有待处理IO完成后才会完成。根据你发布的帖子信息,这个帖子至少有一个待处理的IRP - 我要看一下你是否试图关闭同一个对象。
FWIW,I/O Cancellation is not always supported - 您的串口设备是否支持取消?