我想使用“echo”(或其他 Bash 内置内容)安全地写入目标文件(作为 root,在常见的 Linux 下),如下所示
echo "foo" > /destination/dir/filename
但问题是 /destination/dir 可以被普通系统用户访问,因此存在符号链接条件的风险。
我阅读了所有使用 C 时防止 TOC-TOU 内容的“如何”,所以不检查符号链接/删除它然后打开(常见的建议似乎是使用 O_NOFOLLOW 来 open() )。
但是所有这些(访问内核 open() 及其标志)都是不可能通过 Bash 实现的(或者我错了?)。
然后我想到了
- 使用 mktemp 创建临时文件
- 适当地 chown+chmod 临时文件
- 写入要写入临时文件的内容
- 使用 Bash 参数“-T”将临时文件移动到目标目录
因此,作为一些 Bash 伪代码(在某些地方没有错误检查)
TEMPFILE=$(mktemp)
chown root:root $TEMPFILE
chmod 0600 $TEMPFILE
echo "contents" > $TEMPFILE
mv -T $TEMPFILE /destination/dir/filename
我刚刚用“/destination/dir/filename”测试它作为系统文件的符号链接,但它有效:“mv”确实将临时文件正确移动到“文件名”,符号链接被删除(这是我的意图) ),没有文件被覆盖。
我是否认为我错过了安全/比赛条件等方面的问题?
谢谢 :-)
答案1
让我们假设它/destination/dir
是安全存在的。 (如果没有,则创建具有足够受限权限的最顶层目录,使其子目录无法由非 root 用户访问或创建。然后在层次结构完成后放宽权限。)
mktemp
一种方法是在目标目录内使用临时但唯一的名称创建文件。然后写入其内容,最后mv
将其写入目的地。
关键点是,当mv
在同一文件系统上使用源和目标时,目标将作为重命名过程的一部分被删除:这是rename(2)
内核本身通过系统调用执行的原子操作:
如果
newpath
已经存在,它将被自动替换,这样尝试访问的另一个进程就不会newpath
发现它丢失了。但是,可能会出现一个窗口,其中oldpath
和都newpath
引用正在重命名的文件。
一个简单的实现,与您自己的非常相似,可能是这样的:
base='/destination/dir' # Use '.' for current directory
file='filename'
tf=$(mktemp "$base/XXXXXXXXXX.tmp") # Created with mode 600
echo "contents" >"$tf" # Always double-quote variables when used
if ! mv -Tf "$tf" "$base/$file"
then
echo "Error writing to $base/$file" >&2
rm -f "$tf" # Clean up temporary file
fi
在 POSIX 世界中,实现等效的mv -T
更困难,在这里我依赖于能够创建临时目录。在实际情况下,这可能最好在一个循环中处理,该循环mkdir
使用不同的目录名称重复,直到成功,但在这里我只尝试创建一次:
base='/destination/dir' # Use '.' for current directory
file='filename'
td="$base/dir.$$.tmp" # Must be unique
if mkdir -m700 "$td" # Will fail if not unique
then
echo "contents" >"$td/$file"
if ! mv -f "$td/$file" "$base/" # Overwrite/replace $base/filename, or fail
then
echo "Error writing to $base/$file" >&2
fi
rm -rf "$td" # Clean up temporary directory
else
echo "Error creating temporary directory $td" >&2
fi
该mktemp
命令也不是 POSIX,但有建议的实施可用于此。