Allocating Physical Memory
It's possible to allocate a portion of memory so you can use it in a driver or the kernel. There are two ways to do this:
¦ Dynamically, by calling the AllocPhysMem function AllocPhysMem allocates contiguous physical memory in one or more pages that you can map to virtual memory in the user space by calling MmMapIoSpace or OALPAtoVA, depending on whether the code is running in user mode or kernel mode. Because physical memory is allocated in units of memory pages, it is not possible to allocate less than a page of physical memory. The size of the memory page depends on the hardware platform. A typical page size is 64 KB.
¦ Statically, by creating a RESERVED section in the Config.bib file You can statically reserve physical memory by using the MEMORY section of a run-time image's BIB file, such as Config.bib in the BSP folder. Figure 6-9 illustrates this approach. The names of the memory regions are for informational purposes and are only used to identify the different memory areas defined on the system. The important pieces of information are the address definitions and the RESERVED keyword. According to these settings, Windows Embedded CE excludes the reserved regions from system memory so that they can be used for DMA by peripherals and data transfers. There is no risk of access conflicts because the system does not use reserved memory areas.
Figure 6-9 Definition of reserved memory regions in a Config.bib file
Application Caller Buffers
In Windows Embedded CE 6.0, applications and device drivers run in different process spaces. For example, Device Manager loads stream drivers into the kernel process (Nk.exe) or into the user-mode driver host process (Udevice.exe), whereas each application runs in its own individual process space. Because pointers to virtual memory addresses in one process space are invalid in other process spaces, you must map or marshal pointer parameters if separate processes are supposed to access the same buffer region in physical memory for communication and data transfer across process boundaries.
Using Pointer Parameters
A pointer parameter is a pointer that a caller can pass as a parameter to a function. The DeviceIoControl parameters lpInBuf and lpOutBuf are perfect examples. Applications can use DeviceIoControl to perform direct input and output operations. A pointer to an input buffer (lpInBuf) and a pointer to an output buffer (lpOutBuf) enable data transfer between the application and the driver. DeviceIoControl is declared in Winbase.h as follows:
WINBASEAPI BOOL WINAPI DeviceIoControl (HANDLE hDevice,
DWORD dwIoControlCode,
__inout_bcount_opt(nInBufSize)LPVOID lpInBuf,
DWORD nInBufSize,
__inout_bcount_opt(nOutBufSize) LPVOID lpOutBuf,
DWORD nOutBufSize,
__out_opt LPDWORD lpBytesReturned,
__reserved LPOVERLAPPED lpOverlapped);
Pointer parameters are convenient to use in Windows Embedded CE 6.0 because the kernel automatically performs full access checks and marshaling on these parameters. In the DeviceIoControl declaration above, you can see that the buffer parameters lpInBuf and lpOutBuf are defined as in/out parameters of a specified size, while lpBytesReturned is an out-only parameter. Based on these declarations, the kernel can ensure that an application does not pass in an address to read-only memory (such as a shared heap, which is read-only to user-mode processes, but writable to the kernel) as an in/out or out-only buffer pointer or it will trigger an exception. In this way, Windows Embedded CE 6.0 ensures that an application cannot gain elevated access permissions to a memory region through a kernel-mode driver. Accordingly, on the driver's side, you do not have to perform any access checks for the pointers passed in through the XXX_IOControl stream interface function (pBufIn and pBufOut).
Using Embedded Pointers
Embedded pointers are pointers that a caller passes to a function indirectly through a memory buffer. For example, an application can store a pointer inside the input buffer passed in to DeviceIoControl through the parameter pointer lpInBuf. The kernel will automatically check and marshal the parameter pointer lpInBuf, yet the system has no way to identify the embedded pointer inside the input buffer. As far as the kernel is concerned, the memory buffer simply contains binary data. Windows Embedded CE 6.0 provides no mechanisms to specify explicitly that this block of memory contains pointers.
Because embedded pointers bypass the kernel's access checks and marshaling helpers, you must perform access checks and marshaling of embedded pointers in device drivers manually before you can use them. Otherwise, you might create vulnerabilities that malicious user-mode code can exploit to perform illegal actions and compromise the entire system. Kernel-mode drivers enjoy a high level of privileges and can access system memory that user-mode code should not be able to access.
To verify that the caller process has the required access privileges, marshal the pointer, and access the buffer, you should call the CeOpenCallerBuffer function. CeOpenCallerBuffer checks access privileges based on whether the caller is running in kernel-mode or user-mode, allocates a new virtual address for the physical memory of the caller's buffer, and optionally allocates a temporary heap buffer to create a copy of the caller's buffer. Because the mapping of the physical memory involves allocating a new virtual address range inside the driver, do not forget to call CeCloseCallerBuffer when the driver has finished its processing.
Handling Buffers
Having performed implicit (parameter pointers) or explicit (embedded pointers) access checks and pointer marshaling, the device driver is ready to access the buffer. However, access to the buffer is not exclusive. While the device driver reads data from and writes data to the buffer, the caller might also read and write data concurrently, as illustrated in Figure 6-10. Security issues can arise if a device driver stores marshaled pointers in the caller's buffer. A second thread in the application could then manipulate the pointer to access a protected memory region through the driver. For this reason, drivers should always make secure copies of the pointers and buffer size values they receive from a caller and copy embedded pointers to local variables to prevent asynchronous modification.
Figure 6-10 Manipulating a marshaled pointer in a shared buffer