From 9f86bf1dc1a3d3ae9929975a6bff69711a405b1a Mon Sep 17 00:00:00 2001 From: sneak Date: Mon, 2 Feb 2026 13:48:24 -0800 Subject: [PATCH] Detect file modifications during checksum calculation (TOCTOU fix) - Check file mtime before and after hashing; error if they differ - Store file's mtime as sumtime instead of wall-clock time - Use fresh stat for BytesProcessed to get accurate count This fixes a TOCTOU race where a file could be modified between hashing and writing the xattr, resulting in a stale checksum. It also makes sum update comparisons semantically correct by comparing file mtime against stored mtime rather than wall-clock time. --- attrsum.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/attrsum.go b/attrsum.go index 85ed653..1ad9e1f 100644 --- a/attrsum.go +++ b/attrsum.go @@ -238,10 +238,23 @@ func ProcessSumUpdate(dir string, stats *Stats, bar *progressbar.ProgressBar) er } func writeChecksumAndTime(path string, info os.FileInfo, stats *Stats) error { + // Record mtime before hashing to detect modifications during hash + mtimeBefore := info.ModTime() + hash, err := fileMultihash(path) if err != nil { return err } + + // Check if file was modified during hashing + infoAfter, err := os.Lstat(path) + if err != nil { + return fmt.Errorf("stat after hash: %w", err) + } + if !infoAfter.ModTime().Equal(mtimeBefore) { + return fmt.Errorf("%s: file modified during checksum calculation", path) + } + if err := xattr.Set(path, checksumKey, hash); err != nil { return fmt.Errorf("set checksum attr: %w", err) } @@ -249,7 +262,9 @@ func writeChecksumAndTime(path string, info os.FileInfo, stats *Stats) error { fmt.Printf("%s %s written\n", path, hash) } - ts := time.Now().UTC().Format(time.RFC3339Nano) + // Store the file's mtime as sumtime (not wall-clock time) + // This makes update comparisons semantically correct + ts := mtimeBefore.UTC().Format(time.RFC3339Nano) if err := xattr.Set(path, sumTimeKey, []byte(ts)); err != nil { return fmt.Errorf("set sumtime attr: %w", err) } @@ -258,7 +273,7 @@ func writeChecksumAndTime(path string, info os.FileInfo, stats *Stats) error { } atomic.AddInt64(&stats.FilesProcessed, 1) - atomic.AddInt64(&stats.BytesProcessed, info.Size()) + atomic.AddInt64(&stats.BytesProcessed, infoAfter.Size()) return nil }