What Is a Kernel Object?

[Previous] [Next]

As a Windows software developer, you create, open, and manipulate kernel objects regularly. The system creates and manipulates several types of kernel objects, such as access token objects, event objects, file objects, file-mapping objects, I/O completion port objects, job objects, mailslot objects, mutex objects, pipe objects, process objects, semaphore objects, thread objects, and waitable timer objects. These objects are created by calling various functions. For example, the CreateFileMapping function causes the system to create a file-mapping 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. Microsoft enforces this restriction deliberately to ensure that the kernel object structures maintain a consistent state. This restriction also allows Microsoft to add, remove, or change the members in these structures without breaking any applications.

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. You pass this handle to the various Windows functions so that the system knows which kernel object you want to manipulate. We'll talk a lot more about these handles later in this chapter.

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's handle value would fail. In the section "Sharing Kernel ObjectsAcross Process Boundaries" (at the end of this chapter), we'll look at three mechanisms that allow multiple processes to successfully share a single kernel object.

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. 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 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. Then 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 created the object, who can gain access to or use the object, and who is denied access to the object. Security descriptors are usually used when writing server applications; you can ignore this feature of kernel objects if you are writing client-side applications.

Windows 98
Windows 98 is not designed for use as a server-side operating system. For this reason, Microsoft did not implement security features in Windows 98. However, if you are designing software for Windows 98 today, you should still be aware of security issues and use the proper access information when implementing your application to ensure that it runs correctly on Microsoft Windows 2000.

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 will simply pass NULL for this argument so that the object is created with default security. Default security means that any member of the administrators group and the creator of the object have full access to the object; all others are denied access. 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, "MyFileMapping");  

Since this member has nothing to do with security, I'm going to postpone discussing the bInheritHandle member until the section on inheritance later in this chapter.

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 wanted to gain access to an existing file-mapping kernel object so that I could read data from it, I would call OpenFileMapping as follows:

 HANDLE hFileMapping = OpenFileMapping(FILE_MAP_READ, FALSE, "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). Again, most applications do not use security, so I won't go into this issue any further.

Windows 98
While many applications do not need to be concerned about security, many Windows functions require that you pass desired security access information. Several applications designed for Windows 98 do not work properly on Windows 2000 because security was not given enough consideration when the application was implemented.

For example, imagine an application that, when started, reads some data from a registry subkey. To do this properly, your code should call RegOpenKeyEx, passing KEY_QUERY_VALUE for the desired access.

However, many applications were originally developed for Windows 98 without any consideration for Windows 2000. Since Windows 98 does not secure the registry, software developers frequently called RegOpenKeyEx, passing KEY_ALL_ACCESS as the desired access. Developers did this because it was a simpler solution and meant that the developer didn't have to really think about what access was required. The problem is that the registry subkey might be readable to the user, but not writable. So, when this application now runs on Windows 2000, the call to RegOpenKeyEx with KEY_ALL_ACCESS fails, and without proper error checking the application could run with totally unpredictable results.

If the developer had thought about security just a little and had changed KEY_ALL_ACCESS to KEY_QUERY_VALUE (which is all that is necessary in this example), the product would work on both operating system platforms.

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 originally designed for Windows 98 to Windows 2000.

In addition to 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 Graphics 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); 



Programming Applications for Microsoft Windows
Programming Applications for Microsoft Windows (Microsoft Programming Series)
ISBN: 1572319968
EAN: 2147483647
Year: 1999
Pages: 193

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net