Why does the rename() syscall prohibit moving a directory that I can't write to a different directory?
I am trying to understand why this design decision was made with the rename() syscall in 4.2BSD. There's nothing I'm trying to solve here, just understand the rationale for the behavior itself.
4.2BSD saw the introduction of the rename() syscall for the purpose of allowing atomic renames/moves of files. From 4.3BSD-Reno/src/sys/ufs/ufs_vnops.c:
/*
* If ".." must be changed (ie the directory gets a new
* parent) then the source directory must not be in the
* directory heirarchy a开发者_StackOverflowbove the target, as this would
* orphan everything below the source directory. Also
* the user must have write permission in the source so
* as to be able to change "..". We must repeat the call
* to namei, as the parent directory is unlocked by the
* call to checkpath().
*/
if (oldparent != dp->i_number)
newparent = dp->i_number;
if (doingdirectory && newparent) {
VOP_LOCK(fndp->ni_vp);
error = ufs_access(fndp->ni_vp, VWRITE, tndp->ni_cred);
VOP_UNLOCK(fndp->ni_vp);
So clearly this check was added intentionally. My question is - why? Is this behavior supposed to be intuitive?
The effect of this is that one cannot move a directory (located in a directory that one can write) that one cannot write to another directory that one can write to atomically. You can, however, create a new directory, move the links over (assuming one has read access to the directory), and then remove one's write bit on the directory. You just can't do so atomically.
% cd /tmp
% mkdir stackoverflow-question
% cd stackoverflow-question
% mkdir directory-1
% mkdir directory-2
% mkdir directory-1/directory-i-cant-write
% echo "foo" > directory-1/directory-i-cant-write/contents
% chmod 000 directory-1/directory-i-cant-write/contents
% chmod 000 directory-1/directory-i-cant-write
% mv directory-1/directory-i-cant-write directory-2
mv: rename directory-1/directory-i-cant-write to directory-2/directory-i-cant-write: Permission denied
We now have a directory I can't write with contents I can't read that I can't move atomically. I can, however, achieve the same effect non-atomically by changing permissions, making the new directory, using ln to create the new links, and changing permissions. (Left as an exercise to the reader)
. and .. are special cased already, so I don't particularly buy that it is intuitive that if I can't write a directory I can't "change .." which is what the source suggests. Is there any reason for this besides it being the perceived correct behavior by the author of the code? Is there anything bad that can happen if we let people atomically move directories (that they can't write) between directories that they can write?
I think it's quite likely that Andrew McGregor is right. Not necessarily that UFS has to work this way, but that the implementor (Kirk McKusick) simply extended the logic of file system permissions to cover this case. Thus, if you don't have write permissions on the target directory, you shouldn't be able to change its ".." entry.
But in looking at your example, another possibility came to mind. It might be that the concern isn't a case, like you show, where a single user owns all of the directories in question, but rather a case where the directories are owned by different users. In other words, this check prevents me from moving a directory you own between parent directories that I have write permission on. Assuming, of course, that you haven't given me write permissions in your directory.
Admittedly, the scenarios where this could come up are few and far between in normal usage. But the kernel has to worry about all of the oddball use cases as well as the common ones.
An obvious counter argument is that if we wanted to worry about this scenario, then we would also want to stop people from moving files they don't own between directories they have write permissions on...
Also
the user must have write permission in the source so
as to be able to change "..".
In other words, in order for the directory to be well-formed after the move, you have to change the .. link inside it, which you do not have permission to do. So this is just a logical part of the permissions scheme, albeit not a terribly obvious one.
Many specialized programs exist that allow certain normally-privileged operations to be performed by unprivileged users under certain narrowly defined conditions. Such programs often work using the setuid flag. In the program, it checks to ensure that the special conditions are met and if so it then performs the privileged operation.
Sometimes it is necessary to reference a file by name, for example if a program is to be executed that takes a file name as an argument. If a check must be performed first, this can lead to a dangerous race condition, if it is possible for an unprivileged user to rename any portion of the path name between the time of check and the time of use. This is sometimes solved by requiring that the directory containing each specified path component have no write permission for unprivileged users, ensuring that no path component can be renamed (or unlinked and recreated) by an unprivileged user during this time to refer to something different than what was checked. If an unprivileged user could change what ".." refers to even without write permission in that directory, this would create a security hole. If this was a special exception allowed by the kernel, each program making such a check would have to check specifically for a ".." component to avoid this issue.
Additionally, updating ".." in a directory requires writing to the data blocks for that directory, and will also update the last modified time of the directory, at least on traditional Unix and BSD filesystems (I know that is not the case for Apple HFS+, where ".." is synthesized). It seems intuitive that turning off write permission for other users would prohibit any operation by them that would write to the directory or alter its last modified time.
精彩评论