Monday, August 4, 2014

User space access on Windows, Mac OS X and Linux.

I made this notes just for a record to have all in one place.

Windows

All access to user mode space must be done by code protected by SEH, for more information look at MSDN's  "Structured Exception Handling" .

__try
{
   AccessUserModeSpace();
}
__except( ExceptionFilter() )
{
    ExceptionHandler();
}

Below is a discussion of SEH internal implementation in a compiler and kernel.

Windows 32 bit

A plethora of information is available online, e.g. "A Crash Course on the Depths of Win32 Structured Exception Handling"  , the basic idea is placing SEH frame registrations with filter and handler addresses on a stack and linking them in a list with a head at fs:PcExceptionList where fs is an IA 32 register containing an address for per-thread information, i.e. it is reloaded on each thread switching, so when an exception on memory access happens the kernel exception handler calls RtlDispatchException that knows where to find a list of registered exception filters and handlers.

Windows 64 bit

When a compiler meets __try() __except() construction it adds an entry in the exception table that contains start and end address for a protected region and offsets for filter and handler, this is similar to the method used on Linux. The details can be found here "Exceptional Behavior - x64 Structured Exception Handling"  below I describe what is not mentioned in this article.

 The linker adds an entry for every function, this entry contains an information for RtlVirtualUnwind that is called by RtlDispatchException , this allows to call any function inside a __try() block and the kernel still able to find a way up the call stack to a place where an exception filter and handler are registered, while in case of 32 bit kernel this is done by looking at fs:PcExceptionList  but the kernel doesn't have such luxury in 64 bit mode.

You can look at these entries with .fnent command.

For example an entry for memcpy that is  used to copy memory to/from user space, this function does not register any exception handlers but the kernel must be able to unwind a call stack to find it, so only unwinding information presents

 3: kd> .fnent nt!memcpy
Debugger function entry 00000000`003da6e8 for:
(fffff800`9046b340)   nt!memcpy   |  (fffff800`904d7c50)   nt! ?? ::FNODOBFM::`string'
Exact matches:
    nt!memcpy (<no parameter info>)
    nt!memmove (<no parameter info>)

BeginAddress      = 00000000`00055340
EndAddress        = 00000000`00055679
UnwindInfoAddress = 00000000`00201380

Unwind info at fffff800`90617380, 4 bytes
  version 1, flags 0, prolog 0, codes 0


Now look at the bigger function MmLockAndCopyMemory , as you see an unwinding information is pretty extensive and still no exception filter and handler

2: kd> .fnent nt!MmLockAndCopyMemory
Debugger function entry 00000000`003da6e8 for:
(fffff800`9098f920)   nt!MmLockAndCopyMemory   |  (fffff800`9098fb30)   nt!MmStoreRegister
Exact matches:
    nt!MmLockAndCopyMemory (<no parameter info>)

BeginAddress      = 00000000`00579920
EndAddress        = 00000000`00579b30
UnwindInfoAddress = 00000000`0025406c

Unwind info at fffff800`9066a06c, 20 bytes
  version 2, flags 0, prolog 1c, codes e
  00: offs a, unwind op 6, op info 0 UWOP_EPILOG Length: a. Flags: 0
  01: offs 10, unwind op 6, op info 0 UWOP_EPILOG Offset from end: 10 (FFFFF8009098FB20)
  02: offs 1c, unwind op 4, op info 6 UWOP_SAVE_NONVOL FrameOffset: 70 reg: rsi.
  04: offs 1c, unwind op 4, op info 5 UWOP_SAVE_NONVOL FrameOffset: 68 reg: rbp.
  06: offs 1c, unwind op 4, op info 3 UWOP_SAVE_NONVOL FrameOffset: 60 reg: rbx.
  08: offs 1c, unwind op 2, op info 5 UWOP_ALLOC_SMALL.
  09: offs 18, unwind op 0, op info f UWOP_PUSH_NONVOL reg: r15.
  0a: offs 16, unwind op 0, op info e UWOP_PUSH_NONVOL reg: r14.
  0b: offs 14, unwind op 0, op info d UWOP_PUSH_NONVOL reg: r13.
  0c: offs 12, unwind op 0, op info c UWOP_PUSH_NONVOL reg: r12.
  0d: offs 10, unwind op 0, op info 7 UWOP_PUSH_NONVOL reg: rdi.

Now look at a function that definetly contains a code that accesses user space, e.g. NtReadFile , now you can see _C_specific_handler as a handler routine that takes care of calling filter and handler

3: kd> .fnent nt!NtReadFile
Debugger function entry 00000000`003da6e8 for:
(fffff800`908b6690)   nt!NtReadFile   |  (fffff800`908b6f50)   nt!FsRtlCancellableWaitForMultipleObjects
Exact matches:
    nt!NtReadFile (<no parameter info>)

BeginAddress      = 00000000`004a0690
EndAddress        = 00000000`004a0f50
UnwindInfoAddress = 00000000`00242114

Unwind info at fffff800`90658114, 24 bytes
  version 2, flags 1, prolog 21, codes b
  handler routine: nt!_C_specific_handler (fffff800`905039a0), data 4
  00: offs c, unwind op 6, op info 0 UWOP_EPILOG Length: c. Flags: 0
  01: offs b6, unwind op 6, op info 3 UWOP_EPILOG Offset from end: 3b6 (FFFFF800908B6B9A)
  02: offs 21, unwind op 1, op info 0 UWOP_ALLOC_LARGE FrameOffset: c0.
  04: offs 1a, unwind op 0, op info f UWOP_PUSH_NONVOL reg: r15.
  05: offs 18, unwind op 0, op info e UWOP_PUSH_NONVOL reg: r14.
  06: offs 16, unwind op 0, op info d UWOP_PUSH_NONVOL reg: r13.
  07: offs 14, unwind op 0, op info c UWOP_PUSH_NONVOL reg: r12.
  08: offs 12, unwind op 0, op info 7 UWOP_PUSH_NONVOL reg: rdi.
  09: offs 11, unwind op 0, op info 6 UWOP_PUSH_NONVOL reg: rsi.
  0a: offs 10, unwind op 0, op info 3 UWOP_PUSH_NONVOL reg: rbx.


To be continued (Mac OS X and Linux)...