3
votes

I'm currently performing a kernel debug with liveKD.

In all the cases where my blocking happens ( a ::CloseHandle() function call which never returns ) I happen to have a stacktrace which blocks in the kernel on a synchronisationEvent.

But when I do !object 12345678 if 123456789 is my synchronisationEvent as reported in the thread information of the process, it says Not a valid object (ObjectType invalid).

I'm worrying if corruptions at our application level in user mode could corrupt the kernel ? Does windows guarantee anything like a seperation of the memory spaces that would prevent from something like that ?

The code of the application uses C++, COM/DCOM, and Win32 intensively.


One comment asked for more information about the stacktrace and the handle type. In this case it is related to serial com port. But I think I also have it for file handles (not debugged those cases yet) This is the kind of stacktrace I have:
        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

This stacktrace states that the thread is waiting on synchronisationEvent 860c6f9c.

The command kd> !object 860c6f9c returns Not a valid object (ObjectType invalid). I don't know if this does mean that the synchronisationEvent is corrupted in the Kernel. When I apply the command on other synchronisationEvent of the process I get an output rather like:

0: kd> !object 95369c68
Object: 95369c68  Type: (84aa6378) Event
    ObjectHeader: 95369c50 (new version)
    HandleCount: 1  PointerCount: 2

At the application level, in usermode, this happens after that kind of code that should have canceled and purged any IRP:

::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

The problems occurs really randomly. Sometimes it takes days before it reproduces.


Displaying the memory for the synchronisationEvent object a address 86184f9c ( on another machine with the same bug):
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

and trying to display the object header:

0: kd> dt nt!_object_header 86184f9c - @@c++(sizeof(nt!_object_header) - #RTL_FIELD_SIZE(nt!_object_header, Body))
Cannot find specified field members.
4
What sort of handle are you closing? It would really help if you posted your stack trace.Bukes
Could you post the output of dp <obj> - @@c++(sizeof(nt!_object_header) - #RTL_FIELD_SIZE(nt!_object_header, Body)) and dt nt!_object_header <obj> - @@c++(sizeof(nt!_object_header) - #RTL_FIELD_SIZE(nt!_object_header, Body)) where <obj> is the address of the synchronisationEvent reported by the !threadcommand? (if you haven't solved your problem yet...)Neitsa
@Neitsa kd output for the two commands added. I suppose there's a mistake in the second command, but I have not figured it yetStephane Rolland

4 Answers

5
votes

It's definitely supposed to be. If the kernel were corruptable from user mode, you would have a security flaw that would potentially impact all users on the machine. You could deny service by making the kernel crash. You could elevate privileges by taking advantage of a kernel buffer overrun. You could steal someone else's data by using a kernel information disclosure flaw.

Just because you feed the kernel bad data does not mean it has such a vuln. The kernel may be smart enough to detect and prevent such issues, or it may execute any code that could suffer from corrupt userland input in userland.

If you have actually found a bug that lets you crash the kernel, read someone else's data that was passed to the kernel, or something similar, then you should report it to Microsoft. If you suspect that you have found something, try contacting MS support and see if they can help. They are the experts on the OS and most likely to be able to identify if your suspected flaw is a real flaw.

3
votes

This is just a follow up on why !object is not working on the given address.

TL;DR: Your SynchronizationEvent is not corrupted. The address passed to the !object command is not a kernel object, it's just a kernel structure.

The kd !object command simply looks for a special structure (namely nt!_OBJECT_HEADER) that is prepended to all kernel objects. More precisely a kernel structure becomes an object as soon as it has a nt!_OBJECT_HEADER just before it. Once a structure is prepended with this _OBJECT_HEADER, it becomes a kernel object and is then handled by the kernel object manager (specifically all those kernel functions that start with the Ob prefix, but there are other functions involved in object management in the kernel).

If the kernel wants to create an Event but especially if this object doesn't have to cross the user-land / kernel-land boundary (or if no ref counting is needed) then the kernel might just create a nt!_KEVENT structure with no nt!_OBJECT_HEADER.

Checking if the address is a kernel object (or not)

Looking at your stack trace we have these two lines:

    8aa0db88 914539fb 860c6f9c 00000000 00000000 nt!KeWaitForSingleObject+0x393
    8aa0dbcc 82478c1e 860c6f98 babea5d8 babea73c serial!SerialClose+0x332 (FPO: [Non-Fpo])

Fortunately, serial.sys is a Microsoft driver, so we have symbolic information. Looking at the code inside serial!SerialClose circa offset 0x332, we have this code:

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)

The event (KEVENT type) the code waiting on is from [esi+0x654]... Backtracking at the start of the function, we have:

PAGESER:0001EBD5                 mov     esi, [ebp+DeviceObject]
PAGESER:0001EBD8                 push    edi
PAGESER:0001EBD9                 mov     esi, [esi+_DEVICE_OBJECT.DeviceExtension]

So esi (in [esi+0x654]) is the device extension from the device object.

Searching for this offset in the whole serial driver code returns a few instances. The event initialization is done in 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)

