How Access Control Works

The details of how access control works are quite complex, but the big picture is fairly simple: Subjects act on objects. In the sentence, "Alice opens the file," Alice is the subject, or the agent of an action; opens is the action; and the file is the object. The grammar is similar in Windows 2000.

However, there are some important differences. When we say "Alice opens the file," we know that it isn't really Alice who opens the file; it's done by a program. To be more precise, the program runs as a process with threads of execution. It is actually one of those threads that opens the file. Threads are the only real agents of action on a computer. In the grammar of access control, the subject is always a thread.

In order for a thread to gain access to an object, it must identify itself to the operating system's security subsystem. A thread does not have a security identity, so it must borrow one from a security principal, such as Alice. When Alice logs on, her security identity is encapsulated in an access token that is associated with her logon session. When Alice starts an application, it runs as a process within her logon session. The application process and each of its threads of execution receive copies of Alice's access token. When one of the application's threads needs to open a file, the thread identifies itself as Alice's agent by presenting her access token. Thus responsibility for anything that the thread does to the file on Alice's behalf is charged to Alice.

Of course threads do not open files in the same way that users do. Threads are simply pieces of program code executing on the computer, and they interact with objects through one of several application programming interfaces (APIs) provided by the operating system. If a thread's job is to open a file, its code might contain the following instruction:

hfile=CreateFile(pszFile,GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);

The second argument in the call to CreateFile specifies a desired set of access rights, GENERIC_WRITE, which indicates to the operating system that the thread wants to open the file and modify it. Other APIs work in a similar fashion. The caller must signal its intentions for an object by specifying a desired level of access.

Before allowing the thread of execution to proceed, the operating system performs an access check to determine whether the security principal associated with the thread is authorized the level of access that the thread has requested. An access check compares information in the thread's access token with information in the object's security descriptor:

  • The access token contains a SID that identifies the user associated with the thread and SIDs that identify the groups whose members include the user.

  • The security descriptor contains a DACL with ACEs that specify the access rights allowed or denied to specific users or groups.

The security subsystem checks the object's DACL, looking for ACEs that apply to the user and group SIDs from the thread's access token. It steps through each ACE until it finds one that either allows or denies access to the user or one of the user's groups, or until there are no more ACEs to check. If it comes to the end of the DACL and the thread's desired access is still not explicitly allowed or denied, the security subsystem denies access to the object. Figure 12.1 illustrates this process.

Cc961988.DSCE07(en-us,TechNet.10).gif

Figure 12.1 Validating a Request for Access

The order in which ACEs are listed in a DACL is important. For example, an object's DACL might contain one ACE that allows access to a group and another ACE that denies access to a user who is a member of the group. If access-checking reaches the ACE that allows access to the user's group before it reaches the ACE that denies access to the user, the user is allowed to access the object. This is clearly not a desirable outcome.

In general, ACEs are listed in what is called canonical order, which places deny ACEs before allow ACEs. When the canonical order is used, an access check processes all ACEs that deny access before any ACE that allows access.

However, there are cases where the canonical order does not serve the purpose. For example, consider the DACL shown in Table 12.1.

Table   12.1 Non-Canonical Order

Type

Name

Permission

Allow

Administrators

Full Control

Deny

Network

Read

Allow

Users

Read

This DACL allows administrators access to the object when they log on locally and over the network but restricts non-administrative users by allowing them access only when they are logged on locally. This DACL is non-canonical, but it does serve a useful purpose, and so can other non-canonical DACLs. Thus non-canonical DACLs are not prohibited, just discouraged because their effects can be hard to understand. You cannot create a non-canonical DACL through the user interface. However, you can do it programmatically.

The canonical order just described is simplified somewhat for the purpose of this overview. It does not, for example, account for the ordering of ACEs inherited from a parent object. For the precise canonical order, see "Order of ACEs in a DACL" later in this chapter.