This post is the summarize of Ch3 "Kernal Objects" of Windows via C/C++, Fifth Edition Jeffrey Richter (Wintellect) Christophe Nasarre book.
What Is a Kernel Object ?
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. Some members (security descriptor, usage count, and so on) are the same across all object types, but most are specific to a particular object type. For example, a process object has a process ID, a base priority, and an exit code, whereas a file object has a byte offset, a sharing mode, and an open mode.
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.
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.
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.
Usage Counting:
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. The important thing to remember is that a kernel object can outlive the process that created 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. When an object is first created, its usage count is set to 1. When another process gains access to an existing kernel object, the usage count is incremented. When a process terminates, the kernel automatically decrements the usage count for all the kernel objects the process still has open. If the object's usage count goes to 0, the kernel destroys the object. This ensures that no kernel object will remain in the system if no processes are referencing the object.
Security:
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.
Almost all functions that create kernel objects have a pointer to a SECURITY_ATTRIBUTES structure as an argument, as shown here with the CreateFileMapping function:
HANDLE CreateFileMapping(
HANDLE hFile,
PSECURITY_ATTRIBUTES psa,
DWORD flProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
PCTSTR pszName);
Most applications simply pass NULL for this argument so that the object is created with a default security build based on the current process security token. However, you can allocate a SECURITY_ATTRIBUTES structure, initialize it, and pass the address of the structure for this parameter. A SECURITY_ATTRIBUTES structure looks like this:
typedef struct _SECURITY_ATTRIBUTES {
DWORD nLength;
LPVOID lpSecurityDescriptor;
BOOL bInheritHandle;
} SECURITY_ATTRIBUTES;
Even though this structure is called SECURITY_ATTRIBUTES, it really includes only one member that has anything to do with security: lpSecurityDescriptor. If you want to restrict access to a kernel object you create, you must create a security descriptor and then initialize the SECURITY_ATTRIBUTES structure as follows:
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa); // Used for versioning
sa.lpSecurityDescriptor = pSD; // Address of an initialized SD
sa.bInheritHandle = FALSE; // Discussed later
HANDLE hFileMapping = CreateFileMapping(INVALID_HANDLE_VALUE, &sa,
PAGE_READWRITE, 0, 1024, TEXT("MyFileMapping"));
When you want to gain access to an existing kernel object (rather than create a new one), you must specify the operations you intend to perform on the object. For example, if I want to gain access to an existing file-mapping kernel object so that I could read data from it, I call OpenFileMapping as follows:
HANDLE hFileMapping = OpenFileMapping(FILE_MAP_READ, FALSE,
TEXT("MyFileMapping"));
By passing FILE_MAP_READ as the first parameter to OpenFileMapping, I am indicating that I intend to read from this file mapping after I gain access to it. The OpenFileMapping function performs a security check first, before it returns a valid handle value. If I (the logged-on user) am allowed access to the existing file-mapping kernel object, OpenFileMapping returns a valid handle. However, if I am denied this access, OpenFileMapping returns NULL and a call to GetLastError will return a value of 5 (ERROR_ACCESS_DENIED).
In addition to using kernel objects, your application might use other types of objects, such as menus, windows, mouse cursors, brushes, and fonts. These objects are User objects or Graphical Device Interface (GDI) objects, not kernel objects. 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, as did the CreateFileMapping function shown earlier.
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);
A Process' Kernel Object Handle Table:
What Is a Kernel Object ?
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. Some members (security descriptor, usage count, and so on) are the same across all object types, but most are specific to a particular object type. For example, a process object has a process ID, a base priority, and an exit code, whereas a file object has a byte offset, a sharing mode, and an open mode.
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.
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.
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.
Usage Counting:
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. The important thing to remember is that a kernel object can outlive the process that created 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. When an object is first created, its usage count is set to 1. When another process gains access to an existing kernel object, the usage count is incremented. When a process terminates, the kernel automatically decrements the usage count for all the kernel objects the process still has open. If the object's usage count goes to 0, the kernel destroys the object. This ensures that no kernel object will remain in the system if no processes are referencing the object.
Security:
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.
Almost all functions that create kernel objects have a pointer to a SECURITY_ATTRIBUTES structure as an argument, as shown here with the CreateFileMapping function:
HANDLE CreateFileMapping(
HANDLE hFile,
PSECURITY_ATTRIBUTES psa,
DWORD flProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
PCTSTR pszName);
Most applications simply pass NULL for this argument so that the object is created with a default security build based on the current process security token. However, you can allocate a SECURITY_ATTRIBUTES structure, initialize it, and pass the address of the structure for this parameter. A SECURITY_ATTRIBUTES structure looks like this:
typedef struct _SECURITY_ATTRIBUTES {
DWORD nLength;
LPVOID lpSecurityDescriptor;
BOOL bInheritHandle;
} SECURITY_ATTRIBUTES;
Even though this structure is called SECURITY_ATTRIBUTES, it really includes only one member that has anything to do with security: lpSecurityDescriptor. If you want to restrict access to a kernel object you create, you must create a security descriptor and then initialize the SECURITY_ATTRIBUTES structure as follows:
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa); // Used for versioning
sa.lpSecurityDescriptor = pSD; // Address of an initialized SD
sa.bInheritHandle = FALSE; // Discussed later
HANDLE hFileMapping = CreateFileMapping(INVALID_HANDLE_VALUE, &sa,
PAGE_READWRITE, 0, 1024, TEXT("MyFileMapping"));
When you want to gain access to an existing kernel object (rather than create a new one), you must specify the operations you intend to perform on the object. For example, if I want to gain access to an existing file-mapping kernel object so that I could read data from it, I call OpenFileMapping as follows:
HANDLE hFileMapping = OpenFileMapping(FILE_MAP_READ, FALSE,
TEXT("MyFileMapping"));
By passing FILE_MAP_READ as the first parameter to OpenFileMapping, I am indicating that I intend to read from this file mapping after I gain access to it. The OpenFileMapping function performs a security check first, before it returns a valid handle value. If I (the logged-on user) am allowed access to the existing file-mapping kernel object, OpenFileMapping returns a valid handle. However, if I am denied this access, OpenFileMapping returns NULL and a call to GetLastError will return a value of 5 (ERROR_ACCESS_DENIED).
In addition to using kernel objects, your application might use other types of objects, such as menus, windows, mouse cursors, brushes, and fonts. These objects are User objects or Graphical Device Interface (GDI) objects, not kernel objects. 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, as did the CreateFileMapping function shown earlier.
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);
A Process' Kernel Object Handle Table:
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.
Process' handle table is simply an array of data structures. Each structure contains a pointer to a kernel object, an access mask, and some flags.
Creating a Kernel Object:
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.
Whenever you call a function that accepts a kernel object handle as an argument, you pass the value returned by one of the Create* functions. Internally, the function looks in your process' handle table to get the address of the kernel object you want to manipulate and then manipulates the object's data structure in a well-defined fashion.
If you pass an invalid handle, the function returns failure and GetLastError returns 6 (ERROR_INVALID_HANDLE). Because handle values are actually used as indexes into the process' handle table, these handles are process-relative and cannot be used successfully from other processes. And if you ever tried to do so, you would simply reference the kernel object stored at the same index into the other process' handle table, without any idea of what this object would be.
If you call a function to create a kernel object and the call fails, the handle value returned is usually 0 (NULL). 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. You must be very careful when checking the return value of a function that creates a kernel object. Specifically, you can compare the value with INVALID_HANDLE_VALUE only when you call CreateFile. The following code is incorrect:
HANDLE hMutex = CreateMutex(…);
if (hMutex == INVALID_HANDLE_VALUE) {
// We will never execute this code because
// CreateMutex returns NULL if it fails.
}
Likewise, the following code is also incorrect:
HANDLE hFile = CreateFile(…);
if (hFile == NULL) {
// We will never execute this code because CreateFile
// returns INVALID_HANDLE_VALUE (-1) if it fails.
}
Closing a Kernel Object:
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);
Internally, this function first checks the calling process' handle table to ensure that the handle value passed to it identifies an object that the process does in fact have access to. If the handle is valid, the system gets the address of the kernel object's data structure and decrements the usage count member in the structure. If the count is zero, the kernel object is destroyed and removed from memory.
If an invalid handle is passed to CloseHandle, one of two things might happen. If your process is running normally, CloseHandle returns FALSE and GetLastError returns ERROR_INVALID_HANDLE. Or, if your process is being debugged, the system throws the exception 0xC0000008 ("An invalid handle was specified") so that you can debug the error.
Right before CloseHandle returns, it clears out the entry in the process' handle table this handle is now invalid for your process, and you should not attempt to use it. The clearing happens whether or not the kernel object has been destroyed! After you call CloseHandle, you no longer have access to the kernel object; however, if the object's count did not decrements to zero, the object has not been destroyed. This is OK; it just means that one or more other processes are still using the object. When the other processes stop using the object (by calling CloseHandle), the object will be destroyed.
Process' handle table is simply an array of data structures. Each structure contains a pointer to a kernel object, an access mask, and some flags.
Creating a Kernel Object:
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.
Whenever you call a function that accepts a kernel object handle as an argument, you pass the value returned by one of the Create* functions. Internally, the function looks in your process' handle table to get the address of the kernel object you want to manipulate and then manipulates the object's data structure in a well-defined fashion.
If you pass an invalid handle, the function returns failure and GetLastError returns 6 (ERROR_INVALID_HANDLE). Because handle values are actually used as indexes into the process' handle table, these handles are process-relative and cannot be used successfully from other processes. And if you ever tried to do so, you would simply reference the kernel object stored at the same index into the other process' handle table, without any idea of what this object would be.
If you call a function to create a kernel object and the call fails, the handle value returned is usually 0 (NULL). 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. You must be very careful when checking the return value of a function that creates a kernel object. Specifically, you can compare the value with INVALID_HANDLE_VALUE only when you call CreateFile. The following code is incorrect:
HANDLE hMutex = CreateMutex(…);
if (hMutex == INVALID_HANDLE_VALUE) {
// We will never execute this code because
// CreateMutex returns NULL if it fails.
}
Likewise, the following code is also incorrect:
HANDLE hFile = CreateFile(…);
if (hFile == NULL) {
// We will never execute this code because CreateFile
// returns INVALID_HANDLE_VALUE (-1) if it fails.
}
Closing a Kernel Object:
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);
Internally, this function first checks the calling process' handle table to ensure that the handle value passed to it identifies an object that the process does in fact have access to. If the handle is valid, the system gets the address of the kernel object's data structure and decrements the usage count member in the structure. If the count is zero, the kernel object is destroyed and removed from memory.
If an invalid handle is passed to CloseHandle, one of two things might happen. If your process is running normally, CloseHandle returns FALSE and GetLastError returns ERROR_INVALID_HANDLE. Or, if your process is being debugged, the system throws the exception 0xC0000008 ("An invalid handle was specified") so that you can debug the error.
Right before CloseHandle returns, it clears out the entry in the process' handle table this handle is now invalid for your process, and you should not attempt to use it. The clearing happens whether or not the kernel object has been destroyed! After you call CloseHandle, you no longer have access to the kernel object; however, if the object's count did not decrements to zero, the object has not been destroyed. This is OK; it just means that one or more other processes are still using the object. When the other processes stop using the object (by calling CloseHandle), the object will be destroyed.
Sharing Kernel Objects Across Process Boundaries:
Frequently, threads running in different processes need to share kernel objects. Here are some of the reasons
- File-mapping objects allow you to share blocks of data between two processes running on a single machine.
- Mailslots and named pipes allow applications to send blocks of data between processes running on different machines connected to the network.
- Mutexes, semaphores, and events allow threads in different processes to synchronize their continued execution, as in the case of an application that needs to notify another application when it has completed some task.
There are 3 mechanisms that allow processes to share kernel objects
- Using object handle inheritance.
- Naming objects.
- Duplicating object handles.
1. Using object handle 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.
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);
This code initializes a SECURITY_ATTRIBUTES structure indicating that the object should be created using default security and that the returned handle should be inheritable.
If you pass NULL as the PSECURITY_ATTRIBUTES parameter when you create a kernel object, the handle returned is not inheritable and this bit is zero. Setting the bInheritHandle member to TRUE causes this flag bit to be set to 1.
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:
But because you passed TRUE to CreateProcess' bInheritHandles parameter, the system does one more thing: it walks the parent process' handle table, and for each entry it finds that contains a valid inheritable handle, the system copies the entry exactly into the child process' handle table. The entry is copied to the exact same position in the child process' handle table as in the parent's handle table. This fact is important because it means that the handle value that identifies a kernel object is identical in both the parent and child processes.
In addition to copying the handle table entry, the system increments the usage count of the kernel object because two processes are now using the object. For the kernel object to be destroyed, both the parent process and the child process must either call CloseHandle on the object or terminate. The child does not have to terminate first but neither does the parent. In fact, the parent process can close its handle to the object immediately after the CreateProcess function returns without affecting the child's ability to manipulate the object.
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. The special case of a child process inheriting its parent console is detailed in the Microsoft Knowledge Base at http://support.microsoft.com/kb/190351.
When The parent process wants only one child to inherit the kernel object handle. In other words, you might at times want to control which child processes inherit kernel object handles. To alter the inheritance flag of a kernel object handle, you can call the SetHandleInformation function:
BOOL SetHandleInformation(
HANDLE hObject,
DWORD dwMask,
DWORD dwFlags);
As you can see, this function takes three parameters. The first, hObject, identifies a valid handle. The second parameter, dwMask, tells the function which flag or flags you want to change. Currently, two flags are associated with each handle:
#define HANDLE_FLAG_INHERIT 0x00000001
#define HANDLE_FLAG_PROTECT_FROM_CLOSE 0x00000002
You can perform a bitwise OR on both of these flags together if you want to change each object's flags simultaneously. SetHandleInformation's third parameter, dwFlags, indicates what you want to set the flags to. For example, to turn on the inheritance flag for a kernel object handle, do the following:
SetHandleInformation(hObj, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
To turn off this flag, do this:
SetHandleInformation(hObj, HANDLE_FLAG_INHERIT, 0);
The HANDLE_FLAG_PROTECT_FROM_CLOSE flag tells the system that this handle should not be allowed to be closed:
SetHandleInformation(hObj, HANDLE_FLAG_PROTECT_FROM_CLOSE,
HANDLE_FLAG_PROTECT_FROM_CLOSE);
CloseHandle(hObj); // Exception is raised
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));
2. Naming objects:
The second method available for sharing kernel objects across process boundaries is to name the objects. Many though not all kernel objects can be named. For example, all of the following functions create named kernel objects:
HANDLE CreateMutex(
PSECURITY_ATTRIBUTES psa,
BOOL bInitialOwner,
PCTSTR pszName);
HANDLE CreateEvent(
PSECURITY_ATTRIBUTES psa,
BOOL bManualReset,
BOOL bInitialState,
PCTSTR pszName);
HANDLE CreateSemaphore(
PSECURITY_ATTRIBUTES psa,
LONG lInitialCount,
LONG lMaximumCount,
PCTSTR pszName);
HANDLE CreateWaitableTimer(
PSECURITY_ATTRIBUTES psa,
BOOL bManualReset,
PCTSTR pszName);
HANDLE CreateFileMapping(
HANDLE hFile,
PSECURITY_ATTRIBUTES psa,
DWORD flProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
PCTSTR pszName);
HANDLE CreateJobObject(
PSECURITY_ATTRIBUTES psa,
PCTSTR pszName);
All these 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.
If you don't pass NULL for the pszName parameter, you should pass the address of a zero-terminated string name. This name can be up to MAX_PATH characters long (defined as 260). Unfortunately, Microsoft offers no guidance for assigning names to kernel objects. For example, if you attempt to create an object called "JeffObj," there's no guarantee that an object called "JeffObj" doesn't already exist. To make matters worse, all these objects share a single namespace even though they don't share the same type. Because of this, the following call to CreateSemaphore always returns NULL because a mutex already exists with the same name:
HANDLE hMutex = CreateMutex(NULL, FALSE, TEXT("JeffObj"));
HANDLE hSem = CreateSemaphore(NULL, 1, 1, TEXT("JeffObj"));
DWORD dwErrorCode = GetLastError();
HANDLE hMutexProcessA = CreateMutex(NULL, FALSE, TEXT("JeffMutex"));
This function call creates a new mutex kernel object and assigns it the name "JeffMutex". Notice that in Process A's handle, hMutexProcessA is not an inheritable handle and it doesn't have to be when you're only naming objects.
Some time later, some process spawns Process B. Process B does not have to be a child of Process A; it might be spawned from Windows Explorer or any other application. The fact that Process B need not be a child of Process A is an advantage of using named objects instead of inheritance. When Process B starts executing, it executes the following code:
HANDLE hMutexProcessB = CreateMutex(NULL, FALSE, TEXT("JeffMutex"));
When Process B's call to CreateMutex is made, the system first checks to find out whether a kernel object with the name "JeffMutex" already exists. Because an object with this name does exist, the kernel then checks the object type. Because we are attempting to create a mutex and the object with the name "JeffMutex" is also a mutex, the system then makes a security check to see whether the caller has full access to the object. If it does, the system locates an empty entry in Process B's handle table and initializes the entry to point to the existing kernel object. If the object types don't match or if the caller is denied access, CreateMutex fails (returns NULL).
An alternative method exists for sharing objects by name. Instead of calling a Create* function, a process can call one of the Open* functions shown here:
HANDLE OpenMutex(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
PCTSTR pszName);
HANDLE OpenEvent(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
PCTSTR pszName);
HANDLE OpenSemaphore(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
PCTSTR pszName);
HANDLE OpenWaitableTimer(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
PCTSTR pszName);
HANDLE OpenFileMapping(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
PCTSTR pszName);
HANDLE OpenJobObject(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
PCTSTR pszName);
Notice that all these functions have the same prototype. 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);
}
3. Terminal Services Namespaces:
There is one global namespace, which is used by kernel objects that are meant to be accessible by all client sessions. This namespace is mostly used by services. In addition, each client session has its own namespace. This arrangement keeps two or more sessions that are running the same application from trampling over each other one session cannot access another session's objects even though the objects share the same name.
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"));
Private Namespaces:
If you want to ensure that the kernel object names created by your own applications never conflict with any other application's names or are the subject of hijack attacks, you can define a custom prefix and use it as a private namespace as you do with Global and Local. The server process responsible for creating the kernel object defines a boundary descriptor that protects the namespace name itself.
To sum up, a private namespace is just a directory where you create kernel objects. Like other directories, a private namespace has a security descriptor associated with it that is set when you call CreatePrivateNamespace. However, unlike file system directories, this namespace does not have a parent or a name the boundary descriptor is used as a name to refer to it. This is the reason why the kernel objects created with a prefix based on a private namespace appear in Process Explorer from Sysinternals with a "…\" prefix instead of the expected "namespace name\". The "…\" prefix hides information, thereby granting more protection against potential hackers. The name that you give to a private namespace is an alias visible only within the process. Other processes (and even the same process) can open the very same private namespace and give it a different alias.
Duplicating Object Handles:
As with inheritance, one of the odd things about the DuplicateHandle function is that the target process is not given any notification that a new kernel object is now accessible to it. So Process C must somehow notify Process T that it now has access to a kernel object, and it must use some form of interprocess communication to pass the handle value in hObj to Process T. Obviously, using a command-line argument or changing Process T's environment variables is out of the question because the process is already up and running. A window message or some other interprocess communication (IPC) mechanism must be used.
Usually, DuplicateHandle is called when only two processes are involved.
The call to GetCurrentProcess returns a pseudo-handle that always identifies the calling process Process S in this example.
Here is another way to use DuplicateHandle: Suppose that a process has read and write access to a file-mapping object. At some point, a function is called that is supposed to access the file-mapping object by only reading it. To make our application more robust, we can use Duplicate-Handle to create a new handle for the existing object and ensure that this new handle has read-only access on it. We would then pass this read-only handle to the function; this way, the code in the function would never be able to accidentally write to the file-mapping object. The following code illustrates this example:
int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE,
LPTSTR szCmdLine, int nCmdShow) {
// Create a file-mapping object; the handle has read/write access.
HANDLE hFileMapRW = CreateFileMapping(INVALID_HANDLE_VALUE,
NULL, PAGE_READWRITE, 0, 10240, NULL);
// Create another handle to the file-mapping object;
// the handle has read-only access.
HANDLE hFileMapRO;
DuplicateHandle(GetCurrentProcess(), hFileMapRW, GetCurrentProcess(),
&hFileMapRO, FILE_MAP_READ, FALSE, 0);
// Call the function that should only read from the file mapping.
ReadFromTheFileMapping(hFileMapRO);
// Close the read-only file-mapping object.
CloseHandle(hFileMapRO);
// We can still read/write the file-mapping object using hFileMapRW.
...
// When the main code doesn't access the file mapping anymore,
// close it.
CloseHandle(hFileMapRW);
}
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.
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);
This code initializes a SECURITY_ATTRIBUTES structure indicating that the object should be created using default security and that the returned handle should be inheritable.
If you pass NULL as the PSECURITY_ATTRIBUTES parameter when you create a kernel object, the handle returned is not inheritable and this bit is zero. Setting the bInheritHandle member to TRUE causes this flag bit to be set to 1.
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:
But because you passed TRUE to CreateProcess' bInheritHandles parameter, the system does one more thing: it walks the parent process' handle table, and for each entry it finds that contains a valid inheritable handle, the system copies the entry exactly into the child process' handle table. The entry is copied to the exact same position in the child process' handle table as in the parent's handle table. This fact is important because it means that the handle value that identifies a kernel object is identical in both the parent and child processes.
In addition to copying the handle table entry, the system increments the usage count of the kernel object because two processes are now using the object. For the kernel object to be destroyed, both the parent process and the child process must either call CloseHandle on the object or terminate. The child does not have to terminate first but neither does the parent. In fact, the parent process can close its handle to the object immediately after the CreateProcess function returns without affecting the child's ability to manipulate the object.
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. The special case of a child process inheriting its parent console is detailed in the Microsoft Knowledge Base at http://support.microsoft.com/kb/190351.
When The parent process wants only one child to inherit the kernel object handle. In other words, you might at times want to control which child processes inherit kernel object handles. To alter the inheritance flag of a kernel object handle, you can call the SetHandleInformation function:
BOOL SetHandleInformation(
HANDLE hObject,
DWORD dwMask,
DWORD dwFlags);
As you can see, this function takes three parameters. The first, hObject, identifies a valid handle. The second parameter, dwMask, tells the function which flag or flags you want to change. Currently, two flags are associated with each handle:
#define HANDLE_FLAG_INHERIT 0x00000001
#define HANDLE_FLAG_PROTECT_FROM_CLOSE 0x00000002
You can perform a bitwise OR on both of these flags together if you want to change each object's flags simultaneously. SetHandleInformation's third parameter, dwFlags, indicates what you want to set the flags to. For example, to turn on the inheritance flag for a kernel object handle, do the following:
SetHandleInformation(hObj, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
To turn off this flag, do this:
SetHandleInformation(hObj, HANDLE_FLAG_INHERIT, 0);
The HANDLE_FLAG_PROTECT_FROM_CLOSE flag tells the system that this handle should not be allowed to be closed:
SetHandleInformation(hObj, HANDLE_FLAG_PROTECT_FROM_CLOSE,
HANDLE_FLAG_PROTECT_FROM_CLOSE);
CloseHandle(hObj); // Exception is raised
BOOL GetHandleInformation(
HANDLE hObject,
PDWORD pdwFlags);
DWORD dwFlags;
GetHandleInformation(hObj, &dwFlags);
BOOL fHandleIsInheritable = (0 != (dwFlags & HANDLE_FLAG_INHERIT));
2. Naming objects:
The second method available for sharing kernel objects across process boundaries is to name the objects. Many though not all kernel objects can be named. For example, all of the following functions create named kernel objects:
HANDLE CreateMutex(
PSECURITY_ATTRIBUTES psa,
BOOL bInitialOwner,
PCTSTR pszName);
HANDLE CreateEvent(
PSECURITY_ATTRIBUTES psa,
BOOL bManualReset,
BOOL bInitialState,
PCTSTR pszName);
HANDLE CreateSemaphore(
PSECURITY_ATTRIBUTES psa,
LONG lInitialCount,
LONG lMaximumCount,
PCTSTR pszName);
HANDLE CreateWaitableTimer(
PSECURITY_ATTRIBUTES psa,
BOOL bManualReset,
PCTSTR pszName);
HANDLE CreateFileMapping(
HANDLE hFile,
PSECURITY_ATTRIBUTES psa,
DWORD flProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
PCTSTR pszName);
HANDLE CreateJobObject(
PSECURITY_ATTRIBUTES psa,
PCTSTR pszName);
All these 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.
If you don't pass NULL for the pszName parameter, you should pass the address of a zero-terminated string name. This name can be up to MAX_PATH characters long (defined as 260). Unfortunately, Microsoft offers no guidance for assigning names to kernel objects. For example, if you attempt to create an object called "JeffObj," there's no guarantee that an object called "JeffObj" doesn't already exist. To make matters worse, all these objects share a single namespace even though they don't share the same type. Because of this, the following call to CreateSemaphore always returns NULL because a mutex already exists with the same name:
HANDLE hMutex = CreateMutex(NULL, FALSE, TEXT("JeffObj"));
HANDLE hSem = CreateSemaphore(NULL, 1, 1, TEXT("JeffObj"));
DWORD dwErrorCode = GetLastError();
HANDLE hMutexProcessA = CreateMutex(NULL, FALSE, TEXT("JeffMutex"));
This function call creates a new mutex kernel object and assigns it the name "JeffMutex". Notice that in Process A's handle, hMutexProcessA is not an inheritable handle and it doesn't have to be when you're only naming objects.
Some time later, some process spawns Process B. Process B does not have to be a child of Process A; it might be spawned from Windows Explorer or any other application. The fact that Process B need not be a child of Process A is an advantage of using named objects instead of inheritance. When Process B starts executing, it executes the following code:
HANDLE hMutexProcessB = CreateMutex(NULL, FALSE, TEXT("JeffMutex"));
When Process B's call to CreateMutex is made, the system first checks to find out whether a kernel object with the name "JeffMutex" already exists. Because an object with this name does exist, the kernel then checks the object type. Because we are attempting to create a mutex and the object with the name "JeffMutex" is also a mutex, the system then makes a security check to see whether the caller has full access to the object. If it does, the system locates an empty entry in Process B's handle table and initializes the entry to point to the existing kernel object. If the object types don't match or if the caller is denied access, CreateMutex fails (returns NULL).
An alternative method exists for sharing objects by name. Instead of calling a Create* function, a process can call one of the Open* functions shown here:
HANDLE OpenMutex(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
PCTSTR pszName);
HANDLE OpenEvent(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
PCTSTR pszName);
HANDLE OpenSemaphore(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
PCTSTR pszName);
HANDLE OpenWaitableTimer(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
PCTSTR pszName);
HANDLE OpenFileMapping(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
PCTSTR pszName);
HANDLE OpenJobObject(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
PCTSTR pszName);
Notice that all these functions have the same prototype. 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);
}
3. Terminal Services Namespaces:
There is one global namespace, which is used by kernel objects that are meant to be accessible by all client sessions. This namespace is mostly used by services. In addition, each client session has its own namespace. This arrangement keeps two or more sessions that are running the same application from trampling over each other one session cannot access another session's objects even though the objects share the same name.
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"));
Private Namespaces:
If you want to ensure that the kernel object names created by your own applications never conflict with any other application's names or are the subject of hijack attacks, you can define a custom prefix and use it as a private namespace as you do with Global and Local. The server process responsible for creating the kernel object defines a boundary descriptor that protects the namespace name itself.
To sum up, a private namespace is just a directory where you create kernel objects. Like other directories, a private namespace has a security descriptor associated with it that is set when you call CreatePrivateNamespace. However, unlike file system directories, this namespace does not have a parent or a name the boundary descriptor is used as a name to refer to it. This is the reason why the kernel objects created with a prefix based on a private namespace appear in Process Explorer from Sysinternals with a "…\" prefix instead of the expected "namespace name\". The "…\" prefix hides information, thereby granting more protection against potential hackers. The name that you give to a private namespace is an alias visible only within the process. Other processes (and even the same process) can open the very same private namespace and give it a different alias.
Duplicating Object Handles:
As with inheritance, one of the odd things about the DuplicateHandle function is that the target process is not given any notification that a new kernel object is now accessible to it. So Process C must somehow notify Process T that it now has access to a kernel object, and it must use some form of interprocess communication to pass the handle value in hObj to Process T. Obviously, using a command-line argument or changing Process T's environment variables is out of the question because the process is already up and running. A window message or some other interprocess communication (IPC) mechanism must be used.
Usually, DuplicateHandle is called when only two processes are involved.
The call to GetCurrentProcess returns a pseudo-handle that always identifies the calling process Process S in this example.
Here is another way to use DuplicateHandle: Suppose that a process has read and write access to a file-mapping object. At some point, a function is called that is supposed to access the file-mapping object by only reading it. To make our application more robust, we can use Duplicate-Handle to create a new handle for the existing object and ensure that this new handle has read-only access on it. We would then pass this read-only handle to the function; this way, the code in the function would never be able to accidentally write to the file-mapping object. The following code illustrates this example:
int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE,
LPTSTR szCmdLine, int nCmdShow) {
// Create a file-mapping object; the handle has read/write access.
HANDLE hFileMapRW = CreateFileMapping(INVALID_HANDLE_VALUE,
NULL, PAGE_READWRITE, 0, 10240, NULL);
// Create another handle to the file-mapping object;
// the handle has read-only access.
HANDLE hFileMapRO;
DuplicateHandle(GetCurrentProcess(), hFileMapRW, GetCurrentProcess(),
&hFileMapRO, FILE_MAP_READ, FALSE, 0);
// Call the function that should only read from the file mapping.
ReadFromTheFileMapping(hFileMapRO);
// Close the read-only file-mapping object.
CloseHandle(hFileMapRO);
// We can still read/write the file-mapping object using hFileMapRW.
...
// When the main code doesn't access the file mapping anymore,
// close it.
CloseHandle(hFileMapRW);
}
I like how you want to summarize and share information to others , god bless you man :)
ReplyDelete