Skip to content

ln -f removes the destination before attempting the link, so a failed link destroys it #14550

Description

@sylvestre

ln -f A B unlinks B first and only then calls link. When the link fails, B is already gone and the diagnostic talks only about the source. GNU tries the link first and unlinks only on EEXIST, so the destination survives every failed link.

$ echo keep > B
$ ln -f nonexistent B ; echo "exit=$?" ; cat B
source uutils 0.11.0 / main GNU 9.11
missing exit 1, B deleted exit 1, B keeps its contents
a directory exit 1, B deleted exit 1, B keeps its contents
dangling symlink with -L exit 1, B deleted exit 1, B keeps its contents

All three verified on main against GNU 9.11.

Cause

In the OverwriteMode::Force arm of link() (src/uu/ln/src/ln.rs:436) the destination is removed unconditionally, and everything that can make the link fail comes after it: the -L resolution at ln.rs:454 and fs::hard_link at ln.rs:463. The Err(_) if p.is_dir() arm that prints "hard link not allowed for directory" is diagnosing a failure that has already cost the destination.

GNU's ln.c documents the deliberate opposite order - POSIX 2008 lets an application fail early if it can prove continuing cannot succeed, so it links to a temporary name in the destination's directory and renames it over B. That also removes the window during a successful ln -f where B briefly does not exist at all.

Suggested fix

Attempt the link first and take the force path only on AlreadyExists; resolve -L and reject a directory source before removing anything. Better, link under a temporary name and rename over dst, as GNU does.

Affected: 0.11.0 and main. Reported privately by Hongkai Chen (SEFCOM Lab, Arizona State University); handled as a normal bug rather than an advisory.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions