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