因此,安全地,原子地替换文件内容的正常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
与当前用户和覆盖文件的进程组匹配吗?在这种情况下是否有一种安全的方法可以退回,或者我们是否一定会失去原子保证?
答案 0 :(得分:1)
我绝不是这方面的专家,但我不认为这是可能的。这answer似乎支持了这一点。必须妥协。
以下是一些可能的解决方案。每个人都有优点和缺点,并根据用例和场景加权和选择。
使用原子重命名。
优势:原子操作
缺点:可能无法保留所有者/权限
创建备份。 将文件写入
这是一些文本编辑器所做的。
优势:将保留所有者/权限
缺点:没有原子性。可以破坏文件。其他应用程序可能会获得该文件的“草稿”版本。
设置权限到文件夹,以便原始所有者&可以创建新文件。属性。
优势:原子性&所有者/权限保留
缺点:只能在某些特定场景中使用(在创建要编辑的文件时的知识,安全模型必须允许并允许这样做)。可以降低安全性。
创建一个负责编辑文件的守护程序 / service。此过程具有必要的权限,可以创建具有相应所有者和文件的文件。权限。它会接受编辑文件的请求。
优势:原子性&保留所有者/权限。对编辑内容和方式进行更高级和更精细的控制。
<强>缺点即可。仅在特定情况下可能。实施起来更复杂。可能需要部署和安装。添加攻击面。添加另一个可能的(安全)错误源。由于添加了中间层,可能会对性能产生影响。
答案 1 :(得分:1)
st_nlink > 1
)的多个链接之一的文件。这些问题中的每一个都使操作复杂化。
符号链接相对容易处理;您只需要在实际文件中建立realpath()
,并在包含文件实际路径的目录中进行文件创建操作。从现在开始,它们不是问题。
在最简单的情况下,运行操作的用户(进程)拥有文件和存储文件的目录,可以在文件上设置组,文件没有硬链接,ACL或扩展属性,以及有足够的可用空间,然后您可以使用或多或少的问题中列出的序列进行原子操作 - 在执行原子rename()
操作之前,您需要进行组和权限设置。
TOCTOU存在外部风险 - 检查时间,使用时间 - 文件属性问题。如果在确定没有链接的时间和重命名操作之间添加链接,则链接断开。如果文件的所有者或组或权限在检查和设置新文件之间发生更改,则更改将丢失。您可以通过破坏原子性来降低风险,但是将旧文件重命名为临时名称,将新文件重命名为原始名称,并在删除之前重新检查重命名的旧文件上的属性。对大多数人来说,这可能是一种不必要的并发症,大部分时间都是如此。
如果目标文件有多个硬链接并且必须保留这些链接,或者如果文件具有ACL或扩展属性,并且您不希望弄清楚如何将这些链接复制到新文件,那么您可能会考虑以下几点:
显然,这会失去原子性的所有伪装,但它确实保留了链接,所有者,组,权限,ACLS,扩展属性。它还需要更多空间 - 如果文件没有显着改变大小,它需要原始文件空间的3倍(正式地,它需要size(old) + size(new) + max(size(old), size(new))
个块)。有利的是它是可以恢复的,即使在最终拷贝期间出现问题 - 甚至是流浪的SIGKILL - 只要临时文件具有已知名称(名称可以确定)。
从SIGKILL自动恢复可能不可行。 SIGSTOP信号也可能有问题;在流程停止时可能会发生很多事情。
我希望不言而喻,必须使用所有系统调用来检测和处理错误。
如果目标文件系统上没有足够的空间用于文件的所有副本,或者如果进程无法在目标目录中创建文件(即使它可以修改原始文件),则必须考虑替代方案是。你能识别出有足够空间的另一个文件系统吗?如果旧文件和新文件都没有足够的空间,那么显然存在重大问题 - 无法解决任何接近原子性的问题。
answer Nominal Animal提及Linux功能。由于问题标记为POSIX而不是Linux,因此不清楚这些是否适用于您。但是,如果可以使用它们,那么CAP_LEASE
听起来很有用。