因此,正常的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
与当前用户和进程组覆盖文件相匹配吗?在这种情况下是否有一种安全的方法可以退回,还是我们必然会失去原子保证?
唯一的解决方案是确保文件的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()
来设置模式。
我绝不是这方面的专家,但我认为这不可行。这个answer似乎支持这一点。必须妥协。
这是一些可能的解决方案。每个人都有优点和缺点,并根据用例和场景加权和选择。
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
听起来很有用。