Which Timestamps Survive a File Copy, and Which Give It Away?
A Linux file has four timestamps, and a copy can only carry two of them across. The other two are set by the kernel at the moment of the copy, and no ordinary tool can fake them. That mismatch is exactly what lets you say "this file was copied" years later.
The four timestamps
Run stat on any file and you get these:
- atime: last access (read). Fuzzy in practice, because most mounts use
relatimeand skip many updates. - mtime: last content change. This is what
ls -lshows. - ctime: last inode change (content, permissions, owner, link count, rename). Not "creation time", despite the name.
- btime: birth time, when this inode came into existence. Needs a filesystem and kernel that expose it via
statx.
To see all four at once:
stat -c 'birth=%w atime=%x mtime=%y ctime=%z %n' file
What each copy method keeps
The important split is between what userspace can set and what it can't. The utimensat syscall sets atime and mtime to whatever you like. Nothing in the normal API sets ctime or btime; the kernel stamps those itself. So every tool that "preserves timestamps" is really preserving two of the four.
cp a b: new inode, so every timestamp is "now". The original mtime is gone.cp -p a b(andcp -a): atime and mtime copied. ctime and btime are "now".mvon the same filesystem: just a rename. mtime, atime and btime stay. ctime is bumped by the rename on the filesystems I would expect (ext4 does).mvacross filesystems: copy then delete, so it behaves likecp -a, with fresh ctime and btime.rsync -a: the-tinside-akeeps mtime. Atime needs-U. ctime and btime are new on the destination.
Let me prove it rather than trust my memory:
cd "$(mktemp -d)"
echo hello > a
touch -d '2020-01-01 12:00:00' a
sleep 2
cp a plain
cp -p a kept
stat -c 'birth=%w mtime=%y ctime=%z %n' a plain kept
Expect kept to show a 2020 mtime but a birth and ctime from a moment ago. plain shows "now" everywhere.
The tell: birth time after modification time
A file born after it was last modified has been copied, or had its mtime rewritten. Normal life never does that. A file is created, then written to, so mtime is at or after btime. If you see btime in March 2024 and mtime in June 2021, the content existed before this inode did.
In the demo above, even a shows it: I backdated the mtime with touch, so its birth is later than its mtime. That is exactly what a forensic examiner looks for.
ctime is the second tell. Because it cannot be set directly, it records the last time anything touched the inode. If someone backdates mtime with touch -d, ctime jumps to now. A ctime much later than mtime, on a file that has no reason to have been edited, deserves a question.
Quick detour: why can't ctime just be set?
Hang on, why does the kernel refuse? Because ctime is the integrity record. Every operation that changes an inode updates it, including the call that sets mtime. If you could set ctime freely, it would stop being evidence of anything.
The ways round it are not subtle: change the system clock before the operation, or write to the raw block device and bypass the filesystem driver. Both are noisy, and both are why careful people treat ctime as the hardest of the four to forge.
Archives and other filesystems lose precision
Precision is its own fingerprint. ext4 stores nanoseconds, so a native file has an mtime like 12:00:00.483920117. Copy it through something coarser and the fraction vanishes.
- tar in the default GNU or ustar format stores mtime in whole seconds. The pax format can carry sub-second values.
- FAT stores mtime with two-second resolution, so odd seconds get rounded.
- Classic zip stores local time in DOS format, also two-second resolution, with no timezone. Many tools add an extra field with UTC, but not all do.
So a Linux file whose mtime has exactly .000000000 nanoseconds is suspicious. Natively written files almost never land on a whole second. It suggests the file went through an archive, a coarse filesystem, or a tool that set the time from a seconds-only value.
The privacy side
Preserved timestamps leak too. If you send someone a tarball or zip made with the defaults, the mtimes tell them when you worked on each file. A zip without a timezone field also hints at your local clock. Ownership (uid and sometimes user names) rides along as well.
If you want to share files without that history, copy without -p, or normalise on purpose. GNU tar can pin everything to one value:
tar --sort=name --mtime='2000-01-01 00:00Z' \
--owner=0 --group=0 --numeric-owner \
-cf clean.tar dir/
That also makes the archive reproducible, which is a nice side effect. Note it hides mtime and ownership in the archive only; the files you extracted from still carry their own stamps, and anyone with the originals can compare.
Checking a file yourself
Three questions, in order:
- Is btime later than mtime? Then the content predates the inode.
- Is ctime far later than mtime with no obvious edit? Then someone changed the inode afterwards, possibly the mtime itself.
- Are the nanoseconds all zero on a filesystem that supports them? Then it came through something coarse.
Caveat: btime depends on the filesystem and on whether your stat shows it. If %w prints a dash, there is nothing to compare, and you are down to two timestamps and a guess.