|
|
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 ); }
|