This tells us that the KEVENT is a kernel structure, not a kernel object, since it uses KeInitializeEvent.

A little bit more on KEVENTs

Let's say I have a thread with a SynchronizationEvent:

kd> !thread ffffe001ef7a5400
THREAD ffffe001ef7a5400  Cid 0538.054c  Teb: 00007ff7a9869000 Win32Thread: fffff901406825d0 WAIT: (Executive) KernelMode Non-Alertable
    ffffd0006dba6278  SynchronizationEvent
...

The kernel (and kd) knows the thread is waiting because this thread has a waitblocklist and it's not empty:

kd> dt _kthread ffffe001ef7a5400 waitblocklist
ntdll!_KTHREAD
   +0x0d0 WaitBlockList : 0xffffe001`ef7a5540 _KWAIT_BLOCK

The wait is non-alertable because alertable field is 0:

kd> dt _kthread ffffe001ef7a5400 alertable
ntdll!_KTHREAD
   +0x074 Alertable : 0y0

It's a kernel mode (!= user mode) wait because the thread waitmode is 0:

kd> dt _kthread ffffe001ef7a5400 waitmode
ntdll!_KTHREAD
   +0x187 WaitMode : 0 ''

The thread WaitBlockList is a structure of type _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)

if you look at the above _KWAIT_BLOCK, you can see that there's an Object field which indicates the object on which the thread is waiting.

We know that's an event, but all dispatchable objects have a dispatch header, so we can dt the Object pointer with the help of the nt!_DISPATCHER_HEADER structure.

KEVENTs can exist as standalone data structures initialized by KeInitializeEvent() or as kernel (event) objects created with NtCreateEvent(): If the event is initialized with KeInitializeEvent() then it's not a kernel object, while if initialized with NtCreateEvent(), then it is a kernel object.

A nt!_KEVENT structure is just a wrapper around a nt!_DISPATCHER_HEADER structure.

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 ]

Just given a kernel event you can know which threads are waiting for the event to become in a signaled state (event being set), but the big problem is you can't know who is supposed to set this particular event.

In your case only three functions will set the event in the serial driver: serial!SerialSetPendingDpcEvent, serial!SerialDpcEpilogue and serial!SerialInsertQueueDpc. Getting to these functions is definitely another problem...

2
votes

CloseHandle() will only finish once all pending IO for the handle is complete. According to the thread information you posted, there's at least one pending IRP for this thread - I'd have a look to see if it's for the same object you're attempting to close.

FWIW, I/O Cancellation is not always supported - does your serial device support cancellation?

2
votes

It is supossed that the kernel, running in ring 0, cannot be altered from a process running in ring 3. Windows relays in hardware support from the microprocessor to implement both memory and I/O isolation so no process can access memory neither in the kernel space, nor in other user's memory space.

The only way to access kernel is by means of a system call. System calls in Windows are in the form of an API known as "native API". An example is NtCreateFile, which is the function called in behalf of a call to CreateFile function. NtCreateFile must check all arguments for validity. This very same function is accesible from the kernel itself as ZwCreateFile. When called from the kernel, it does no checks, as kernel trusts any piece of code that runs in kernel mode.