避免在 Bash 中使用“mv”进行符号链接竞争条件

避免在 Bash 中使用“mv”进行符号链接竞争条件

我想使用“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,但有建议的实施可用于此。

相关内容