如何在替换原子文件时保留所有权和权限?

问题描述 投票:5回答:3

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

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

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

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

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

c posix chmod file-rename chown
3个回答
3
投票

唯一的解决方案是确保文件的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()来设置模式。


1
投票

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

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

  • 使用原子重命名。 优点:原子操作 缺点:可能不保留所有者/权限
  • 创建备份。写文件到位 这是一些文本编辑器所做的。 优点:将保留所有者/权限 缺点:没有原子性。可以破坏文件。其他应用程序可能会获得该文件的“草稿”版本。
  • 设置文件夹的权限,以便可以使用原始所有者和属性创建新文件。 优点:保留原子性和所有者/权限 缺点:只能在某些特定场景中使用(在创建要编辑的文件时的知识,安全模型必须允许并允许这样做)。可以降低安全性。
  • 创建负责编辑文件的守护程序/服务。此过程具有创建具有相应所有者和权限的文件的必要权限。它会接受编辑文件的请求。 优点:保留原子性和所有者/权限。对编辑内容和方式进行更高级和更精细的控制。 缺点。仅在特定情况下可能。实施起来更复杂。可能需要部署和安装。添加攻击面。添加另一个可能的(安全)错误源。由于添加了中间层而可能对性能产生影响。

1
投票
  • 您是否必须担心文件系统中其他位置的文件的符号链接?
  • 您是否必须担心被命名为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信号也可能有问题;过程停止时可能会发生很多事情。

我希望不言而喻,必须在使用所有系统调用的情况下仔细检测和处理错误。

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

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

  • 原子性与准确性有多关键?
  • POSIX合规性与在Linux(或任何其他特定POSIX实现)上工作有多重要?
© www.soinside.com 2019 - 2024. All rights reserved.