Few things in the Windows NT kernel environment are more elusive than security. Indeed, this area is sufficiently complex that even those experienced with Windows NT internals do not fully understand its ramifications. For example, when a device driver creates a device object using IoCreateDevice(), the I/O Manager creates and applies a security descriptor. As it turns out, the security descriptor created varies depending upon the type of device object being created. Thus, sometimes we hear of people who find their device is inaccessible from non-Administrative users.
In addition, while there is considerable information about security programming within the Win32 SDK, the information available for kernel mode programmers is quite limited. The Windows NT DDK does not even contain sufficient information to create a security descriptor – even though a number of DDK functions accept such security descriptors. The Windows NT IFS Kit contains a limited number of functions, but anyone who has actually constructed a security descriptor using those functions probably knows how challenging it is.
In this article we will explore the issues involved in Windows NT security and demonstrate some of the techniques that one can use to take advantage of the security mechanisms available in Windows NT. This might include implementing enhanced security within your file system driver or merely allowing a normal device driver to modify the security of your device object.
Before we can engage in a serious discussion of security on Windows NT, it is worthwhile to define the terminology that is used when discussing NT security. While there is quite a lot of terminology specific to security, each of these terms is used throughout our discussion of security.
A security identifier is used by Windows NT to uniquely describe a “security entity” which most people think of as a “user”. In fact, while many people think of their domain and account name as their identification in the Windows NT environment, in fact Windows NT relies entirely upon the SID. Thus, one of the important functions performed when you log into a Windows NT system is to use your name to look up your security identifier. Once your user name is mapped to the SID, only the SID is tracked by the system.
Figure 1 -- The Security Identifier (SID)
The SID consists of a Revision declared by the symbolic constant SID_REVISION (in ntifs.h) which is currently the value 1, a SubAuthorityCount (a value between one and fifteen) indicating the number of “sub authority” values included in the SID, the IdentifierAuthority that indicates where and how the SID was generated and an array of up to 15 SubAuthority values that uniquely define this security entity.
SIDs are used by Windows NT to identify users, groups, and even computers – anything that can operate with its own private set of security credentials. They are associated with almost every other security object within the operating system.
Windows NT uses the Access Mask to describe access information about a particular type of object. The Access Mask consists of a single 32-bit mask that is logically managed as two distinct pieces. One piece (16 bits) is used to maintain access information common to all objects on Windows NT. The other piece (16 bits) is interpreted in a manner that is specific to the type of object being managed.
Figure 2 -- Access Mask
Within the common portion of the access mask, the operating system defines a set of bits that correspond to the “generic rights”. These “generic rights” are used to provide a programming paradigm that allows programmers to use a simple model. The operating system translates generic rights into the appropriate standard and specific rights for the particular object.
Generic rights can be one of:
- Read – the ability to “read” the information maintained by the object
- Write – the ability to “write” the information maintained by the object
- Execute – the ability to “execute” or alternatively “look into” the object.
- All – Read, Write, and Execute privileges.
For those who might be familiar with UNIX access concepts, you might actually recognize this as the “rwx” style access used to control UNIX resources. Because this simple paradigm is familiar to existing programmers and is available when working with any Windows NT object, it allows them to write applications for Windows NT without understanding the details of the security implementation for the specific object.
Standard rights are applicable to all types of objects. These include:
- Delete – the ability to delete the object
- Read control – the ability to read the security information for the object
- Write DAC – the ability to modify the “discretionary access control” for the object. We discuss discretionary access controls later in this article (Part II)
- Write Owner – the ability to modify the “owner” for the object. We discuss the concept of ownership later in this article (Part II)
- Synchronize – the ability to use the object for synchronization. This allows the caller to “wait” on the given object.
Specific rights are entirely dependent upon the specific object. They can be simple such as they are for the “symbolic link object”. A symbolic link object has a single, specific right, defined in ntddk.h as SYMBOLIC_LINK_QUERY (0x1). This indicates that in addition to the predefined rights, this object also has the right to “query” the link – this actually refers to querying the contents of the link.
An example of an object that defines many specific rights would be the file object. For the file object the following specific rights are defined:
- FILE_READ_DATA - the right to read from the file object.
- FILE_LIST_DIRECTORY –the right to enumerate the contents of the directory.
- FILE_WRITE_DATA –the right to write data to the file.
- FILE_APPEND_DATA – the right to append data to the end of the file.
- FILE_ADD_SUBDIRETORY – the right to create subdirectories.
- FILE_CREATE_PIPE_INSTANCE – the right to create an instance of a named pipe.
- FILE_READ_EA – the right to read the contents of the “extended attributes”.
- FILE_WRITE_EA – the right to write the contents of the “extended attributes”.
- FILE_EXECUTE – the right to execute the file.
- FILE_TRAVERSE – the right to access specific elements contained within a directory. Note that this differs from FILE_LIST_DIRECTORY because to traverse you need only know the name of the specific element being accessed.
- FILE_DELETE_CHILD – the right to delete an entry within a directory.
- FILE_READ_ATTRIBUTES – the right to read the attributes of the file.
- FILE_WRITE_ATTRIBUTES – the right to modify the attributes of the file.
Of course, access to driver-exported resources is always done via file objects, which is why the precise meaning of these attributes can be useful. Later in this article, we will discuss the specific rights for several other objects that are used by some kernel drivers.
As we mentioned previously, each object has a defined mapping from the generic rights to the standard and specific rights. For example, GENERIC_EXECUTE access is mapped to FILE_GENERIC_EXECUTE access for the file object. This includes the file object specific access rights FILE_READ_ATTRIBUTES and FILE_EXECUTE as well as the standard object access rights SYNCHRONIZE and READ_CONTROL.
Generic mappings are defined for all objects. This can be surprising, especially since some generic rights might not seem to apply to a given object. For example, GENERIC_EXECUTE is defined even for registry keys (KEY_EXECUTE) even though one would not normally consider a registry key as an object that could be executed.
One of the important tenants of good security design is that there is no such thing as a secure system. Thus, we must be aware that people will circumvent whatever security is present. This could be done actively, such as by probing the security subsystem to find (and exploit) holes within the system, or it could be accidental. Whatever the cause, it is imperative to construct a system that can detect such breaches.
Thus, when system security is compromised, it is important to be able to determine how it was compromised, what information was compromised and who was responsible. The key tool for this is an Auditing facility that tracks security events as they occur throughout the system. In Windows NT, these security events appear in the security portion of the event log. By default, however, auditing is normally not enabled and thus only logon/logoff events are indicated in the security log.
As we described in the section on auditing, an important aspect of a good security system is its ability to detect and report potential attempts to penetrate system security. For example, failed attempts to logon to a computer system are normally just because a user has mistyped their password. However, several failed attempts might indicate that a malicious intruder is attempting to “break in” or otherwise compromise the security of the system.
When the security system identifies such an instance, it can generate an Alarm condition. These alarm conditions can then be reported to an administrative alert mechanism to allow intervention. For example, this might entail something as simple as disabling a user’s account, or disabling the ability to log on to a particular computer. Alternatively, it might invoke a program that pages a security officer to investigate the problem more fully.
In current versions of Windows NT, alarms are not fully implemented.
Figure 3 -- Access Control Entry
Access Control Entry
An Access Control Entry (ACE) describes access rights associated with a particular SID. The access control entry is evaluated by the operating system in order to compute the “effective access” granted to a particular program based upon its credentials. For example, when a user logs into the computer and then executes a program, the program uses the credentials associated with that particular user’s account.
Thus, when a program attempts to open an object, the credentials associated with the program are compared against the security controls associated with the object. The security subsystem then utilizes the ACE information in order to determine if the program should be allowed (or denied) access to the given object. Thus, the ACE determines the behavior of the security subsystem.
There are five types of ACEs used by the security subsystem. The Type field of the ACE controls the interpretation of the ACE. The defined types are:
- ACCESS_ALLOWED_ACE_TYPE – this indicates that the ACE specifies access rights that are to be granted to the specific SID.
- ACCESS_DENIED_ACE_TYPE – this indicates that the ACE specifies access rights that are to be denied to the specific SID.
- SYSTEM_AUDIT_ACE_TYPE – this indicates that the ACE specifies auditing behavior.
- SYSTEM_ALARM_ACE_TYPE – this indicates that the ACE specifies alarm behavior.
- ACCESS_ALLOWED_COMPOUND_ACE_TYPE – this indicates that the ACE is tied to a particular server and the entity it is impersonating.
Thus, three of the types are used to control programmatic access to an object, while the other two are used to control the audit and alarm behavior of the security subsystem when the object is accessed. Note that the actual behavior of the security subsystem is computed by combining the information for some or all of the ACEs associated with the object.
Access Control List
An Access Control List (ACL) is a list of ACEs created by the operating system to control the security behavior associated with a given (protected) object of some sort. In Windows NT, there are two types of ACLs:
- Discretionary ACL – this is a list of zero or more ACEs describing the access rights for a protected object. It is discretionary because the access granted is at the discretion of the owner or any user with appropriate rights.
- System ACL – this is a list of zero or more ACEs describing the auditing and alarm policy for a protected object.
The term “discretionary” is actually based upon the differentiation between “mandatory” and “discretionary” control. In an environment using mandatory controls, the owner of an object may not be able to grant access to the object. In a discretionary environment, the owner of an object is allowed to grant such access. Mandatory controls are normally associated with very tight security environments, such as those using compartmentalized security, where the system must prevent disclosure of sensitive information.
Windows NT supports discretionary access controls, although it is always possible that future versions, suitable for classified work, might incorporate mandatory controls (in addition to other controls).
Every object created by the operating system may be protected using a Security Descriptor. The Security Descriptor in turn encapsulates all of the security information about the object, notably:
- Owner – this is a distinguished SID associated with a particular security descriptor. The owner always has the ability to write the security information for the object. This ensures that the owner of an object cannot lock out her own control.
- Group – this is an optional value that associates a group SID with a particular security descriptor. This is used by the POSIX subsystem, for example.
- SACL – this is the System ACL and it establishes the auditing and alarm policy for the given object.
- DACL – this is the Discretionary ACL and it establishes the discretionary access policy for the given object.
Figure 4 -- Security Descriptor
The Control information describes how the Security Descriptor was constructed, including a special form known as self-relative. In the self-relative format, the Owner, Group, SACL, and DACL fields are byte offsets to their respective (in-line) versions. Otherwise, they are pointers to memory buffers containing the appropriate element. Self-relative Security Descriptor’s are important because it allows other kernel components to store the Security Descriptor without understanding it. This is particularly important for file systems, and the registry subsystem, both of which store persistent security descriptors to protect objects that they in turn manage.
Interpreting the meaning of a security descriptor is thus left to the interpretation of the security subsystem. This method of encapsulating security policy minimizes the likelihood of introducing errors and allows third parties to validate and confirm the general security behavior of Windows NT – something that was no doubt essential to the C2-certification effort for Windows NT.
A Privilege is the right to perform some specific operation. Privileges operate independently of ACLs and under certain circumstances bypass normal security validation checking. For example, there is a backup privilege that allows opening an object for write access, even if the ACL on the file prohibits write access.
Privileges must be activated so that the operating system will use them. Thus, while a particular user might have the right to use backup privilege, until it is programmatically activated, the operating system will treat that user as if she did not have that privilege.
The operating system creates a security object called the token as the container of security information for a particular process or thread. These tokens provide authentication information that is in turn used to make security decisions. This token is treated as an opaque data element and thus its format is determined by the operating system. However, the token encapsulates information about “authentication state” including:
- SID of the user
- SID of the primary group (this is optional)
- SIDs for each group
- Privilege Information
- Default DACL (used by servers)
- Source information (used to determine how the token was generated)
- Token Type
- Impersonation Level
Tokens come in two basic types: Primary tokens, which are generated by the operating system when a user authenticates directly to the system, and impersonation tokens, which are used by servers to perform operations on behalf of the actual user.
The concept of impersonation is important because it allows servers to perform operations on behalf of remote users without implementing their own security policy. Thus, security decisions are made by the operating system as if the user were performing the operation, rather than the server.
Note that some operations are based upon the security credentials of the server, and not of the user being impersonated. For example, the ability to stop the impersonation is based upon the security credentials of the server. Of course, the security subsystem is responsible for determining which token to use, alleviating the server from understanding the nuances of the internal security implementation.
That’s all for this issue. While quite a bit has been covered here, we’ve just begun to scratch the surface on security. We’ll continue our discussions in a future edition of The NT Insider.