Kernel Programming: Nt and Zw Versions of the Native System Services Routines
The Windows native operating system services API is implemented as a set of routines that run in kernel mode. These routines have names that begin with the prefix Nt or Zw. Kernel-mode drivers can call these routines directly. User-mode applications can access these routines through system calls.
With a few exceptions, each native system services routine has two slightly different versions that have similar names but different prefixes. For example, calls to NtCreateFile and ZwCreateFile perform similar operations and are, in fact, serviced by the same kernel-mode system routine. For system calls from user mode, the Nt and Zw versions of a routine behave identically. For calls from a kernel-mode driver, the Nt and Zw versions of a routine differ in the way that they handle the parameter values that the caller passes to the routine.
A kernel-mode driver calls the Zw version of a native system services routine to inform the routine that the parameters come from a trusted, kernel-mode source. In this case, the routine assumes that it can safely use the parameters without first validating them. However, if the parameters might be from either a user-mode source or a kernel-mode source, the driver instead calls the Nt version of the routine. The Nt version determines, based on the history of the calling thread, whether the parameters originated in user mode or kernel mode. (How does the Nt routine distinguish user-mode parameters from kernel-mode parameters? See Previous Mode, below.)
When a user-mode application calls the Nt or Zw version of a native system services routine, the routine always treats the parameters that it receives as values that come from an untrusted, user-mode source. The routine thoroughly validates the parameter values before it uses the parameters. In particular, the routine probes any caller-supplied buffers to verify that the buffers reside in valid user-mode memory and are properly aligned.
Native system services routines make additional assumptions about the parameters that they receive. If a routine receives a pointer to a buffer that was allocated by a kernel-mode driver, the routine assumes that the buffer was allocated in system memory, not in user-mode memory. If the routine receives a handle that was opened by a user-mode application, the routine looks for the handle in the user-mode handle table, not in the kernel-mode handle table.
PreviousMode
When a user-mode application calls the Nt or Zw version of a native system services routine, the system call mechanism traps the calling thread to kernel mode. To indicate that the parameter values originated in user mode, the trap handler for the system call sets the PreviousMode field in the thread object of the caller to UserMode. The native system services routine checks the PreviousMode field of the calling thread to determine whether the parameters are from a user-mode source.
If a kernel-mode driver calls a native system services routine and passes parameter values to the routine that are from a kernel-mode source, the driver must ensure that the PreviousMode field in the current thread object is set to KernelMode.
A kernel-mode driver can run in the context of an arbitrary thread, and the PreviousMode field of this thread might be set to UserMode. In this situation, a kernel-mode driver can call the Zw version of a native system services routine to inform the routine that the parameter values are from a trusted, kernel-mode source. The Zw call goes to a thin wrapper function that overrides the PreviousMode value in the current thread object. The wrapper function sets PreviousMode to KernelMode and calls the Nt version of the routine. On return from the Nt version of the routine, the wrapper function restores the original PreviousMode value of the thread object and returns.
A kernel-mode driver can directly call the Nt version of a native system services routine. When a kernel-mode driver processes an I/O request that might have originated either in user mode or in kernel mode, the driver can call the Nt version of the routine to ensure that the PreviousMode value of the current thread remains unaltered during the call. That way, the routine can determine whether the parameter values are from a user-mode application or a kernel-mode component, and treat them accordingly.
An error can occur if a kernel-mode driver calls an NtXxx routine and the PreviousMode value in the current thread object does not accurately indicate whether the parameter values are from a user-mode or a kernel-mode source.
For example, assume that a kernel-mode driver is running in the context of an arbitrary thread, and that the PreviousMode value for this thread is set to UserMode. If this driver passes a kernel-mode file handle to the NtClose routine, this routine attempts to close the user-mode handle of the arbitrary thread instead of the kernel-mode handle. When NtClose fails to find the handle in the user-mode handle table, it returns the STATUS_INVALID_HANDLE error code. Meanwhile, the driver leaks the kernel-mode handle, which was never closed.
For another example, if the parameters for an NtXxx routine include an input or output buffer, and if PreviousMode is UserMode, the routine calls the ProbeForRead or ProbeForWrite routine to validate the buffer. If the buffer was allocated in system memory instead of in user-mode memory, the ProbeForXxx routine raises an exception, and the NtXxx routine returns the STATUS_ACCESS_VIOLATION error code.
If necessary, a driver can call the ExGetPreviousMode routine to get the PreviousMode value from the current thread object. Alternatively, the driver can read the RequestorMode field from the IRP structure that describes the requested I/O operation. The RequestorMode field contains a copy of the PreviousMode value from the thread that requested the operation.
What Does the Zw Prefix Mean?
The Windows native system services routines have names that begin with the prefixes Nt and Zw. The Nt prefix is an abbreviation of Windows NT, but the Zw prefix has no meaning. Zw was chosen partly to avoid potential naming conflicts with other APIs, and partly to avoid using any potentially useful two-letter prefixes that might be needed for future APIs.
Today, the ZwXxx routines are more visible to drivers and to applications than the kernel architects might have anticipated in the early days of Windows NT development. In retrospect, perhaps a more meaningful prefix could have been chosen.
– Jerry Van Aken [MSFT], WDK Programming Writer