Each kernel object is simply a memory block allocated by the kernel and is accessible only by the kernel. This memory block is a data structure whose members maintain information about the object.
Because the kernel object data structures are accessible only by the kernel, it is impossible for an application to locate these data structures in memory and directly alter their contents.
If we cannot alter these structures directly, how do our applications manipulate these kernel objects? The answer is that Windows offers a set of functions that manipulate these structures in well-defined ways. These kernel objects are always accessible via these functions. When you call a function that creates a kernel object, the function returns a handle that identifies the object. Think of this handle as an opaque value that can be used by any thread in your process. A handle is a 32-bit value in a 32-bit Windows process and a 64-bit value in a 64-bit Windows process.
To make the operating system robust, these handle values are process-relative. So if you were to pass this handle value to a thread in another process (using some form of interprocess communication), the calls that this other process would make using your process’ handle value might fail or, even worse, they will create a reference to a totally different kernel object at the same index in your process handle table.
Kernel objects are owned by the kernel, not by a process. In other words, if your process calls a function that creates a kernel object and then your process terminates, the kernel object is not necessarily destroyed. Under most circumstances, the object will be destroyed; but if another process is using the kernel object your process created, the kernel knows not to destroy the object until the other process has stopped using it.
The kernel knows how many processes are using a particular kernel object because each object contains a usage count. The usage count is one of the data members common to all kernel object types.
Kernel objects can be protected with a security descriptor. A security descriptor describes who owns the object (usually its creator), which group and users can gain access to or use the object, and which group and users are denied access to the object. Security descriptors are usually used when writing server applications.
Neglecting proper security access flags is one of the biggest mistakes that developers make. Using the correct flags will certainly make it much easier to port an application between Windows versions. However, you also need to realize that each new version of Windows brings a new set of constraints that did not exist in the previous versions. For example, in Windows Vista, you need to take care of the User Account Control (UAC) feature. By default, UAC forces applications to run in a restricted context for security safety even though the current user is part of the Administrators group.
When you first start programming for Windows, you might be confused when you try to differentiate a User object or a GDI object from a kernel object. For example, is an icon a User object or a kernel object? The easiest way to determine whether an object is a kernel object is to examine the function that creates the object. Almost all functions that create kernel objects have a parameter that allows you to specify security attribute information.
None of the functions that create User or GDI objects have a PSECURITY_ATTRIBUTES parameter. For example, take a look at the CreateIcon function:
HICON CreateIcon(HINSTANCE hinst, int nWidth, int nHeight, BYTE cPlanes, BYTE cBitsPixel, CONST BYTE *pbANDbits, CONST BYTE *pbXORbits);
When a process is initialized, the system allocates a handle table for it. This handle table is used only for kernel objects, not for User objects or GDI objects.
When a process first initializes, its handle table is empty. When a thread in the process calls a function that creates a kernel object, such as CreateFileMapping, the kernel allocates a block of memory for the object and initializes it. The kernel then scans the process’ handle table for an empty entry.
All functions that create kernel objects return process-relative handles that can be used successfully by any and all threads that are running in the same process. This handle value should actually be divided by 4 (or shifted right two bits to ignore the last two bits that are used internally by Windows) to obtain the real index into the process’ handle table that identifies where the kernel object’s information is stored.
If you call a function to create a kernel object and the call fails, the handle value returned is usually 0 (NULL), and this is why the first valid handle value is 4. The system would have to be very low on memory or encountering a security problem for this to happen. Unfortunately, a few functions return a handle value of -1 (INVALID_HANDLE_VALUE defined in WinBase.h) when they fail. For example, if CreateFile fails to open the specified file, it returns INVALID_HANDLE_VALUE instead of NULL.
Regardless of how you create a kernel object, you indicate to the system that you are done manipulating the object by calling CloseHandle:
BOOL CloseHandle(HANDLE hobject);
Usually, when you create a kernel object, you store the corresponding handle in a variable. After you call CloseHandle with this variable as a parameter, you should also reset the variable to NULL. If, by mistake, you reuse this variable to call a Win32 function, two unexpected situations might occur. Because the handle table slot referenced by the variable has been cleared, Windows receives an invalid parameter and you get an error. But another situation that is harder to debug is also possible. When you create a new kernel object, Windows looks for a free slot in the handle table. So, if new kernel objects have been constructed in your application workflows, the handle table slot referenced by the variable will certainly contain one of these new kernel objects. Thus, the call might target a kernel object of the wrong type or, even worse, a kernel object of the same type as the closed one. Your application state then becomes corrupted without any chance to recover.
Let’s say that you forget to call CloseHandle—will there be an object leak? Well, yes and no. It is possible for a process to leak resources (such as kernel objects) while the process runs. However, when the process terminates, the operating system ensures that all resources used by the process are freed—this is guaranteed. For kernel objects, the system performs the following actions:
- When your process terminates, the system automatically scans the process’ handle table.
- If the table has any valid entries (objects that you didn’t close before terminating), the system closes these object handles for you.
- If the usage count of any of these objects goes to zero, the kernel destroys the object.
Because kernel object handles are process-relative, performing these tasks is difficult. However, Microsoft had several good reasons for designing the handles to be process-relative.
- The most important reason was robustness. If kernel object handles were system wide values, one process could easily obtain the handle to an object that another process was using and wreak havoc on that process.
- Another reason for process-relative handles is security. Kernel objects are protected with security, and a process must request permission to manipulate an object before attempting to manipulate it. The creator of the object can prevent an unauthorized user from touching the object simply by denying access to it.
There are three different ways to allow processes to share kernel objects:
- Using object handler inheritance.
- Naming objects.
- Duplicating Object Handlers.
Using Object Handler Inheritance
Object handle inheritance can be used only when processes have a parent-child relationship. In this scenario, one or more kernel object handles are available to the parent process, and the parent decides to spawn a child process, giving the child access to the parent’s kernel objects. For this type of inheritance to work, the parent process must perform several steps.
First, when the parent process creates a kernel object, the parent must indicate to the system that it wants the object’s handle to be inheritable. Sometimes I hear people use the term object inheritance. However, there is no such thing as object inheritance; Windows supports object handle inheritance. In other words, it is the handles that are inheritable, not the objects themselves.
To create an inheritable handle, the parent process must allocate and initialize a SECURITY_ATTRIBUTES structure and pass the structure’s address to the specific Create function. The following code creates a Mutex object and returns an inheritable handle to it:
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE; // Make the returned handle inheritable.
HANDLE hMutex = CreateMutex(&sa, FALSE, NULL);
The next step to perform when using object handle inheritance is for the parent process to spawn the child process. This is done using the CreateProcess function:
BOOL CreateProcess(
PCTSTR pszApplicationName,
PTSTR pszCommandLine,
PSECURITY_ATTRIBUTES psaProcess,
PSECURITY_ATTRIBUTES psaThread,
BOOL bInheritHandles,
DWORD dwCreationFlags,
PVOID pvEnvironment,
PCTSTR pszCurrentDirectory,
LPSTARTUPINFO pStartupInfo,
PPROCESS_INFORMATION pProcessInformation);
Usually, when you spawn a process, you pass FALSE for this parameter. This value tells the system that you do not want the child process to inherit the inheritable handles that are in the parent process’ handle table. If you pass TRUE for this parameter, however, the child inherits the parent’s inheritable handle values.
The content of kernel objects is stored in the kernel address space that is shared by all processes running on the system. For 32-bit systems, this is in memory between the following memory addresses: 0x80000000 and 0xFFFFFFFF. For 64-bit systems, this is in memory between the following memory addresses: 0x00000400’00000000 and 0xFFFFFFF’FFFFFFFF.
Be aware that object handle inheritance applies only at the time the child process is spawned. If the parent process were to create any new kernel objects with inheritable handles, an already-running child process would not inherit these new handles.
Object handle inheritance has one very strange characteristic: when you use it, the child has no idea that it has inherited any handles. Kernel object handle inheritance is useful only when the child process documents the fact that it expects to be given access to a kernel object when spawned from another process. Usually, the parent and child applications are written by the same company; however, a different company can write the child application if that company documents what the child application expects.
By far, the most common way for a child process to determine the handle value of the kernel object that it’s expecting is to have the handle value passed as a command-line argument to the child process. The child process’ initialization code parses the command line (usually by calling _stscanf_s) and extracts the handle value. Once the child has the handle value, it has the same access to the object as its parent. Note that the only reason handle inheritance works is because the handle value of the shared kernel object is identical in both the parent process and the child process. This is why the parent process is able to pass the handle value as a command-line argument.
Another technique is for the parent process to add an environment variable to its environment block. The variable’s name would be something that the child process knows to look for, and the variable’s value would be the handle value of the kernel object to be inherited. Then when the parent spawns the child process, the child process inherits the parent’s environment variables and can easily call GetEnvironmentVariable to obtain the inherited object’s handle value. This approach is excellent if the child process is going to spawn another child process, because the environment variables can be inherited again.
For the sake of completeness, I’ll also mention the GetHandleInformation function:
BOOL GetHandleInformation(HANDLE hObject, PDWORD pdwFlags);
This function returns the current flag settings for the specified handle in the DWORD pointed to by pdwFlags. To see if a handle is inheritable, do the following:
DWORD dwFlags; GetHandleInformation(hObj, &dwFlags); BOOL fHandleIsInheritable = (0 != (dwFlags & HANDLE_FLAG_INHERIT));
Naming Objects
Most of Kernel Object functions have a common last parameter, pszName. When you pass NULL for this parameter, you are indicating to the system that you want to create an unnamed (anonymous) kernel object. When you create an unnamed object, you can share the object across processes by using either inheritance or DuplicateHandle. To share an object by name, you must give the object a name.
An alternative method exists for sharing objects by name. Instead of calling a Create* function, a process can call one of the Open* function. As shown in the function below:
HANDLE OpenMutex( DWORD dwDesiredAccess, BOOL bInheritHandle, PCTSTR pszName);
The last parameter, pszName, indicates the name of a kernel object. You cannot pass NULL for this parameter; you must pass the address of a zero-terminated string. These functions search the single namespace of kernel objects attempting to find a match. If no kernel object with the specified name exists, the functions return NULL and GetLastError returns 2 (ERROR_FILE_NOT_FOUND). However, if a kernel object with the specified name does exist, but it has a different type, the functions return NULL and GetLastError returns 6 (ERROR_INVALID_HANDLE). And if it is the same type of object, the system then checks to see whether the requested access (via the dwDesiredAccess parameter) is allowed. If it is, the calling process’ handle table is updated and the object’s usage count is incremented. The returned handle will be inheritable if you pass TRUE for the bInheritHandle parameter.
The main difference between calling a Create* function versus calling an Open* function is that if the object doesn’t already exist, the Create* function will create it, whereas the Open* function will simply fail.
Named objects are commonly used to prevent multiple instances of an application from running. To do this, simply call a Create* function in your _tmain or _tWinMain function to create a named object. (It doesn’t matter what type of object you create.) When the Create* function returns, call GetLastError. If GetLastError returns ERROR_ALREADY_EXISTS, another instance of your application is running and the new instance can exit. Here’s some code that illustrates this:
int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE, PTSTR pszCmdLine,
int nCmdShow) {
HANDLE h = CreateMutex(NULL, FALSE,
TEXT("{FA531CC1-0497-11d3-A180-00105A276C3E}"));
if (GetLastError() == ERROR_ALREADY_EXISTS) {
// There is already an instance of this application running.
// Close the object and immediately return.
CloseHandle(h);
return(0);
}
// This is the first instance of this application running.
...
// Before exiting, close the object.
CloseHandle(h);
return(0);
}
A service’s named kernel objects always go in the global namespace. By default, in Terminal Services, an application’s named kernel object goes in the session’s namespace. However, it is possible to force the named object to go into the global namespace by prefixing the name with “Global\”, as in the following example:
HANDLE h = CreateEvent(NULL, FALSE, FALSE, TEXT("Global\\MyName"));
You can also explicitly state that you want a kernel object to go in the current session’s namespace by prefixing the name with “Local\”, as in the following example:
HANDLE h = CreateEvent(NULL, FALSE, FALSE, TEXT("Local\\MyName"));
When you create a kernel object, you can protect the access to it by passing a pointer to a SECURITY_ATTRIBUTES structure. However, prior to the release of Windows Vista, it was not possible to protect the name of a shared object against hijacking. Any process, even with the lowest privileges, is able to create an object with a given name. If you take the previous example where an application is using a named mutex to detect whether or not it is already started, you could very easily write another application that creates a kernel object with the same name. If it gets started before the singleton application, this application becomes a “none-gleton” because it will start and then always immediately exit, thinking that another instance of itself is already running. This is the base mechanism behind a couple of attacks known as Denial of Service (DoS) attacks. Notice that unnamed kernel objects are not subject to DoS attacks, and it is quite common for an application to use unnamed objects, even though they can’t be shared between processes.
Duplicating Object Handlers
The last technique for sharing kernel objects across process boundaries requires the use of the DuplicateHandle function:
BOOL DuplicateHandle( HANDLE hSourceProcessHandle, HANDLE hSourceHandle, HANDLE hTargetProcessHandle, PHANDLE phTargetHandle, DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwOptions);
Simply stated, this function takes an entry in one process’ handle table and makes a copy of the entry into another process’ handle table. DuplicateHandle takes several parameters but is actually quite straightforward. The most general usage of the DuplicateHandle function could involve three different processes that are running in the system.