Moving a file does not recalculate inherited permissions
Inherited permissions on an object are established when it is created. Once the object has been created, you can change the permissions of the parent and it won’t have any effect unless you explicitly ask for the inheritable properties to be re-propagated to the child objects. (You may recall that the
CREATOR_OWNER SID works in a similar way.) This rule applies to files, though that can lead to behavior that some people might consider non-intuitive.
Files are strange from a security perspective because a single file can have multiple parent folders, thanks to hard links. Suppose you have two directories
DirA has an inheritable permission that gives
UserA full access and denies access to
DirB does the same with the roles of the two users reversed. It’s easy to tell who the parent of a file is when it is created, since a file is created by giving a path, and you can extract the parent from the path. For example, if the file
DirA\File is created, it will naturally inherit permissions from
One the file is created, though, that’s the end of it. Inheritable permissions don’t have any effect any more.
This simple rule wouldn’t normally cause any problems, except that files have properties unusual among most named objects: They can go by multiple names (thanks to hard links) and can change their names (via renaming). The apparently counter-intuitive behavior stems from confusing the object with its name.
For example, suppose we create a hard link to
DirA\File under the name
DirB\File. What effect does this have on the file’s ACLs? Answer: None whatsoever. The inheritable ACLs on
DirB aren’t applied to the file since the file is not being created. Besides, you couldn’t apply the inheritable ACLs from
DirB even if you wanted to because it would create a paradox: The file resides in both
DirB, but each of those directories contains contradictory inheritable permissions. What would the result be if you somehow managed to apply both of them?
All right, then. We have no choice but to decide that a file’s ACLs don’t change when you create a hard link. Now delete the original link
DirA\File, leaving just
DirB\File. Does this change the ACLs now? If you believe that it should, then you’re saying that inheritable ACLs can take effect even when nothing got created! After all, we didn’t create a hard link; we deleted one.
Okay, maybe you concede that deleting a hard link shouldn’t affect the ACL. But what did we just do by creating a hard link and then deleting another one? The net effect is that we moved the file from
DirB\File. Which brings us to our third example: Renaming/moving a file does not change its ACL.
We’ve just rediscovered the simple rule that inheritable ACLs take effect only when a file is created. Nothing special happens when a new hard link is created or when the file is moved.
Of course, that simple rule holds only when you look at the file system at a low level. Layers built on top of the low-level file system can end up complicating our simple rule.
When you move a file across volumes with the
MOVEFILE_COPY_ALLOWED flag, you’re saying that “move the file if possible; if not, then convert it to a copy/delete operation”. The copy operation creates a new file, which means that inheritable properties on the destination folder do take effect. But only if the file motion crosses volumes. If you’re moving the file within the same volume, then the ACL remains unchanged. How confusing. When you pass the
MOVEFILE_COPY_ALLOWED flag, you lose control of the ACL. (You actually lose control of much more than just the ACL. Since the file is being copied, none of the attributes from the original file are kept on the copy. The copy inherits its encryption and compression status from the new parent directory. The copy also gets a new owner, which has follow-on consequences for things like disk quota.)
Another layer built on top of the low-level file system operations is the shell’s copy engine. If you use
SHFileOperation to move a file and pass the
FOF_NOCOPYSECURITYATTRIBUTES flag, then it will not preserve the original ACLs on the moved files but will rather recalculate them from the destination’s inheritable properties. (If you want to do the same thing in your own code, you can call the
SetNamedSecurityInfo function, specifying that you want an empty, unprotected DACL.) In Windows XP, the shell decides whether or not to use this “naive mode” ACL management based on the following algorithm:
- If the
MoveSecurityAttributespolicy is set, then the policy determines how ACLs are handled when files are moved. (ACLs are reset if set to “0” and are preserved if set to “1”.)
- Otherwise, the “Use simple file sharing” setting controls how ACLs are handled when files are moved. (ACLs are reset if simple file sharing is enabled and are preserved if simple file sharing is disabled.)