【问题标题】:How to preserve ownership and permissions when doing an atomic file replace?进行原子文件替换时如何保留所有权和权限?
【发布时间】:2018-08-08 10:30:16
【问题描述】:

因此,安全、原子地替换文件内容的正常 POSIX 方法是:

  • fopen(3) 同一卷上的临时文件
  • fwrite(3)临时文件的新内容
  • fflush(3)/fsync(2) 确保内容写入磁盘
  • fclose(3)临时文件
  • rename(2)替换目标文件的临时文件

但是,在我的 Linux 系统(Ubuntu 16.04 LTS)上,这个过程的一个后果是目标文件的所有权和权限更改为临时文件的所有权和权限,默认为uid/@987654329 @ 和当前umask

我想在覆盖之前将代码添加到目标文件stat(2),并在调用rename之前将代码添加到临时文件fchmod(2),但由于EPERM,这可能会失败。

是唯一能保证文件的uid/gid匹配覆盖文件的进程的当前用户和组吗?在这种情况下是否有安全的回退方式,还是我们必然会失去原子保证?

【问题讨论】:

  • 这个答案unix.stackexchange.com/a/368641/92787 谈到了这个问题。看起来没有解决方案。您必须选择原子性或保留所有者和权限。
  • Linux 上的 ZFS 是否支持 rstchown 选项?如果支持,那将是一个解决方案。
  • 没有人会感谢您为此付出的努力。使用文件系统作为临时同步机制从来都不是一个好主意,在现代系统中是一个非常糟糕的主意。尽可能地,您希望您的代码在所有可能的环境中工作;依赖单一系统图像语义不利于这一点。
  • @mevets:你说得对,事实上我正在努力从文件系统中迁移出这种机制。但是,在替换系统到位之前,我想尽最大努力避免现有进程损坏文件,这些进程已经在不安全地操纵此文件中的共享状态,这就是最初提出这个问题的原因。

标签: c posix chmod file-rename chown


【解决方案1】:

我绝不是这方面的专家,但我认为这是不可能的。这个answer 似乎支持这一点。必须妥协。

这里有一些可能的解决方案。每个都有优点和缺点,并根据用例和场景进行加权和选择。

  • 使用原子重命名

    优势:原子操作

    缺点:可能不保留所有者/权限

  • 创建备份。 就地写入文件

    这是一些文本编辑器所做的。

    优势:将保留所有者/权限

    缺点:没有原子性。可以损坏文件。其他应用程序可能会获得该文件的“草稿”版本。

  • 设置文件夹的权限,以便可以使用原始所有者和属性创建新文件。

    优点:保留原子性和所有者/权限

    缺点:只能在某些特定情况下使用(在创建要编辑的文件时的知识,安全模型必须允许并允许这样做)。会降低安全性。

  • 创建一个守护进程/服务来负责编辑文件。此过程将具有创建具有相应所有者和权限的文件的必要权限。它将接受编辑文件的请求。

    优点:保留原子性和所有者/权限。对可以编辑的内容和方式进行更高、更精细的控制。

    缺点。仅在特定情况下可能。实现起来更复杂。可能需要部署和安装。添加攻击面。添加另一个可能的(安全)错误来源。由于添加了中间层,可能会影响性能。

