views:

116

answers:

3

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 above 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?

A: 

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.

Andrew McGregor
Except, when you look at the code, you see it's doing the write check specifically for this reason (the call to ufs_access) - so this is the code that means you need to be able to write a directory to change .. so I believe your answer is circular reasoning, no?
Daniel Papasian
Every directory contains a .. link, yes? And that link is INSIDE the directory, so changing it requires write permission. But moving a directory requires .. to be changed, and therefore you need write permission to a directory in order to move it.
Andrew McGregor
It might help to see ufs_vnops.c yourself and look. The code to update .. is part of the rename syscall, and the simpler thing to do when implementing rename() would have been to not bother checking write permission before actually writing it. Remember, we're in the kernel here, not userspace - it is up to us to decide whether or not we're allowed to do what we're trying.If this behavior is intuitive, I think it's violated by the fact I can create a directory that I can't write using mkdir()
Daniel Papasian
A: 

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.

mark4o
A user can rename a directory (or delete a directory) that they can't write, however, they just can't move it to a different directory.
Daniel Papasian
Renaming a directory (keeping the same parent) or removing the directory do not require writing to the directory. Moving it under a different directory requires writing to the directory to change `..`.
mark4o
+1  A: 

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...

Keith Smith