When a user, or rather, a process acting on behalf of the user, opens an object the OS performs an access check before access is granted. The access check matches up the permissions on the object with the type of access the user requested. If the user has permissions granting the type of access requested, the access is granted. If even one is missing, the access is denied. In other words, say that you have a user foo, who has the following access to an object bar: read, write, execute. The user executes an application that attempts to open the object. The application asks for read, write, and delete access. In this case, the access attempt would be denied because user foo does not have delete access. It makes no difference if the application actually deletes the object or not. The mere fact that the application asked for delete access is enough to deny the entire request. If the application retries and asks for read and write access only, the access attempt is allowed.
At a more detailed level, the access check works following a very specific process. When the user attempts to access an object the OS determines what level of access was requested. Let us say the level of access was Read and
Write. The OS now retrieves the DACL on the object along with the process token for the process performing the access, and proceeds to evaluate whether the DACL grants the user the requisite permissions. The access check proceeds only until all requested permissions have been granted or a deny ACE is encountered that prohibits at least one of the requested access methods. If the user is a member of Administrators and the Administrators group is granted Read and Execute and the user is granted write access, then the access attempt would succeed. However, if the user is also a member of the Users group, and the Users group is denied write permission, then the access would be rejected because the application asked for write permission.
The DACL should follow a specific order. However, the operating system will not verify that the ACL is ordered correctly during an access attempt. That can cause some mildly surprising results. The recommended order specifies that explicit (non-inherited) deny ACEs go first, following by explicit allow ACEs. Then follows inherited deny ACES, in the order they were inherited. Finally, you have the inherited allow ACEs, again in the order they were inherited. The order within the inherited ACEs is based on where they were inherited from. The closer to the object you are trying to access the ACE came from, the higher in the order it goes.
This inheritance order can cause some unexpected results if you do not understand it and pay close attention to it when setting ACLs. Let us say, for example, that you have a directory and a file with the following permissions:
C:\>icacls c:\users\jesper\downloads\test /t
c:\users\jesper\downloads\test TEST\jesper:(OI)(IO)(DENY)(S,GR,GW)
TEST\jesper:(OI)(CI)(F)
NT AUTHORITY\SYSTEM:(OI)(CI)(F)
BUILTIN\Administrators:(OI)(CI)(F)
c:\users\jesper\downloads\test\testDoc.txt TEST\jesper:(F)
TEST\jesper:(I)(DENY)(R,W)
TEST\jesper:(I)(F)
NT AUTHORITY\SYSTEM:(I)(F)
BUILTIN\Administrators:(I)(F)
In this case, you have a folder, called TEST. The SYSTEM user (also called LocalSystem) and all Administrators have full access to the folder. The user jesper has full access to the folder, but there is an inherit-only ACE that is inherited by objects, which denies jesper read (GR or Generic Read) and write (GW or Generic Write) access. Jesper also is denied Synchronize (S) access. Synchronize permits, or in this case, denies, jesper creating a synchronization handle to the object. Synchronization handles can be used, for example, to get notified when an object changes.
The deny ACE for Jesper is also inherited by the testDoc.txt file inside the test directory. You can see it there as the second ACE in the list and can tell that it is inherited (based on the I flag). However, it is not the first ACE in the list. The first ACE is the explicit full control ACE specified directly on the file. Now, what do you suppose happens when jesper tries to open the file? If you guessed "access is granted and the file opens normally" you are correct! This is counter-intuitive to many people who are used to hearing that deny ACEs take precedence over allow ACEs. They do, but explicit ACEs, whether inherited or denied, take precedence over inherited ACEs, whether inherited or denied. Therefore, the access is granted.
This result is very important because it means that putting deny ACEs on parent objects is not particularly meaningful as those ACEs may not be honored by children that have explicit ACEs on them. Ideally, you should grant permission only to those that should have it, and use deny ACEs extremely sparingly.
Another important thing to consider here is that while this ACL was ordered correctly, that may not always happen. Some third-party tools, and even builtin tools used incorrectly, may order the ACL incorrectly. In that case you may get some very strange results. If you use such tools, it is best to verify the ACL with the regular Windows ACL editor, also known as ACL UI. If the ACL is ordered incorrectly you will get an error stating this. You can also verify the ACL using icacls.exe command line tool with the /verify switch.
One very important thing to understand is the difference between an empty DACL and a NULL DACL.
-
An empty DACL is an ACL that has no ACEs in it. Remember that an ACE grants a subject permissions to an object. In other words, if a DACL is empty, nobody has been granted permissions.
-
A NULL DACL, on the other hand, is an object without a DACL. This is a very different thing from an empty DACL. An object that does not have a DACL at all has not been secured! This means the object is accessible to everyone. Many people have been told that the semantics of NULL DACLs were going to change in Windows Vista, and that they were going to be interpreted the same way as an empty DACL. This is not correct. Our testing shows that a NULL DACL is interpreted exactly the same way it always has been-as leaving the object without protection. In normal operations it is very rare to find a NULL DACL, at least on common objects. In order to create one a developer has to deliberately set one, which is usually done by misunderstanding how the APIs work. One of the more notable examples of a NULL DACL in the past several years was set on several data files by Microsoft's Age of Mythology game. In Windows XP, the ACL UI would present a NULL DACL as having Everyone Full Control, which is the effective equivalent. In Windows Vista ACL UI has been updated to actually show what really is going on, as shown in Figure 1.