【讨论】:

    【解决方案2】:

    只有保证文件的uid/gid与覆盖文件的进程的当前用户和组匹配吗?

    没有。

    在 Linux 中,具有CAP_LEASE 能力的进程可以获得文件的独占租约,这会阻止其他进程打开文件长达/proc/sys/fs/lease-break-time 秒。这意味着从技术上讲,您可以采取独占租约,替换文件内容并释放租约,以原子地修改文件(从其他进程的角度来看)。

    此外,具有CAP_CHOWN 能力的进程可以任意更改文件所有权(用户和组)。

    有没有安全的方法【处理uid或gid与当前进程不匹配的情况】,还是我们必然失去原子保证?

    考虑到通常文件可能有ACLs 和xattrs,创建一个帮助程序可能会很有用,它将所有权(包括 ACL 和扩展属性)从现有文件克隆到新文件在同一个目录下(可能有一个固定的名称模式,比如.new-################,其中#表示随机字母数字字符),如果允许真实用户(getuid()getgid()getgroups())修改原始文件。该帮助程序至少具有CAP_CHOWN 功能,并且必须考虑各种安全方面(尤其是可以利用它的方式)。 (但是,如果调用者可以覆盖内容,并在目标目录中创建新文件——调用者必须对目标目录具有写入权限,以便他们可以进行重命名/硬链接替换——,在他们以空内容代表他们应该是安全的。不过,我个人会排除 root 用户或组拥有的目标文件。)

    本质上,帮助程序的行为与mktemp 命令非常相似,只是它将现有目标文件的路径作为参数。然后将其包装到库函数中会相对简单,例如使用fork()/exec() 和管道或套接字。

    我个人通过使用基于组的访问控制来避免这个问题:每组都有专用(本地)组。文件所有者字段基本上只是一个信息字段,指示上次重新创建(或负责)该文件的用户,访问控制完全基于组。这意味着更改模式和组 id 以匹配原始文件就足够了。 (不过,复制 ACL 会更好。)如果用户是目标组的成员,他们可以执行 fchown() 更改他们拥有的任何文件的组,以及执行 fchmod() 设置模式,也是。

    【讨论】:

    • re: CAP_LEASE: 如果另一个进程在获得独占租约时打开文件或 mmap()'ed 会发生什么?
    • @user2962393:如果另一个进程打开或映射了它,您不会获得独占租约。
    • 关于这个问题的所有答案都很好,但我认为这个问题应该得到赏金,因为最后一段:如果我们确保我们有一个具有正确权限的专用本地组(在我的情况下,我们做),然后文件所有者字段变得不那么重要,并且保留它并不重要。这最终是我最终采用的解决方案,至少现在是这样。 (从长远来看,我会将这部分数据迁移到专用数据库中,以便我们可以外包锁定和原子更新的整个问题。)
    【解决方案3】:
    • 您是否需要担心被命名的文件是指向文件系统中其他位置的文件的符号链接?
    • 您是否需要担心被命名为指向 inode (st_nlink > 1) 的多个链接之一的文件。
    • 您需要担心扩展属性吗?
    • 您需要担心 ACL 吗?
    • 当前进程的用户ID和组ID是否允许该进程写入文件所在目录?
    • 是否有足够的磁盘空间可用于同一文件系统上的新旧文件?

    这些问题中的每一个都使操作变得复杂。

    符号链接相对容易处理;您只需要为实际文件建立realpath(),并在包含文件实际路径的目录中进行文件创建操作。从这里开始,它们不再是问题。

    在最简单的情况下,运行操作的用户(进程)拥有文件和存储文件的目录,可以在文件上设置组,文件没有硬链接、ACL或扩展属性,并且有足够的可用空间,然后您可以或多或少地按照问题中概述的顺序进行原子操作 - 您可以在执行原子 rename() 操作之前进行组和权限设置。

    TOCTOU 存在外部风险——检查时间、使用时间——文件属性问题。如果在确定没有链接和重命名操作之间添加链接,则链接断开。如果文件的所有者或组或权限在检查和设置新文件之间发生更改,则更改将丢失。您可以通过破坏原子性但将旧文件重命名为临时名称、将新文件重命名为原始名称以及在删除之前重新检查重命名的旧文件的属性来降低这种风险。大多数时候,这对大多数人来说可能是不必要的并发症。

    如果目标文件有多个指向它的硬链接并且必须保留这些链接,或者如果文件具有 ACL 或扩展属性并且您不希望弄清楚如何将它们复制到新文件中,那么您可以考虑以下内容:

    1. 将输出写入与目标文件位于同一目录中的命名临时文件;
    2. 将旧(目标)文件复制到与目标位于同一目录中的另一个命名临时文件;
    3. 如果在第 1 步或第 2 步中出现任何问题,请放弃操作而不造成任何损坏;
    4. 尽可能忽略信号,将新文件复制到旧文件上;
    5. 如果在第 4 步中出现任何问题,您可以从第 2 步中的额外备份中恢复;
    6. 如果第 5 步出现问题,报告文件名(新文件、原文件备份、损坏文件)供用户清理;
    7. 清理临时输出文件和备份文件。

    显然,这失去了原子性的所有伪装,但它确实保留了链接、所有者、组、权限、ACLS、扩展属性。它还需要更多空间——如果文件大小没有显着改变,它需要原始文件的 3 倍空间(正式地,它需要 size(old) + size(new) + max(size(old), size(new)) 块)。对它有利的是,即使在最终复制过程中出现问题(甚至是杂散的 SIGKILL),它也可以恢复,只要临时文件具有已知名称(可以确定名称)。

    从 SIGKILL 自动恢复可能是不可行的。 SIGSTOP 信号也可能有问题;进程停止时可能会发生很多事情。

    我希望不言而喻,必须使用所有使用的系统调用来检测和小心处理错误。

    如果目标文件系统上没有足够的空间来存放文件的所有副本,或者如果进程无法在目标目录中创建文件(即使它可以修改原始文件),你必须考虑什么替代品是。你能识别出另一个有足够空间的文件系统吗?如果旧文件和新文件的任何地方都没有足够的空间,那么您显然会遇到重大问题——任何接近原子性的东西都无法解决。

    Nominal Animalanswer 提到了 Linux 功能。由于该问题被标记为 POSIX 而不是 Linux,因此尚不清楚这些是否适用于您。但是,如果它们可以使用,那么CAP_LEASE 听起来很有用。

    • 原子性与准确性有多重要?
    • 与在 Linux(或任何其他特定 POSIX 实施)上工作相比,POSIX 合规性有多重要?

    【讨论】:

    • 这是一个很好的答案,我考虑过奖励它,但最终我给了 Nominal Animal 点头。但感谢您的回答:制作备份文件和复制数据的舞蹈是我对这个问题的第二喜欢的解决方案。还要感谢您指出限制自己使用 POSIX 可能会限制我在这里的选择;在我的具体情况下,Linux 是最重要的(尽管与 Mac OS 的兼容性很好,因为开发人员使用 Mac 来开发其中的一些软件)。
    猜你喜欢
    • 2022-01-20
    • 2013-11-16
    • 2022-01-15
    • 2012-05-17
    • 2019-01-17
    • 2013-02-22
    • 2011-10-13
    • 2019-07-06
    • 2013-10-08
    相关资源
    最近更新 更多