Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

445 lines
14 KiB

We have a problem
GDI has loaded a TrueType font of unknown origin and now it is
time to put some glyphs on the screen. What do you do?
In TrueType each glyph is defined by a program. The glyph bits are
generated by running that program to construct an outline and then
filling the resulting path. If you think that this sounds potentially
dangerous, you would be right. It is ot hard to produce a font that can
produce an exception during the rasterization process. Unfortunately,
in the kernel, _try _excepts cannot save you in all cases. For example,
in the kernel access of an invalid kernel mode address is not handled
gracefully by _try _except; the result of accessing a bad kernel address
is a blue screen.
Sometimes GRE, or one of its components in the kernel need to call
back to user space to perform dangerous task.
Where do we draw the line between Kerneal and User?
What is the interface to the user mode of the font driver? I will examine
several options.
(1) The Current DDI defines the interface between Kernel and User Mode.
In this case we would place the entire font driver in user space
and we would make calls to the font driver using the existing DDI
interface.
Pro: Very safe. All foreign code is removed from kernel.
Con: Less efficient. This would require that all calls to the font
driver would cost a context switch.
Con: Callbacks are part of the DDI. For example, the font drivers
return a glyph outline by calling PATHOBJ functions to form a
path. If we were to retain this query method, then we would be
forced to reproduce the PATHOBJ fucntionality in user space [1].
(2) Modify the current font driver interface (as defined by the DDI) so
that it is more appropriate as a User Mode interface.
(3) Driver Defined Interface between Kernel and User Mode
In this case, the font driver is split into two pieces: a kernel
part and an user part. GRE talks to the kernel portion of the font driver
using the current interface as defined in the DDI. However, GRE provides
services to let the font driver call the user mode part of its driver.
Con: Some of the font drvier remains in kernel mode and so we have not
completely solved the problem. An errant driver can still kill
the system.
Pro: This is simple. GRE is not touched other than new code added to
provide a mechanism for a font driver to call user space
functions.
_________
[1] Unfortunately the user mode driver cannot use GDI path functions
because this would invite deadlock.
The Proposed Client-Server Architecture
Nomeclature
GDI
The routines a provided by gdi32.dll. All of these routines
are in user mode.
GRE
The kernel mode portion of the graphics engine.
Server
A user mode process that responds to requests
made by client process. Most of the work done satisfying the
client request is done in user mode so as to protect other
processes.
UMS
"User Mode Server", the server process when in user mode.
KMS
"Kernel Mode Server", the server process when in kernel mode.
Client
A process, currently in kernel mode, that calls to the
server process. Sometimes I will refer to this as the
kernel client
What is so tricky?
The NT architecture complicates the client-server process because the user
mode address space of the server process is not visible to the client
process. However, the all valid kernel mode addresses are visible to both
the client and server, once they are in kernel mode.
The Strategy I.
Communication between client and server is done in kernel mode. The grunt
work is done in user mode in the address space of the server process.
The client process finds out that he needs to call the server. The
client creates a buffer in kernel memory, informs the server process where
it is and then waits for the answer. The server process copies that
message to the user memory where it is processed by the user mode portion
of the server code. The server goes back to kernel mode and then copies
the user mode result to the kernel. The client, who has been patiently
waiting in the kernel, is then informed that his package is ready. The
server waits in the kernel for the next request.
The Message
A message consists of two parts. The first part is the GDIMSG strcuture
that contains information about the data buffer. The second part of the
message is the data buffer, a contiguous block of memory. This data
buffer is usually separate from the GDIMSG.
typedef struct _GDIMSG {
void *pv; // pointer to message buffer
unsigned cj; // size of message buffer
struct {
void *pv; // pointer to `in' portion of buffer
unsigned cj; // size of `in' portion of buffer
} in;
struct {
void *pv; // pointer to `out' portion of buffer
unsigned cj; // size of `out' portion of buffer
} out;
} GDIMSG;
The Strategy II.
When the user mode server process is started, it calls GdiEnableMessage to
request a pointer to an initial message buffer of a size secified by the
server process. GRE will allocate a buffer in the user mode memory of the
user mode server (UMS), fill in the GDIMSG buffer provided by the UMS
giving the address of the requested user mode buffer. The UMS
/*****************************************************************
* We need to add a couple fields to the TEB *
* *
* typedef struct _TEB { *
* ... *
* GDI_MESSAGE *pMsg; // pointer to client *
* ... *
* } TEB; *
*****************************************************************/
/******************************Public*Routine******************************\
*
* Routine Name: vUserServerProcess
*
* Routine Description:
*
* This is the user more server process that processes
* messages from the kernel mode part of the font
* driver.
*
* Arguments: none
*
* Return Value: none
*
\**************************************************************************/
void vUserServerThread(void)
{
GDI_MESSAGE Msg;
while ( GdiGetMessage( &Msg ) == NO_ERROR )
{
ProcessMessage( &Msg );
}
}
/******************************Public*Routine******************************\
*
* Routine Name: NtGdiGetMessage
*
* Routine Description:
*
* Server mode thread routine that transports messages between
* kernel mode and user mode.
*
* This routine does two things. It returns a message from
* the user mode font driver to the kernel mode font driver
* and goes to sleep. After it is awakend by a signal from
* the kernel mode font driver, this routine sends a new
* message to the the user mode server.
*
* Arguments:
*
* pUserMsg address of an GDI_MESSAGE structure residing
* in user space.
* GRE will allocate a buffer and
* initialize it. Then it will fill the
* GDI_MESSAGE structure to tell the
* user mode font server process where the
* locations of the `in' and `out' parts of the
* message buffer.
*
* Return Value:
*
* If the routine was successful then it will return NO_ERROR.
*
\**************************************************************************/
NT_STATUS NtGdiGetMessage( GDI_MESSAGE *pUserMsg )
{
void *pv;
unsigned dp;
TEM *pteb = NtCurrentTeb();
// If the kernel memory has been allocated then there is an application
// expecting an answer. We copy the message from the message buffer
// to the kernel buffer where it becomes visible to the application
// thread that is currently asleep in the kernel mode portion of the
// font driver. When this is finished copying to the kernel buffer
// we wake the application thread and go to sleep awaiting the
// wakeup call the next application call.
// If this is the first call from the server thread,
// then there is no message buffer. In that case
// we do not unload the message to the kernel buffer and
// we do not signal the application
if ( pUserMsg->cj )
{
// An application thread is asleep in the kernel mode portion of
// the font driver. It is awaiting some data from the User side.
// We fill the kernel buffer from the a portion of the message
// buffer and then free the user buffer.
CopyMemory(
pteb->pMsg->out.pv, // destination address
pUserMsg->out.pv, // source address
pUserMsg->out.cj // size in bytes
);
// the server process is done with the user message so we
// can free up the associated memory
FreeUserBuffer( pUserMsg->pv );
// Wake up the client thread
SignalEvent( pteb->ClientEvent );
}
// Go to sleep until woken by the client thread.
WaitForEvent( pteb->ServerEvent );
// <<< APPLICATION WAKES SERVER THREAD UP HERE >>>
// OK, we're awake!
// The application has left a message in pteb->pKernelThread which
// points into a single contiguous data buffer in kernel memory.
// It is our job to copy that block of data over to user memory
// and then to fix up a user message that corresponds to that
// block of memory in user space.
// Copy the applications's message to the TEB. Remember that in this
// nomenclature, that the "message" contains pointers to data. It
// does not contain the data itself. Note that at this point, the
// pointers in the user message are wrong. They point to the
// kernel data which will not be accessable by the user process.
pteb->UserMsg = *(pteb->pMsg);
// Allocate memory in user space to receive a copy of the message
// data that now resides in kernel memory. In practice, this step
// will consist to see if we already have enough space allocated
// in a dedicated chunk of memory. If it is big enough, we use it,
// otherwise we create a look-aside chunk of memory and use it
// temprorarily.
if (!(pteb->UserMsg.pv = AllocUserBuffer( pteb->UserMsg.cj )))
{
return( ERROR_NOT_ENOUGH_MEMORY );
}
// Fix up the message pointers
//
// Now that we have allocated space for the message date in
// User mode memory, we must correct the User message so that
// the pointer's to the various parts of the buffer are correct.
// Since the user buffer is a copy of the kernel buffer, all the
// pointers are off by the same amount. This amount is equal
// to the numerical difference between the pointer to start
// of the kernel message buffer and the start of the user
// message buffer.
dp = (BYTE*) UserMsg.pv - (BYTE*) pMsg->pv;
(BYTE*) UserMsg.pv += dp; // correct the pointers
(BYTE*) UserMsg.in.pv += dp; // in the user message
(BYTE*) UserMsg.out.pv += dp; // directory
// The
// we do the copy after the pointers have been corrected.
CopyMemory(
UserMsg.in.pv, // destination
pteb->pMsg.in.pv, // source
UserMsg.in.cj // size in bytes
);
// Fill in the caller's message header
*pUserMsg = pteb->UserMsg;
return( STATUS_SUCCESS );
}
/******************************Public*Routine******************************\
*
* Routine Name: SomeRandomFontDriverKernelRoutine
*
* Routine Description:
*
* This is the routine that is called when GRE makes a call
* to the font driver. This routine runs in the `application'
* thread.
*
* Arguments: Depends upon the call
*
* Return Value: Depends upon the call
*
\**************************************************************************/
SomeRandomFontDriverKernelRoutine(FONTOBJ *pfo)
{
// cjIn and cjOut are known by this point
GDI_MESSAGE Msg;
Msg.cj = size_of_message_buffer;
if ( Msg.pv = EngAllocMem( 0, Msg.cj, 'gmtt') )
{
// fill out the message directory and the associated buffer
InitMessage( &Msg );
// send the message to the font server in user space
if ( EngSendMessage( pfo, &Msg ) == NO_ERROR )
{
ProcessMessage( &Msg ); // process the returned message
}
EngFreeMem( Msg.pv ); // free the kernel message buffer
}
}
/******************************Public*Routine******************************\
*
* Routine Name: EngSendMessage
*
* Routine Description:
*
* Engine Service provided to kernel mode font clients for sending
* and receiving messages from the server portion of the font
* driver residing in user mode. This routine runs in the
* `application' thread.
*
* Arguments:
*
* pfo pointer to a FONTOBJ. This identifies
* the font driver
* pMsg A pointer to a GDI_MESSAGE structure
* supplied by the calling application thread
* in the kernel.
*
* Return Value:
*
* NT_SUCCESS upon success.
*
\**************************************************************************/
NTSTATUS EngSendMessage( FONTOBJ *pfo, GDI_MESSAGE *pMsg )
{
NTSTATUS rc;
TEB *pteb;
if (!(pteb = GreGetServerThread(pfo)))
{
rc = ERROR_CAN_NOT_COMPLETE;
}
else
{
pteb->pMsg = pMsg;
SignalEvent( pteb->ServerEvent ); // wake up server thread
WaitForEvent( pteb->ClientEvent ); // sleep until server
// returns
rc = ERROR_SUCCESS;
}
return( rc );
}