Never use pointers in the caller's buffer after they have been marshaled, and do not use the caller's buffer to store marshaled pointers or other variables required for driver processing. For example, copy buffer size values to local variables so that callers cannot manipulate these values to cause buffer overruns. One way to prevent asynchronous modification of a buffer by the caller is to call CeOpenCallerBuffer with the ForceDuplicate parameter set to TRUE to copy the data from the caller's buffer to a temporary heap buffer.
Synchronous Access
Synchronous memory access is synonymous with non-concurrent buffer access. The caller's thread waits until the function call returns, such as DeviceIoControl, and there are no other threads in the caller process that access the buffer while the driver performs its processing tasks. In this scenario, the device driver can use parameter pointers and embedded pointers (after a call to CeOpenCallerBuffer) without additional precautions.
The following is an example of accessing a buffer from an application synchronously. This sample source code is an excerpt from an XXX_IOControl function of a stream driver:
BOOL SMP_IOControl(DWORD hOpenContext, DWORD dwCode,
PBYTE pBufIn, DWORD dwLenIn, PBYTE pBufOut, DWORD dwLenOut,
PDWORD pdwActualOut) {
BYTE *lpBuff = NULL;
...
if (dwCode == IOCTL_A_WRITE_FUNCTION) {
// Check parameters
if ( pBufIn == NULL || dwLenIn != sizeof(AN_INPUT_STRUCTURE)) {
DEBUGMSG(ZONE_IOCTL, (TEXT('Bad parameters
')));
return FALSE;
}
// Access input buffer
hrMemAccessVal = CeOpenCallerBuffer((PVOID) &lpBuff,
(PVOID) pBufIn, dwLenIn, ARG_I_PTR, FALSE);
// Check hrMemAccessVal value
// Access the pBufIn through lpBuff
...
// Close the buffer when it is no longer needed
CeCloseCallerBuffer((PVOID)lpBuff, (PVOID)pBufOut,
dwLenOut, ARG_I_PTR);
}
...
}
Asynchronous Access
Asynchronous buffer access assumes that multiple caller and driver threads access the buffer sequentially or concurrently. Both scenarios present challenges. In the sequential access scenario, the caller thread might exit before the driver thread has finished its processing. By calling the marshaling helper function CeAllocAsynchronousBuffer, you must re-marshal the buffer after it was marshaled by CeOpenCallerBuffer to ensure in the driver that the buffer remains available even if the caller's address space is unavailable. Do not forget to call CeFreeAsynchronousBuffer after the driver has finished its processing.
To ensure that your device driver works in kernel and user mode, use the following approach to support asynchronous buffer access:
¦ Pointer parameters Pass pointer parameters as scalar DWORD values and then call CeOpenCallerBuffer and CeAllocAsynchronousBuffer to perform access checks and marshaling. Note that you cannot call CeAllocAsynchronousBuffer on a pointer parameter in user-mode code or perform asynchronous write-back of O_PTR or IO_PTR values.
¦ Embedded pointers Pass embedded pointers to CeOpenCallerBuffer and CeAllocAsynchronousBuffer to perform access checks and marshaling.
To address the second scenario of concurrent access, you must create a secure copy of the buffer after marshaling, as mentioned earlier. Calling CeOpenCallerBuffer with the ForceDuplicate parameter set to TRUE and CeCloseCallerBuffer is one option. Another is to call CeAllocDuplicateBuffer and CeFreeDuplicateBuffer for buffers referenced by parameter pointers. You can also copy a pointer or buffer into a stack variable or allocate heap memory by using VirtualAlloc and then use memcpy to copy the caller's buffer. Keep in mind that if you do not create a secure copy, you're leaving in a vulnerability that a malicious application could use to take control of the system.
Exception Handling
Another important aspect that should not be ignored in asynchronous buffer access scenarios revolves around the possibility that embedded pointers might not point to valid memory addresses. For example, an application can pass a pointer to a driver that refers to an unallocated or reserved memory region, or it could asynchronously free the buffer. To ensure a reliable system and prevent memory leaks, you should enclose buffer- access code in a __try frame and any cleanup code to free memory allocations in a __finally block or an exception handler. For more information about exception handling, see Chapter 3, 'Performing System Programming.'
Lesson Summary
Windows Embedded CE 6.0 facilitates inter-process communication between applications and device drivers through kernel features and marshaling helper functions that hide most of the complexities from driver developers. For parameter pointers, the kernel performs all checks and pointer marshaling automatically. Only embedded pointers require extra care because the kernel cannot evaluate the content of application buffers passed to a driver. Validating and marshaling an embedded pointer in a synchronous access scenario involves a straightforward call to CeOpenCallerBuffer. Asynchronous access scenarios, however, require an additional call to CeAllocAsynchronousBuffer to marshal the pointer one more time. To ensure that your driver does not introduce system vulnerabilities, make sure you handle buffers correctly, create a secure copy of the buffer content so that callers cannot manipulate the values, and do not use pointers or buffer size values in the caller's buffer after they have been marshaled. Never store marshaled pointers or other variables required for driver processing in the caller's buffer.