Mapping Pointers and Sharing Memory between Processes: Windows CE 5.0 vs. Windows Embedded CE 6.0
1/6/2010
In Windows CE 5.0, the kernel performed a trivial 1-byte access check on pointer parameters. In Windows Embedded CE 6.0, the kernel performs a full check on buffer parameters.
Pointer Terminology
The following defines some terms related to passing pointers:
- Access checking: Verifying that the caller process has sufficient privileges to access a buffer.
- Marshaling: Preparing a pointer that a server can use to access a caller’s buffer.
- Secure-copy: Making a copy of a buffer to prevent asynchronous modification by the caller.
- Pointer parameter: Pointer that is passed as a parameter to an API.
- Embedded pointer: Pointer that is passed to an API by storing it inside a buffer or inside of another embedded pointer.
Previous Versions of the OS
When an application called a function in Windows CE 5.0, the kernel performed a 1-byte access check on pointer parameters and marshaled the pointer by mapping it. The OS address space was arranged so that the 32-MB virtual-memory address space for every process was accessible at all times. Therefore, mapping required only modifying the pointer to point to the right memory space of the target process. The server process was responsible for calling a function such as MapCallerPtr to verify that the caller process had sufficient privileges to access the entire buffer, and the server process was responsible for access-checking and marshaling any embedded pointers. Generally, both the access check and marshaling were covered by calling MapCallerPtr. The server was also responsible for performing a secure copy, if necessary.
Current Version of the OS
In Windows Embedded CE 6.0, when an application calls a function, the kernel performs the full access check on buffer parameters, taking responsibility for that protection away from the server processes. This is made possible by giving the kernel more information about parameter sizes. The kernel still marshals pointer parameters, and servers must still marshal embedded pointers. Servers must still perform a secure copy, if necessary. The following list shows what has changed:
- The kernel has taken over full access checking of pointer parameters.
- The function servers must call to marshal parameters.
- Options available for servers to perform a secure copy.
- The way marshaling must be handled if the server needs to access a buffer asynchronously.
Marshaling Mechanics
Now there are two possible ways that a buffer can be marshaled so that the server can use it:
- Duplication: Allocate a new buffer for the server to use, and copy data into and out of the buffer as necessary. In-only buffers are copied only from the caller to the server. Out-only buffers are copied only from the server to the caller. In/out buffers are copied in both directions. The server owns the buffer for the duration of the call. Duplication not only gives the server access to the buffer, but also prevents the caller from asynchronously modifying the buffer after it has been validated by the server.
- Aliasing: Map part of the memory of the caller process into the memory space of the server process. This is a different kind of mapping than was done on previous versions of the OS. Because every process has a separate address space, it is not possible to access another process's memory by using a pointer. Instead, the kernel aliases part of the memory of the caller into the address space of the server, using the VirtualCopyEx function. The same piece of physical memory is temporarily shared between the two processes by mapping it to two different virtual address ranges inside the two processes. This form of mapping involves allocating a new virtual address range, instead of just re-basing a pointer, and requires the address range to be freed when it is no longer needed.
The automatic marshalling of pointer parameters, and the helper functions CeOpenCallerBuffer with CeCloseCallerBuffer, and CeAllocAsynchronousBuffer with CeFreeAsynchronousBuffer, choose between duplication and aliasing, based on system security and performance needs. If the buffer is small enough, the kernel might duplicate the buffer. Copying can affect performance, so if the buffer is too large to duplicate, the kernel might alias the buffer instead.
The helper function CeAllocDuplicateBuffer with CeFreeDuplicateBuffer is provided to make a duplicate copy of a marshaled buffer only if it has not already been duplicated by marshaling. You can use other means of creating a secure copy if you want. CeAllocDuplicateBuffer and CeFreeDuplicateBuffer are provided only for convenience. You can also use stack or heap buffers with memcpy to achieve the same effect.
Kernel-mode servers have only two pointer marshaling cases:
Synchronous access: Pointer parameters and embedded pointers can be used without any marshaling. However, for embedded pointers, the server must call the CeOpenCallerBuffer helper to perform access checking and marshaling.
Asynchronous access: For asynchronous access to a pointer, a kernel-mode server must call the marshaling helper function CeAllocAsynchronousBuffer to duplicate or alias the buffer for access when the caller’s address space is unavailable. Use CeAllocAsynchronousBuffer with function parameters or with embedded pointers that were opened with CeOpenCallerBuffer.
Note
Treat your code as though it was operating in user mode by passing parameters asynchronously as scalars as shown below for user-mode servers so that your code will work in both kernel and user mode.
User-mode servers have more pointer marshaling cases:
- Synchronous access: The kernel always marshals pointer parameters automatically. The server does not need to do anything extra. For embedded pointers, user-mode servers must call the helper function CeOpenCallerBuffer to perform the marshalling.
- Pointer parameters, used asynchronously: For asynchronous access to a pointer, a user-mode server must use the marshaling helpers, not automatic marshaling of pointer parameters. This means that any parameter that may be used asynchronously must be passed as a scalar value (DWORD) instead of a pointer (I_PTR, O_PTR, IO_PTR). The automatic access checking and marshaling does not occur, and the server must call CeOpenCallerBuffer and then CeAllocAsynchronousBuffer to perform those tasks. It is not possible to call CeAllocAsynchronousBuffer on a pointer parameter in user mode code. You cannot perform an asynchronous write-back of O_PTR or IO_PTR values.
- Embedded pointers, used asynchronously: After calling CeOpenCallerBuffer, pass the buffer to CeAllocAsynchronousBuffer.
Pointer Access Quick-Reference
The following table sums up what servers must do to ensure that access checking, marshaling, and secure copying are done properly.
Use case | Work required | What the implementation must do |
---|---|---|
Parameter: used synchronously |
Access checking |
Nothing: kernel checks during trap. |
Parameter: used synchronously |
Marshaling |
None, kernel marshals during trap. |
Parameter: used synchronously |
Secure copying |
If necessary, make a copy yourself or use CeAllocDuplicateBuffer and CeFreeDuplicateBuffer. |
Parameter: used asynchronously |
Prepare for asynchronous access, which is not possible in user mode. |
CeAllocAsynchronousBuffer with CeFreeAsynchronousBuffer. |
Embedded pointer: used synchronously |
Access checking |
All is taken care of by CeOpenCallerBuffer with CeCloseCallerBuffer. |
Embedded pointer: used synchronously |
Marshalling |
All is taken care of by CeOpenCallerBuffer with CeCloseCallerBuffer. |
Embedded pointer: used synchronously |
Secure-copy |
All is taken care of by CeOpenCallerBuffer with CeCloseCallerBuffer. |
Embedded pointer: used asynchronously |
Prepare for asynchronous access |
Call CeAllocAsynchronousBuffer after you call CeOpenCallerBuffer. You must call CeFreeAsynchronousBuffer before you call CeCloseCallerBuffer. |
Other methods of sharing memory between processes include:
- ReadProcessMemory with WriteProcessMemory. Pair the pointer with a process identifier, and pass them around together.
- Sharing a named memory-mapped file. Both processes must open their own views.
- Shared heaps, which are writable to the kernel, but read-only to everyone else.
- The Windows Embedded CE 6.0 CeRemoteHeapCreate and the CeRemoteHeapTranslatePointer functions enable you to create a heap that is shared only between a kernel- or user-mode server, and a client. The server can allocate, free, read, and write, whereas the client can read and write, but cannot corrupt heap metadata.
- DLL-shared data section.
- Point-to-point message queues. For more information, see Message Queue Point-to-Point Reference.
- Use the VirtualAllocEx, the VirtualCopyEx, and the VirtualAllocCopyEx functions to alias memory between processes.
Mapped Files
You can no longer access a mapped file without creating a view. Before CE 6.0, you could pass a view address between processes, and the other process could access the view. You cannot use the virtual memory functions for mapping view addresses. Before CE 6.0, you could commit and decommit mapped views with the virtual memory functions.
See Also
Other Resources
Kernel Functionality Modifications: Windows CE 5.0 vs. Windows Embedded CE 6.0
Kernel API Modifications: Windows CE 5.0 vs. Windows Embedded CE 6.0