I want to safely write to a destination file (as root, unter common Linux'es) with "echo" (or catany other Bash built-in stuff) like this

echo "foo" > /destination/dir/filename

But the problem is that /destination/dir could be accessible for normal system users, so there is the risk of symlink conditions.

I read all the "how to" for preventing TOC-TOU stuff when using C, so NOT checking for symlink/remove it and open then (the common recommendations seems to be to open() with O_NOFOLLOW).

But all of this (access to kernel open() and it's flags) is not possible via Bash (or am I wrong?).

Then I got the idea of

  • creating a tempfile with mktemp
  • chown+chmod the tempfile appropriately
  • write the contents to write to the tempfile
  • move the tempfile to the destination dir with the Bash param "-T"

So as some Bash peudo-code (without error checking at some places)


chown root:root $TEMPFILE
chmod 0600 $TEMPFILE
echo "contents" > $TEMPFILE
mv -T $TEMPFILE /destination/dir/filename

I just tested it with "/destination/dir/filename" to be a symlink to a system file, but it worked: "mv" did move the tempfile correctly to the "filename", the symlink was removed (which is was I intended), no file was overwritten.

Is there anythink I missed out with regards to security/race conditions etc.?

Thanks :-)

  • Check if /destination/dir/filename exists, remove it and then echo "foo" > /destination/dir/filename?
    – Cyrus
    Jun 18 at 20:52
  • @Cyrus you still have a race condition there - it's just that now it's between the "remove it" and the echo "foo"
    – roaima
    Jun 18 at 20:59

1 Answer 1


Let's assume that /destination/dir exists safely. (If it does not, then create the topmost directory with sufficiently restricted permissions that its subdirectories cannot be accessed or created by a non-root user. Then relax the permissions once the hierarchy is complete.)

One approach is to create the file with a temporary but unique name using mktemp inside the target directory. Then write its contents, and finally mv it to the destination.

The key point is that when using mv with a source and destination on the same filesystem the destination will be removed as part of the rename process: it's an atomic operation performed through the rename(2) system call by the kernel itself:

If newpath already exists, it will be atomically replaced, so that there is no point at which another process attempting to access newpath will find it missing. However, there will probably be a window in which both oldpath and newpath refer to the file being renamed.

A simple implementation, very similar to your own, might be this:

base='/destination/dir'                # Use '.' for current directory

tf=$(mktemp "$base/XXXXXXXXXX.tmp")    # Created with mode 600
echo "contents" >"$tf"                 # Always double-quote variables when used
if ! mv -Tf "$tf" "$base/$file"
    echo "Error writing to $base/$file" >&2
    rm -f "$tf"                        # Clean up temporary file

In a POSIX world, implementing an equivalent to mv -T is harder, and here I've relied on being able to create a temporary directory. In a real situation this would probably be best handled in a loop that repeats mkdir with different directory names until it succeeds, but here I only attempt the creation once:

base='/destination/dir'                # Use '.' for current directory

td="$base/dir.$$.tmp"                  # Must be unique
if mkdir -m700 "$td"                   # Will fail if not unique
    echo "contents" >"$td/$file"
    if ! mv -f "$td/$file" "$base/"    # Overwrite/replace $base/filename, or fail
        echo "Error writing to $base/$file" >&2
    rm -rf "$td"                       # Clean up temporary directory
    echo "Error creating temporary directory $td" >&2

The mktemp command is also not POSIX, but there are suggested implementations available for that.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.