【问题标题】:Why does the rename() syscall prohibit moving a directory that I can't write to a different directory?为什么 rename() 系统调用禁止将我无法写入的目录移动到其他目录?
【发布时间】:2011-02-02 23:35:11
【问题描述】:

我试图理解为什么这个设计决定是使用 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 创建新链接以及更改权限来非原子地实现相同的效果。 (留给读者作为练习)

。和 .. 已经是特殊情况了,所以我并不特别相信,如果我不能写一个目录,我就不能“改变 ..”,这就是消息来源所暗示的。除了代码作者认为正确的行为之外,还有什么理由吗?如果我们让人们在他们可以写的目录之间自动移动(他们不能写的)目录,会有什么不好的事情发生吗?

【问题讨论】:

    标签: unix filesystems system-calls


    【解决方案1】:

    Also the user must have write permission in the source so as to be able to change "..".

    换句话说,为了在移动后目录格式正确,您必须更改其中的 .. 链接,而您无权执行此操作。所以这只是权限方案的一个逻辑部分,虽然不是一个非常明显的部分。

    【讨论】:

    • 除了,当您查看代码时,您会发现它正在为此专门进行写入检查(对 ufs_access 的调用) - 所以这意味着您需要能够编写要更改的目录..所以我相信您的答案是循环推理,不是吗?
    • 每个目录都包含一个..链接,是吗?该链接位于目录内部,因此更改它需要写入权限。但是移动目录需要更改..,因此您需要对目录具有写权限才能移动它。
    • 自己看看 ufs_vnops.c 可能会有所帮助。更新 .. 的代码是 rename 系统调用的一部分,在实现 rename() 时要做的更简单的事情是在实际编写它之前不去检查写权限。请记住,我们在这里是在内核中,而不是在用户空间中——由我们决定是否允许我们做我们正在尝试的事情。如果这种行为是直观的,我认为它违反了我可以创建一个无法使用 mkdir() 编写的目录的事实
    【解决方案2】:

    存在许多专门的程序,它们允许非特权用户在某些狭义的条件下执行某些正常特权的操作。此类程序通常使用 setuid 标志工作。在程序中,它会检查以确保满足特殊条件,如果满足则执行特权操作。

    有时需要按名称引用文件,例如,如果要执行将文件名作为参数的程序。如果必须首先执行检查,如果非特权用户可以在检查时间和使用时间之间重命名路径名的任何部分,这可能会导致危险的竞争条件。这有时可以通过要求包含每个指定路径组件的目录对非特权用户没有写入权限来解决,确保在此期间非特权用户不能重命名(或取消链接和重新创建)任何路径组件来引用与被检查了。如果非特权用户即使没有对该目录的写权限也可以更改“..”所指的内容,这将造成安全漏洞。如果这是内核允许的特殊异常,则进行此类检查的每个程序都必须专门检查“..”组件以避免此问题。

    此外,更新目录中的“..”需要写入该目录的数据块,并且还会更新目录的最后修改时间,至少在传统的 Unix 和 BSD 文件系统上(我知道这不是Apple HFS+ 的情况,其中“..”是合成的)。似乎很直观,关闭其他用户的写权限将禁止他们进行任何会写入目录或更改其上次修改时间的操作。

    【讨论】:

    • 用户可以重命名一个他们不能写的目录(或删除一个目录),但是他们不能把它移动到另一个目录。
    • 重命名目录(保持相同的父目录)或删除目录不需要写入目录。将其移动到不同的目录下需要写入目录以更改..
    【解决方案3】:

    我认为 Andrew McGregor 很有可能是对的。不一定 UFS 必须 以这种方式工作,而是实现者 (Kirk McKusick) 只是简单地扩展了文件系统权限的逻辑来涵盖这种情况。因此,如果您没有目标目录的写入权限,您应该无法更改其“..”条目。

    但是在查看您的示例时,我想到了另一种可能性。可能不像您所展示的那样,关注的不是单个用户拥有所有相关目录的情况,而是目录由不同用户拥有的情况。换句话说,这个检查阻止我在我有写权限的父目录之间移动你拥有的目录。当然,假设您没有授予我在您的目录中的写入权限。

    诚然,在正常使用中可能出现这种情况的情况很少见。但是内核必须担心所有奇怪的用例以及常见的用例。

    一个明显的反驳论点是,如果我们想担心这种情况,那么我们还想阻止人们在他们拥有写权限的目录之间移动他们不拥有的文件......

    【讨论】:

      猜你喜欢
      • 2017-12-02
      • 2017-10-09
      • 1970-01-01
      • 1970-01-01
      • 2019-06-09
      • 1970-01-01
      • 1970-01-01
      • 2014-09-18
      • 1970-01-01
      相关资源
      最近更新 更多