为什么 rename() 系统调用禁止将我无法写入的目录移动到其他目录?
我试图理解为什么这个设计决策是通过 4.2BSD 中的 rename() 系统调用做出的。我在这里不想解决任何问题,只是了解行为本身的基本原理。
4.2BSD 引入了 rename() 系统调用,其目的是允许原子重命名/移动文件。从 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);
很明显这个检查是故意添加的。我的问题是——为什么?这种行为应该是直观的吗?
这样做的效果是,无法将一个无法写入的目录(位于一个可以写入的目录中)移动到另一个可以原子写入的目录。但是,您可以创建一个新目录,将链接移过去(假设具有对该目录的读取访问权限),然后删除对该目录的写入位。你只是不能原子地这样做。
% 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
我们现在有一个目录,我无法写入我无法读取的内容,也无法自动移动。但是,我可以通过更改权限、创建新目录、使用 ln 创建新链接以及更改权限来非原子地实现相同的效果。 (留给读者作为练习)
。和 .. 已经是特殊情况了,所以我并不特别相信,如果我不能编写目录,我就无法“更改 ..”,这很直观,这就是来源所建议的。除了代码作者认为正确的行为之外,还有什么原因吗?如果我们让人们在他们可以写入的目录之间自动移动目录(他们无法写入),会发生什么不好的事情吗?
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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我认为安德鲁·麦格雷戈很可能是对的。 UFS 不一定必须以这种方式工作,但实现者 (Kirk McKusick) 只是扩展了文件系统权限的逻辑以涵盖这种情况。因此,如果您没有目标目录的写入权限,则不应更改其“..”条目。
但在看你的例子时,我想到了另一种可能性。问题可能不是像您所示的那样,单个用户拥有所有相关目录,而是目录由不同用户拥有的情况。换句话说,此检查阻止我在我具有写入权限的父目录之间移动您拥有的目录。当然,假设您没有授予我对您的目录的写入权限。
诚然,在正常使用中,出现这种情况的情况很少见。但内核必须担心所有奇怪的用例以及常见的用例。
一个明显的反驳观点是,如果我们想担心这种情况,那么我们还希望阻止人们在他们有写权限的目录之间移动他们不拥有的文件......
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.
存在许多专门的程序,允许非特权用户在某些狭义定义的条件下执行某些通常特权的操作。此类程序通常使用 setuid 标志来运行。在程序中,它会检查以确保满足特殊条件,如果满足,则执行特权操作。
有时需要按名称引用文件,例如,如果要执行将文件名作为参数的程序。如果必须首先执行检查,则如果非特权用户有可能在检查时间和使用时间之间重命名路径名的任何部分,则可能会导致危险的竞争条件。有时,可以通过要求包含每个指定路径组件的目录对非特权用户没有写权限来解决,确保在此期间非特权用户不能重命名(或取消链接和重新创建)任何路径组件来引用与实际路径不同的内容。被检查了。如果非特权用户即使没有该目录的写权限也可以更改“..”所指的内容,这将造成安全漏洞。如果这是内核允许的特殊异常,则进行此类检查的每个程序都必须专门检查“..”组件以避免此问题。
此外,更新目录中的“..”需要写入该目录的数据块,并且还将更新目录的最后修改时间,至少在传统的 Unix 和 BSD 文件系统上(我知道 Apple 的情况并非如此) HFS+,其中“..”是合成的)。似乎直观的是,关闭其他用户的写入权限将禁止他们执行任何写入目录或更改其上次修改时间的操作。
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.