Object protection and access accounting is the essence of discretionary access control and auditing. The objects that can be protected on Windows 2000 include files, devices, mailslots, pipes (named and anonymous), jobs, processes, threads, events, mutexes, semaphores, shared memory sections, I/O completion ports, LPC ports, waitable timers, access tokens, window stations, desktops, network shares, services, registry keys, and printers.
Because system resources that are exported to user mode (and hence require security validation) are implemented as objects in kernel mode, the Windows 2000 object manager plays a key role in enforcing object security. (For more information on the object manager, see Chapter 3.) To control who can manipulate an object, the security system must first be sure of each user's identity. This need to guarantee the user's identity is the reason that Windows 2000 requires authenticated logon before accessing any system resources. When a process requests a handle to an object, the object manager and the security system use the caller's security identification to determine whether the caller should be assigned a handle that grants the process access to the object it desires.
As we'll discuss later in this chapter, a thread can assume a different security context than that of its process. This mechanism is called impersonation, and when a thread is impersonating, security validation mechanisms use the thread's security context instead of that of the thread's process. When a thread isn't impersonating, security validation falls back on using the security context of the thread's owning process. It's important to keep in mind that all the threads in a process share the same handle table, so when a thread opens an object—even if it's impersonating—all the threads of the process have access to the object.
The Windows 2000 security model requires that a thread specify up front, at the time that it opens an object, what types of actions it wants to perform on the object. The system performs access checks based on a thread's desired access, and if the access is granted, a handle is assigned to the thread's process with which the thread (or other threads in the process) can perform further operations on the object. As explained in Chapter 3, the object manager records the access permissions granted for a handle in the process's handle table.
One event that causes the object manager to perform security access validation is when a process opens an existing object using a name. When an object is opened by name, the object manager performs a lookup of the specified object in the object manager namespace. If the object isn't located in a secondary namespace, such as the configuration manager's registry namespace or a file system driver's file system namespace, the object manager calls the internal function ObpCreateHandle once it locates the object. As its name implies, ObpCreateHandle creates an entry in the process's handle table that becomes associated with the object. However, ObpCreateHandle calls the executive function ExCreateHandle to create the handle only if another object manager function, ObpIncrementHandleCount, indicates that the thread has permission to access the object. Another object manager function, ObCheckObjectAccess, actually carries out the security access check and returns the results to ObpIncrementHandleCount.
ObpIncrementHandleCount passes ObCheckObjectAccess the security credentials of the thread opening the object, the types of access to the object that the thread is requesting (read, write, delete, and so forth), and a pointer to the object. ObCheckObjectAccess first locks the object's security and the security context of the thread. The object security lock prevents another thread in the system from changing the object's security while the access check is in progress. The lock on the thread's security context prevents another thread of that process or a different process from altering the security identity of the thread while security validation is in progress. ObCheckObjectAccess then calls the object's security method to obtain the security settings of the object. (See Chapter 3 for a description of object methods.) The call to the security method might invoke a function in a different executive component. However, many executive objects rely on the system's default security management support.
When an executive component defining an object doesn't want to override the SRM's default security policy, it marks the object type as having default security. Whenever the SRM calls an object's security method, it first checks to see whether the object has default security. An object with default security stores its security information in its header, and its security method is SeDefaultObjectMethod. An object that doesn't rely on default security must manage its own security information and supply a specific security method. Objects that rely on default security include mutexes, events, and semaphores. A file object is an example of an object that overrides default security. The I/O manager, which defines the file object type, has the file system driver on which a file resides manage (or choose not to implement) the security for its files. Thus, when the system queries the security on a file object that represents a file on an NTFS volume, the I/O manager file object security method retrieves the file's security using the NTFS file system driver. Note, however, that ObCheckObjectAccess isn't executed when files are opened because they reside in secondary namespaces; the system invokes a file object's security method only when a thread explicitly queries or sets the security on a file (with the Win32 SetFileSecurity or GetFileSecurity functions, for example).
After obtaining an object's security information, ObCheckObjectAccess invokes the SRM function SeAccessCheck. SeAccessCheck is one of the functions at the heart of the Windows 2000 security model. Among the input parameters SeAccessCheck accepts are the object's security information, the security identity of the thread as captured by ObCheckObjectAccess, and the access that the thread is requesting. SeAccessCheck returns True or False, depending on whether the thread is granted the access it requested to the object.
Another event that causes the object manager to execute access validation is when a process references an object using an existing handle. Such references often occur indirectly, as when a process calls on a Win32 API to manipulate an object and passes an object handle. For example, a thread opening a file can request access to the object that permits it to read from the file. If the thread has permission to access the object in this way, as dictated by its security context and the security settings of the file, the object manager creates a handle—representing the file—in the handle table of the thread's process. The accesses the process is granted through the handle are stored with the handle by the object manager.
Subsequently, the thread can attempt to write to the file using the WriteFile Win32 function, passing the file's handle as a parameter. The system service NtWriteFile, which WriteFile calls via Ntdll.dll, uses the object manager function ObReferenceObjectByHandle to obtain a pointer to the file object from the handle. ObReferenceObjectByHandle accepts the access that the caller wants from the object as a parameter. After finding the handle entry in the process's handle table, ObReferenceObjectByHandle compares the access being requested with the access granted at the time the file was opened. In this case, ObReferenceObjectByHandle will indicate that the write operation should fail because the caller didn't obtain write access when the file was opened.
The Windows 2000 security functions also enable Win32 applications to define their own private objects and to call on the services of the SRM to enforce the Windows 2000 security model on those objects. Many kernel-mode functions that the object manager and other executive components use to protect their own objects are exported as Win32 user-mode APIs. The user-mode equivalent of SeAccessCheck is AccessCheck, for example. Win32 applications can therefore leverage the flexibility of the security model and transparently integrate with the authentication and administrative interfaces that are present in Windows 2000.
The essence of the SRM's security model is an equation that takes three inputs: the security identity of a thread, the access that the thread wants to an object, and the security settings of the object. The output is either "yes" or "no" and indicates whether or not the security model grants the thread the access it desires. The following sections describe the inputs in more detail and then document the model's access validation algorithm.
Instead of using names (which might or might not be unique) to identify entities that perform actions in a system, Windows 2000 uses security identifiers (SIDs). Users have SIDs, and so do local and domain groups, local computers, domains, and domain members. A SID is a variable-length numeric value that consists of a SID structure revision number, a 48-bit identifier authority value, and a variable number of 32-bit subauthority or relative identifier (RID) values. The authority value identifies the agent that issued the SID, and this agent is typically a Windows 2000 local system or a domain. Subauthority values identify trustees relative to the issuing authority, and RIDs are simply a way for Windows 2000 to create unique SIDs based on a common-base SID. Because SIDS are long and Windows 2000 takes care to generate truly random values within each SID, it is virtually impossible for Windows 2000 to issue the same SID twice on machines or domains anywhere in the world.
When displayed textually, each SID carries an S prefix, and its various components are separated with hyphens:
In this SID, the revision number is 1, the identifier authority value is 5 (the Windows 2000 security authority), and four subauthority values plus one RID (1128) make up the remainder of the SID. This SID is a domain SID, but a local computer on the domain would have a SID with the same revision number, identifier authority value, and number of subauthority values.
Using GetSID to View Account SIDs
You can easily see the SID representation for any account you're using by running the GetSID utility, located in the Windows 2000 resource kits. It has the following interface:
C:\>getsid Usage: getsid \\server1 account \\server2 account
GetSID's intended use is as a tool for detecting inconsistencies in the account databases of two domain controllers. It accepts the names of two users, each relative to a server, and retrieves the SIDs of each user from the server specified for them. It then compares the SIDs and indicates whether or not they match, at the same time displaying their textual representations. Despite the fact that GetSID requires you to specify two account names, you can still use it to obtain the SID of a single account simply by specifying the same account and server for both names. Here's an example of obtaining a single SID:
C:\>getsid \\w2kpro administrator \\w2kpro administrator The SID for account W2KPRO\administrator matches account W2KPRO\administrator The SID for account W2KPRO\administrator is S-1-5-21-1123561945-484763869-1957994488-500 The SID for account W2KPRO\administrator is S-1-5-21-1123561945-484763869-1957994488-500
When you install Windows 2000, the Windows 2000 Setup program issues the computer a SID. Windows 2000 assigns SIDs to local accounts on the computer. Each local-account SID is based on the source computer's SID and has a RID at the end. RIDs for user accounts and groups start at 1000 and increase in increments of 1 for each new user or group. Similarly, Windows 2000 issues a SID to each newly created Windows 2000 domain. Windows 2000 issues to new domain accounts SIDS that are based on the domain SID and have an appended RID (again starting at 1000 and increasing in increments of 1 for each new user or group). A RID of 1028 indicates that the SID is the 29th SID the domain issued.
Windows 2000 issues SIDS that consist of a computer or domain SID with a predefined RID to many predefined accounts and groups. For example, the RID for the administrator account is 500, and the RID for the guest account is 501. A computer's local administrator account, for example, has the computer SID as its base with the RID of 500 appended to it:
Windows 2000 also defines a number of built-in local and domain SIDs to represent groups. For example, a SID that identifies any and every account is the Everyone, or World, SID: S-1-1-0. Another example of a group that a SID can represent is the network group, which is the group that represents users who can log on to a machine from the network. The network-group SID is S-1-5-2. Table 8-2, reproduced here from the Platform SDK documentation, shows some of the basic well-known SIDs, their numeric values, and their use.
Table 8-2 Well-Known SIDs
|Everyone||A group that includes all users.|
|Local||Users who log on to terminals locally (physically) connected to the system.|
|Creator Owner ID||A security identifier to be replaced by the security identifier of the user who created a new object. This SID is used in inheritable access-control entries (ACEs).|
|Creator Group ID||Identifies a security identifier to be replaced by the primary-group SID of the user who created a new object. Use this SID in inheritable ACEs.|
The SRM uses an object called a token (or access token) to identify the security context of a process or thread. A security context consists of information that describes the privileges, accounts, and groups associated with the process or thread. During the logon process (described at the end of this chapter), Winlogon creates an initial token to represent the user logging on and attaches the token to the user's logon shell process. All programs the user executes inherit a copy of the initial token. You can also generate a token by using the Win32 LogonUser function. You can then use this token to create a process that runs within the security context of the user logged on by the LogonUser function by passing the token to the Win32 CreateProcessAsUser function. Tokens vary in size because different user accounts have different sets of privileges and associated group accounts. However, all tokens contain the same information, shown in Figure 8-3.
Figure 8-3 Access tokens
The security mechanisms in Windows 2000 use two token components to determine what a token's thread or process can do. One component comprises the token's user account SID and group SID fields. The SRM uses SIDs to determine whether a process or thread can obtain requested access to a securable object, such as an NTFS file.
The group SIDs in a token signify which groups a user's account is a member of. A server application can disable specific groups to restrict a token's credentials when the server application is performing actions a client requests. Disabling a group produces nearly the same effect as if the group wasn't present in the token. (Disabled SIDs are used as part of security access checks, described later in the chapter.)
The second component in a token that determines what the token's thread or process can do is the privilege array. A token's privilege array is a list of rights associated with the token. An example privilege is the right for the process or thread associated with the token to shut down the computer. There are about two-dozen token privileges, and a few of the most commonly used are shown in Table 8-3.
Table 8-3 Some Common Privileges
|Privilege Name||Privilege Usage|
|SeBackup||Bypasses security checks during backups|
|SeDebug||Required to debug a process|
|SeShutdown||Required to shut down a local system|
|SeTakeOwnership||Required to take ownership of an object without being granted discretionary access|
A token's default primary group field and default discretionary access-control list (DACL) field are security attributes that Windows 2000 applies to objects that a process or thread creates when it uses the token. By including security information in tokens, Windows 2000 makes it convenient for a process or thread to create objects with standard security attributes because the process or thread doesn't need to request discrete security information for every object it creates.
Each token's type distinguishes a primary token (a token that identifies the security context of a process) from an impersonation token (a token threads use to temporarily adopt a different security context, usually of another user). Impersonation tokens carry an impersonation level that signifies what type of impersonation is active in the token. We'll describe impersonation in more detail shortly.
The remainder of the fields in a token serve informational purposes. The token source field contains a short textual description of the entity that created the token. Programs that want to know where a token originated use the token source to distinguish among sources such as the Windows 2000 Session Manager, a network file server, or the remote procedure call (RPC) server. The token identifier is a locally unique identifier (LUID) that the SRM assigns to the token when it creates the token. The Windows 2000 executive maintains the executive LUID, a counter it uses to assign a unique numeric identifier to each token.
The token authentication ID is another kind of LUID. A token's creator assigns the token's authentication ID. Lsass is typically the only token creator on a system, and Lsass obtains the LUID from the executive LUID. Lsass then copies the authentication ID for all tokens descended from an initial logon token. A program can obtain a token's authentication ID to see whether the token belongs to the same logon session as other tokens the program has examined.
The executive LUID refreshes the modified ID every time a token's characteristics are modified. An application can test the modified ID to discover changes in a security context since the context's last use.
Tokens contain an expiration time field that has been present but unused in Windows NT technology since Windows NT 3.1. A future version of Windows 2000 might allow for tokens that are valid for a period of time before expiring. Consider a user for which the systems administrator sets an account expiration time. Currently, if the user logs on and remains logged on past the account expiration, the system will let the user continue to access resources. The only way to prevent the user from accessing resources is to forcibly log the user off the machine. If Windows 2000 supported token expiration, the system could prevent the user from opening resources past the token expiration time.
Viewing Access Tokens with the Kernel Debugger
The kernel debugger !tokenfields command displays the format of an internal token object. Although this structure differs from the user-mode token structure returned by Win32 API security functions, the fields are similar. For further information on tokens, see the description in the Platform SDK documentation.
The following output is from the kernel debugger's !tokenfields command:
kd> !tokenfields !tokenfields TOKEN structure offsets: TokenSource: 0x0 AuthenticationId: 0x18 ExpirationTime: 0x28 ModifiedId: 0x30 UserAndGroupCount: 0x3c PrivilegeCount: 0x44 VariableLength: 0x48 DynamicCharged: 0x4c DynamicAvailable: 0x50 DefaultOwnerIndex: 0x54 DefaultDacl: 0x6c TokenType: 0x70 ImpersonationLevel: 0x74 TokenFlags: 0x78 TokenInUse: 0x79 ProxyData: 0x7c AuditData: 0x80 VariablePart: 0x84
You can examine the token for a process with the !token command. You'll find the address of the token in the output of the !process command, as shown here:
kd> !process 380 1 !process 380 1 Searching for Process with Cid == 380 PROCESS ff8027a0 SessionId: 0 Cid: 0380 Peb: 7ffdf000 ParentCid: 0124 DirBase: 06433000 ObjectTable: ff7e0b68 TableSize: 23. Image: cmd.exe VadRoot 84c30568 Clone 0 Private 77. Modified 0. Locked 0. DeviceMap 818a3368 Token e22bc730 ElapsedTime 14:22:56.0536 UserTime 0:00:00.0040 KernelTime 0:00:00.0100 QuotaPoolUsage[PagedPool] 13628 QuotaPoolUsage[NonPagedPool] 1616 Working Set Sizes (now,min,max) (261, 50, 345)(1044KB, 200KB, 1380KB) PeakWorkingSetSize 262 VirtualSize 11 Mb PeakVirtualSize 11 Mb PageFaultCount 313 MemoryPriority FOREGROUND BasePriority 8 CommitCharge 86 kd> !token e22bc730 !token e22bc730 TOKEN e22bc730 Flags: 9 Source User32 b" AuthentId (0, ae6d) Type: Primary (IN USE) Token ID: 1803a ParentToken ID: 0 Modified ID: (0, 12bf5) TokenFlags: 0x9 SidCount: 9 Sids: e22bc880 RestrictedSidCount: 0 RestrictedSids: 0 PrivilegeCount: 17 Privileges: e22bc7b4
Impersonation is a powerful feature Windows 2000 uses frequently in its security model. Windows 2000 also uses impersonation in its client/server programming model. For example, a server application can export resources such as files, printers, or databases. Clients wanting to access a resource send a request to the server. When the server receives the request, it must ensure that the client has permission to perform the desired operations on the resource. For example, if a user on a remote machine tries to delete a file on an NTFS share, the server exporting the share must determine whether the user is allowed to delete the file. The obvious process to determine whether a user has permission is for the server to query the user's account and group SIDs, and scan the security attributes on the file. This process is tedious to program, prone to errors, and wouldn't permit new security features to be supported transparently. Thus, Windows 2000 provides impersonation services to simplify the server's job.
Impersonation lets a server notify the SRM that the server is temporarily adopting the security profile of a client making a resource request. The server can then access resources on behalf of the client, and the SRM carries out the access validations. Usually, a server has access to more resources than a client does and loses some of its security credentials during impersonation. However, the reverse can be true: the server can gain security credentials during impersonation.
A server impersonates a client only within the thread that makes the impersonation request. Thread-control data structures contain an optional entry for an impersonation token. However, a thread's primary token, which represents the thread's real security credentials, is always accessible in the process's control structure.
Windows 2000 makes impersonation available through several mechanisms. If a server communicates with a client through a named pipe, the server can use the ImpersonateNamedPipeClient Win32 API function to tell the SRM that it wants to impersonate the user on the other end of the pipe. If the server is communicating with the client through Dynamic Data Exchange (DDE) or an RPC, it can make similar impersonation requests using DdeImpersonateClient and RpcImpersonateClient. A thread can create an impersonation token that's simply a copy of its process token with the ImpersonateSelf function. The thread can then alter its impersonation token, to disable SIDs or privileges, for example. Finally, a Security Support Provider Interface (SSPI) package can impersonate its clients with ImpersonateSecurityContext. SSPIs implement a network security model such as LAN Manager 2 or Kerberos.
After the server thread finishes its task, it reverts to its primary security profile. These forms of impersonation are convenient for carrying out specific actions at the request of a client. The disadvantage to these forms of impersonation is that they can't execute an entire program in the context of a client. In addition, an impersonation token can't access files or printers on network shares unless the file or printer share supports null sessions. (A null session is one that results from an anonymous logon.)
If an entire application must execute in a client's security context or must access network resources, the client must be logged on to the system. The LogonUser Win32 API function enables this action. LogonUser takes an account name, a password, a domain or computer name, a logon type (such as interactive, batch, or service), and a logon provider as input, and it returns a primary token. A server thread can adopt the token as an impersonation token, or the server can start a program that has the client's credentials as its primary token. From a security standpoint, the process that LogonUser creates to run the program in an interactive logon session looks like a program a user starts by logging on to the machine interactively.
A second way Windows 2000 provides for impersonation of a client's security context that is similar to the use of LogonUser is by taking a client's access token, duplicating it, and using it as the primary token that is passed to the CreateProcessAsUser command. The disadvantage to using the LogonUser and CreateProcessAsUser approaches is that a server must obtain the user's account name and password. If the server transmits this information across the network, the server must encrypt it securely so that a malicious user snooping network traffic can't capture it.
To prevent the misuse of impersonation, Windows 2000 doesn't let servers perform impersonation without a client's consent. A client process can limit the level of impersonation that a server process can perform by specifying a security quality of service (SQOS) when connecting to the server. A process can specify SECURITY_ANONYMOUS, SECURITY_IDENTIFICATION, SECURITY_IMPERSONATION, and SECURITY_DELEGATION as flags for the Win32 CreateFile function. Each level lets a server perform different types of operations with respect to the client's security context:
If the client doesn't set an impersonation level, Windows 2000 chooses the SecurityImpersonation level by default. The CreateFile function also accepts SECURITY_EFFECTIVE_ONLY and SECURITY_CONTEXT_TRACKING as modifiers for the impersonation setting:
Windows 2000 introduces a new type of token called a restricted token. A restricted token is created from a primary or impersonation token using the CreateRestrictedToken function. The restricted token is a copy of the token it's derived from, with the following possible modifications:
The behavior of deny-only and restricted SIDs is covered shortly. Restricted tokens are useful when an application wants to impersonate a client at a reduced security level, primarily for safety reasons when running untrusted code. For example, the restricted token can have the reboot-system privilege removed from it to prevent code executed in the restricted token's security context from rebooting the system.
Tokens, which identify a user's credentials, are only part of the object security equation. Another part of the equation is the security information associated with an object, which specifies who can perform what actions on the object. The data structure for this information is called a security descriptor. A security descriptor consists of the following attributes:
An access-control list (ACL) is made up of a header and zero or more access-control entry (ACE) structures. There are two types of ACLs: DACLs and SACLs. In a DACL, each ACE contains a SID and an access mask (and a set of flags, explained shortly). Four types of ACEs can appear in a DACL: access allowed, access denied, allowed-object, and denied-object. As you would expect, the access-allowed ACE grants access to a user, and the access-denied ACE denies the access rights specified in the access mask.
The difference between allowed-object and access allowed, and between denied-object and access denied, is that the object types are used only within Active Directory. ACEs of these types have a GUID (globally unique identifier) field that indicates that the ACE applies only to particular objects or subobjects (those that have GUID identifiers). In addition, another optional GUID indicates what type of child object will inherit the ACE when a child is created within an Active Directory container that has the ACE applied to it. (A GUID is a 128bit identifier guaranteed to be universally unique.)
The accumulation of access rights granted by individual ACEs forms the set of access rights granted by an ACL. If no DACL is present (a null DACL) in a security descriptor, everyone has full access to the object. If the DACL is empty (has 0 ACEs), no user has access to the object.
The ACEs used in DACLs also have a set of flags that control and specify characteristics of the ACE related to inheritance. Some object namespaces have container objects and leaf objects (or just objects). A container can hold other container objects and leaf objects, which are its child objects. Examples of containers are directories in the file system namespace and keys in the registry namespace. Certain flags in an ACE control how the ACE propagates to child objects of the container associated with the ACE. Table 8-4, reproduced in part from the Platform SDK, lists the inheritance rules for ACE flags.
Table 8-4 Inheritance Rules for ACE Flags
|CONTAINER_INHERIT_ACE||Child objects that are containers, such as directories, inherit the ACE as an effective ACE. The inherited ACE is inheritable unless the NO_PROPAGATE_INHERIT_ACE bit flag is also set.|
|INHERIT_ONLY_ACE||Indicates an inherit-only ACE that doesn't control access to the object it's attached to.|
|INHERITED_ACE||Indicates that the ACE was inherited. The system sets this bit when it propagates an inheritable ACE to a child object.|
|NO_PROPAGATE_INHERIT_ACE||If the ACE is inherited by a child object, the system clears the OBJECT_INHERIT_ACE and CONTAINER_INHERIT_ACE flags in the inherited ACE. This action prevents the ACE from being inherited by subsequent generations of objects.|
|OBJECT_INHERIT_ACE||Noncontainer child objects inherit the ACE as an effective ACE. For child objects that are containers, the ACE is inherited as an inherit-only ACE unless the NO_PROPAGATE_INHERIT_ACE bit flag is also set.|
A SACL contains two types of ACEs, system audit ACEs and system audit-object ACEs. These ACEs specify which operations performed on the object by specific users or groups should be audited. Audit information is stored in the system Audit Log. Both successful and unsuccessful attempts can be audited. Like their DACL object-specific ACE cousins, system audit-object ACEs specify a GUID indicating the types of objects or subobjects that the ACE applies to and an optional GUID that controls propagation of the ACE to particular child object types. If a SACL is null, no auditing takes place on the object. (Security auditing is described later in this chapter.) The inheritance flags that apply to DACL ACEs also apply to system audit and system audit-object ACEs.
Figure 8-4 is a simplified picture of a file object and its DACL.
Figure 8-4 Discretionary access-control list (DACL)
As shown in Figure 8-4, the first ACE allows USER1 to read the file. The second ACE allows members of the group TEAM1 to have read and write access to the file, and the third ACE grants all other users (Everyone) execute access.
To determine which DACL to assign to a new object, the security system uses the first applicable rule of the following four assignment rules:
The rules the system uses when assigning a SACL to a new object are similar to those used for DACL assignment, with a couple exceptions. The first is that inherited system audit ACEs don't propagate to objects with security descriptors marked with the SE_SACL_PROTECTED flag (similar to the SE_DACL_PROTECTED flag, which protects DACLs). Second, if there are no specified security audit ACEs and there is no inherited SACL, no SACL is applied to the object. This behavior is different than that used to apply default DACLs because tokens don't have a default SACL.
When a new security descriptor containing inheritable ACEs is applied to a container, the system automatically propagates the inheritable ACEs to the security descriptors of child objects. (Note that a security descriptor's DACL doesn't accept inherited DACL ACES if its SE_DACL_PROTECTED flag is enabled, and its SACL doesn't inherit SACL ACES if the descriptor has the SE_SACL_PROTECTED flag set.) The order with which inheritable ACEs are merged with an existing child object's security descriptor is such that any ACEs that were explicitly applied to the ACL are kept ahead of ACEs that the object inherits. The system uses the following rules for propagating inheritable ACEs:
As you'll soon discover, the order of ACEs in an ACL is an important aspect of the Windows 2000 security model.
Because inheritance isn't supported for file system directories and registry keys, Windows Explorer and Regedt32 manually propagate security settings that you specify to apply to a directory and its contents or to an entire registry subkey.
Two algorithms are used for determining access to an object:
The first algorithm examines the entries in the DACL as follows:
When all the entries in the DACL have been examined, the computed granted-access mask is returned to the caller as the maximum allowed access to the object. This mask represents the total set of access types that the caller will be able to successfully request when opening the object.
The preceding description applies only to the kernel-mode form of the algorithm. The Win32 version implemented by GetEffectiveRightsFromAcl differs in that it doesn't perform step 2, and it considers a single user or group SID rather than an access token.
The second algorithm is used to determine whether a specific access request can be granted, based on the caller's access token. Each open function in the Win32 API that deals with securable objects has a parameter that specifies the desired access mask, which is the last component of the security equation. To determine whether the caller has access, the following steps are performed:
If it is an access-allowed ACE, the rights in the access mask in the ACE that were requested are granted; if all the requested access rights have been granted, the access check succeeds. If it is an access-denied ACE and any of the requested access rights are in the denied-access rights, access is denied to the object.
The behavior of both access-validation algorithms depends on the relative ordering of allow and deny ACEs. Consider an object with only two ACEs where one ACE specifies that a certain user is allowed full access to an object and the other ACE denies the user access. If the allow ACE precedes the deny ACE, the user can obtain full access to the object, but if the order is reversed, the user can not gain any access to the object.
Older Win32 functions, such as AddAccessAllowedAce, add ACEs to the end of a DACL, which usually isn't the desired behavior, so most Win32 applications prior to Windows 2000 were forced to construct DACLs manually, with deny ACEs placed at the front of the list. New Windows 2000 functions such as SetSecurityInfo and SetNamedSecurityInfo apply ACEs in the preferred order of deny ACEs preceding allow ACEs. Note that the security editor dialog boxes with which you edit permissions on NTFS files and registry keys, for example, construct security descriptors by placing all the deny ACEs at the front of the list. SetSecurityInfo and SetNamedSecurityInfo also apply ACE inheritance rules to the security descriptor on which they are applied.
Figure 8-5 shows an example access validation demonstrating the importance of ACE ordering. In the example, access is denied a user wanting to open a file even though an ACE in the object's DACL grants the access (by virtue of the user's membership in the Writers group) because the ACE denying the user access precedes the ACE granting access.
Figure 8-5 Access validation example
As we stated earlier, because it wouldn't be efficient for the security system to process the DACL every time a process uses a handle, the SRM makes this access check only when a handle is opened, not each time the handle is used. Thus, once a process successfully opens a handle, the security system can't revoke the access rights that have been granted, even if the object's DACL changes. Also keep in mind that because kernel-mode code uses pointers rather than handles to access objects, the access check isn't performed when the operating system uses objects. In other words, the Windows 2000 executive "trusts" itself in a security sense.
The fact that an object's owner is always granted write-DACL access to an object means that users can never be prevented from accessing the objects they own. If, for some reason, an object had an empty DACL (no access), the owner would still be able to open the object with write-DACL access and then apply a new DACL with the desired access permissions.
The take-ownership privilege is a similarly powerful tool for accounts, such as the Administrator account, to which it is assigned. Any object on the system is accessible using this privilege. Consider an object that is owned by another user and that explicitly denies the Administrator account all access to the object. With the take-ownership privilege, an administrator can open the object with write-owner permission and change the owner to Administrator. Then the administrator can close and reopen the object with write-DACL access and change the DACL to give the Administrator account full access to the object.