uutils / uutils/coreutils

mv: cross-device move drops the rest of the xattrs after one failure, and reports success

Open
#14,598 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

U - mv
Dominant language
Rust
Stars
24.1k
Forks
2k
Avg merge
1d 5h
Merged PRs (30d)
365

Description

On a cross-filesystem move, the xattr copy loop stops at the first per-attribute error. mv then discards that error, unlinks the source and exits 0 with no diagnostic — so the destination silently ends up with fewer attributes than the source, including attributes the destination filesystem fully supports.

Line references are against main at be00b4c4e.

Two separable defects

1. The loop aborts the whole list (src/uucore/src/lib/features/fsxattr.rs)

copy_xattrs (:34), copy_xattrs_fd (:56) and copy_xattrs_skip_selinux (:80) all ? out on the first failure:

for attr_name in xattr::list(&source)? {
    if let Some(value) = xattr::get(&source, &attr_name)? {
        xattr::set(&dest, &attr_name, &value)?;   // aborts the remaining attributes
    }
}

The *_ignore_unsupported wrappers only map ENOTSUP/EOPNOTSUPP, so ENOSPC, EPERM, EACCES, EDQUOT and E2BIG abort the rest of the list. GNU reports the failing attribute and continues with the others.

This is shared uucore code, so it also affects cp --preserve=xattr / cp -a.

2. mv throws the result away

src/uu/mv/src/mv.rs:1043, :1423 and :1488 are each let _ = fsxattr::copy_xattrs*(...). This is what makes the loss silent. cp does propagate the error (cp.rs:1847), so this half is mv-only.

Reproduction

Source on tmpfs (accepts a 60 KB value), destination on ext4 (~4 KB per-attribute limit), file carrying both user.big and security.capability in that order:

$ setcap cap_net_bind_service=ep src/art     # plus a 60000-byte user.big
$ uutils-mv src/art dst/art ; echo "exit: $?"
exit: 0                                       # no output at all
$ listxattr dst/art  ->  ['security.selinux']
$ getcap    dst/art  ->  (nothing)

$ gnu-mv src/art2 dst/art2 ; echo "exit: $?"
mv: setting attribute 'user.big': No space left on device
exit: 0
$ listxattr dst/art2 ->  ['security.selinux', 'security.capability']
$ getcap    dst/art2 ->  cap_net_bind_service=ep

Both remove the source. Only GNU preserves the capability, which the destination supports and which failed to be copied only because an unrelated earlier attribute did.

Control: GNU coreutils 9.11.130 (master).

Expected

Record the failure, keep going, and report each failed attribute on stderr, while keeping exit 0 for a completed move — GNU's behaviour.

Suggested test

Two attributes in a known order; force the first destination setxattr to fail; assert the second is still attempted and preserved.


Reported by Hongkai Chen (SEFCOM Lab, Arizona State University). Triaged as a correctness bug rather than a security issue — the demonstrated outcome is a deployed file with fewer privileges than intended.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with copy_xattrs, copy_xattrs_fd, and copy_xattrs_skip_selinux in src/uucore/src/lib/features/fsxattr.rs, then inspect the let _ calls at src/uu/mv/src/mv.rs:1043, :1423, and :1488. Run the suggested two-attribute reproduction or relevant mv tests. Done means failed attributes are reported, later attributes are still attempted, and mv no longer silently discards the result.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
cli, operating-systems
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
76/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.