VYPR
Medium severity5.0NVD Advisory· Published Apr 22, 2026· Updated May 4, 2026

CVE-2026-35372

CVE-2026-35372

Description

A logic error in the ln utility of uutils coreutils allows the utility to dereference a symbolic link target even when the --no-dereference (or -n) flag is explicitly provided. The implementation previously only honored the "no-dereference" intent if the --force (overwrite) mode was also enabled. This flaw causes ln to follow a symbolic link that points to a directory and create new links inside that target directory instead of treating the symbolic link itself as the destination. In environments where a privileged user or system script uses ln -n to update a symlink, a local attacker could manipulate existing symbolic links to redirect file creation into sensitive directories, potentially leading to unauthorized file creation or system misconfiguration.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
coreutilscrates.io
< 0.8.00.8.0

Affected products

2
  • Uutils/Coreutilsreferences2 versions
    (expand)+ 1 more
    • (no CPE)
    • cpe:2.3:a:uutils:coreutils:*:*:*:*:*:rust:*:*range: <0.8.0

Patches

1
394c4b17f2f3

ln: Avoid dereferencing target if --no-dereference is passed.

https://github.com/uutils/coreutilsaweinstockMar 2, 2026via ghsa
2 files changed · +58 22
  • src/uu/ln/src/ln.rs+36 22 modified
    @@ -294,30 +294,44 @@ fn link_files_in_dir(files: &[PathBuf], target_dir: &Path, settings: &Settings)
     
         let mut all_successful = true;
         for srcpath in files {
    -        let targetpath = if settings.no_dereference
    -            && matches!(settings.overwrite, OverwriteMode::Force)
    -            && target_dir.is_symlink()
    -        {
    -            // In that case, we don't want to do link resolution
    -            // We need to clean the target
    -            if target_dir.is_file() {
    -                if let Err(e) = fs::remove_file(target_dir) {
    -                    show_error!(
    -                        "{}",
    -                        translate!("ln-error-could-not-update", "target" => target_dir.quote(), "error" => e)
    -                    );
    +        let targetpath = if settings.no_dereference && target_dir.is_symlink() {
    +            let remove_target = || -> UResult<()> {
    +                // In that case, we don't want to do link resolution
    +                // We need to clean the target
    +                if target_dir.is_file() {
    +                    if let Err(e) = fs::remove_file(target_dir) {
    +                        show_error!(
    +                            "{}",
    +                            translate!("ln-error-could-not-update", "target" => target_dir.quote(), "error" => e)
    +                        );
    +                    }
                     }
    -            }
    -            #[cfg(windows)]
    -            if target_dir.is_dir() {
    -                // Not sure why but on Windows, the symlink can be
    -                // considered as a dir
    -                // See test_ln::test_symlink_no_deref_dir
    -                if let Err(e) = fs::remove_dir(target_dir) {
    -                    show_error!(
    +                #[cfg(windows)]
    +                if target_dir.is_dir() {
    +                    // Not sure why but on Windows, the symlink can be
    +                    // considered as a dir
    +                    // See test_ln::test_symlink_no_deref_dir
    +                    if let Err(e) = fs::remove_dir(target_dir) {
    +                        show_error!(
    +                            "{}",
    +                            translate!("ln-error-could-not-update", "target" => target_dir.quote(), "error" => e)
    +                        );
    +                    }
    +                }
    +                Ok(())
    +            };
    +            match settings.overwrite {
    +                OverwriteMode::NoClobber => {}
    +                OverwriteMode::Interactive => {
    +                    if prompt_yes!(
                             "{}",
    -                        translate!("ln-error-could-not-update", "target" => target_dir.quote(), "error" => e)
    -                    );
    +                        translate!("ln-prompt-replace", "file" => target_dir.quote())
    +                    ) {
    +                        remove_target()?;
    +                    }
    +                }
    +                OverwriteMode::Force => {
    +                    remove_target()?;
                     }
                 }
                 target_dir.to_path_buf()
    
  • tests/by-util/test_ln.rs+22 0 modified
    @@ -1043,3 +1043,25 @@ fn test_ln_backup_no_path_traversal() {
         assert!(at.file_exists("b~"));
         assert!(!at.plus("b~").is_symlink());
     }
    +
    +#[test]
    +fn test_ln_no_dereference_symbolic() {
    +    let scene = TestScenario::new(util_name!());
    +    let at = &scene.fixtures;
    +    at.mkdir("a");
    +    at.symlink_dir("a", "b");
    +    at.touch("x");
    +    scene
    +        .ucmd()
    +        .args(&["-n", "x", "b"])
    +        .fails()
    +        .stderr_contains("Already exists");
    +    assert!(!at.file_exists("a/x"));
    +    #[cfg(not(target_os = "android"))]
    +    {
    +        scene.ucmd().args(&["-bn", "x", "b"]).succeeds();
    +        assert!(!at.file_exists("a/x"));
    +        assert!(at.file_exists("b"));
    +        assert!(at.is_symlink("b~"));
    +    }
    +}
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.