Summary
RenameFile checks whether the destination exists with os.Stat, and if not, calls os.Rename:
if _, err := os.Stat(newPath); errors.Is(err, os.ErrNotExist) {
if err := os.Rename(f.Path, newPath); err != nil { ... }
}
This is a TOCTOU window: between the Stat and the Rename, another process (or another goroutine inside find-replace itself, since the walker is concurrent) can create newPath. os.Rename on Linux silently overwrites the destination — the safety check is bypassed and data is destroyed.
Impact (Security: Medium)
- Concurrent traversal of two siblings whose names collapse to the same
newPath after replacement (e.g., Foo and foo with find=Foo replace=foo) will race; one will overwrite the other.
- External process writing in the same directory can race the check and have its file silently clobbered.
- Loss of safety guarantee documented in the source comment ("renames a file if the destination file name does not already exist").
Suggested Fix
Use os.Link(src, dst) followed by os.Remove(src). os.Link fails atomically with EEXIST if dst exists, eliminating the TOCTOU. Fall back to Rename only on EXDEV/EPERM (e.g., filesystems without hardlink support), and document the weaker guarantee.
Alternatively, on Linux, use unix.Renameat2(..., RENAME_NOREPLACE) directly.
Files
find_replace.go:98-112 (RenameFile)
Summary
RenameFilechecks whether the destination exists withos.Stat, and if not, callsos.Rename:This is a TOCTOU window: between the
Statand theRename, another process (or another goroutine insidefind-replaceitself, since the walker is concurrent) can createnewPath.os.Renameon Linux silently overwrites the destination — the safety check is bypassed and data is destroyed.Impact (Security: Medium)
newPathafter replacement (e.g.,Fooandfoowithfind=Foo replace=foo) will race; one will overwrite the other.Suggested Fix
Use
os.Link(src, dst)followed byos.Remove(src).os.Linkfails atomically withEEXISTifdstexists, eliminating the TOCTOU. Fall back toRenameonly onEXDEV/EPERM(e.g., filesystems without hardlink support), and document the weaker guarantee.Alternatively, on Linux, use
unix.Renameat2(..., RENAME_NOREPLACE)directly.Files
find_replace.go:98-112(RenameFile)