Skip to content

ext4 and XFS Timestamps, Inodes and Deleted Files

How to read MAC and birth timestamps on ext4 and XFS, spot timestomping through ctime, and understand the real limits of deleted file recovery on Linux.

Published on 7 min read

File timestamps are the backbone of most Linux timelines, but they are also the artifact most often misread. This guide covers what each timestamp means on ext4 and XFS, how to read the ones ls never shows, how timestomping betrays itself, and why deleted file recovery on Linux is much harder than on Windows filesystems. All commands assume you are working on a read-only image or loop device, as described in acquiring Linux evidence.

Inodes and the four timestamps

Every file on ext4 and XFS is described by an inode: a fixed-size structure holding ownership, mode, size, block or extent pointers and timestamps. The file name lives separately in a directory entry that points to the inode number. That split matters: renaming or moving a file within the same filesystem changes the directory, not the file's data, and the inode number survives.

TimestampNameUpdated whenSettable with touch?
atimeAccessFile content is read (subject to mount options)Yes
mtimeModifyFile content is written or truncatedYes
ctimeChangeInode metadata changes: permissions, owner, link count, rename, and any content changeNo
crtime / btimeBirthInode is allocated for this fileNo

ctime is not "creation time". It is inode change time, and it is updated by almost everything, including a chmod, a chown, a hard link, or the act of timestomping itself. Birth time is called crtime in ext4 on-disk structures and debugfs output, btime in the statx() API, and Birth in stat output. The ext4 crtime glossary entry covers the on-disk detail.

Resolution and storage

ext4 with 256-byte inodes (the default for years) stores nanosecond precision and extra epoch bits that push the range past 2038. Old filesystems created with 128-byte inodes have second resolution and no crtime at all. XFS v5 (the crc=1 format, default since xfsprogs 3.2.3) stores nanoseconds and a creation time; the bigtime feature extends the range beyond 2038. All values are UTC seconds since the epoch; the time zone only appears when a tool renders them.

atime is unreliable by design

Since kernel 2.6.30 the default mount option is relatime: atime is updated only if the previous atime is older than mtime or ctime, or older than 24 hours. Systems mounted with noatime never update it. Treat atime as "accessed at least once around this time", never as a record of each read. Check /etc/fstab and the mount options recorded in your triage data before building conclusions on it.

Reading timestamps

stat and statx

On the analysis box, stat uses statx() and shows birth time when the filesystem, kernel and coreutils all support it:

TZ=UTC stat /mnt/evidence/home/deploy/.cache/kworkerd
  File: /mnt/evidence/home/deploy/.cache/kworkerd
  Size: 1843200    Blocks: 3600       IO Block: 4096   regular file
Device: 7,0        Inode: 1311027     Links: 1
Access: (0755/-rwxr-xr-x)  Uid: ( 1001/  deploy)   Gid: ( 1001/  deploy)
Access: 2026-09-14 02:11:47.529301877 +0000
Modify: 2023-03-02 10:00:00.000000000 +0000
Change: 2026-09-14 02:11:52.104488213 +0000
 Birth: 2026-09-14 02:11:47.418862130 +0000

This fictional example from host web-prod-03 is a textbook timestomp: mtime claims 2023 with a suspiciously round .000000000 fraction, while birth and ctime both sit in September 2026. For scripting, stat -c '%i %W %X %Y %Z %n' prints the inode followed by birth, access, modify and change as epoch seconds.

debugfs on ext4

debugfs reads the inode directly and shows crtime even where stat cannot. It opens filesystems read-only unless you pass -w:

debugfs -R 'stat <1311027>' /dev/loop0
Inode: 1311027   Type: regular    Mode:  0755   Flags: 0x80000
...
 ctime: 0x6aa757e8:18e97454 -- Mon Sep 14 02:11:52 2026
 atime: 0x6aa757e3:7e3205d4 -- Mon Sep 14 02:11:47 2026
 mtime: 0x640073a0:00000000 -- Thu Mar  2 10:00:00 2023
crtime: 0x6aa757e3:63dd50c8 -- Mon Sep 14 02:11:47 2026
Size of extra inode fields: 32
EXTENTS:
(0-449):4229120-4229569

The hex pairs are seconds and the "extra" field holding nanoseconds plus epoch bits. debugfs renders human dates in the local time zone, so run it with TZ=UTC. A deleted inode also shows a dtime value, the deletion time, which is valuable on its own.

xfs_db on XFS

XFS has no debugfs equivalent, but xfs_db in read-only mode prints the full inode, including the v3 creation time:

xfs_db -r -c 'inode 133' -c 'print' /dev/loop1 | grep -E 'time|crtime'

Get the inode number first with ls -i on the mounted image. XFS mounted with ro,norecovery will not replay its log, which keeps the image untouched but may show a slightly stale state if the system crashed.

Timestomping and what ctime reveals

touch -d or touch -r reference_file lets an attacker make a dropped binary look as old as its neighbours. The weakness is that the kernel sets ctime to the current clock on that very operation, and nothing in userland can set it back. Indicators worth checking:

  • mtime older than crtime. Content cannot be modified before the inode existed. Legitimate causes exist (cp -p, tar and rsync -t preserve mtime on extraction), so look at the directory context before concluding.
  • Zero nanoseconds. Timestamps set with touch -d at second granularity end in .000000000, while real writes almost never do. Archive extraction produces the same pattern, so it is an indicator, not proof.
  • ctime clusters. Several files in /usr/bin with years-old mtime but a ctime from the incident window stand out, especially when compared to package manager logs.
  • Inode numbers. Files created together tend to get nearby inode numbers. A "2019" binary whose inode sits among files created last week deserves a look.

An attacker with root can still defeat this by changing the system clock before touching the file (visible in logs and journal time jumps) or by editing the unmounted filesystem with debugfs. Both are uncommon on live servers and leave their own traces, which is why ctime and crtime remain strong anchors in a super timeline.

The ext4 journal (jbd2)

ext4 journals metadata through jbd2, stored by default in the hidden inode 8. In the default data=ordered mode, only metadata blocks are journaled, but those blocks include whole inode table blocks. The journal is circular and small, so it holds a short, recent history of earlier inode versions: previous timestamps, previous sizes and, crucially, extent trees from before a file was deleted. debugfs can dump it:

debugfs -R 'logdump' /dev/loop0 | less

Mount ext4 images with ro,noload so the journal is not replayed. Replaying it would both modify the image and consume the historic copies you might need.

Deleted files: realistic expectations

On FAT or NTFS, a deleted file's metadata often still points at its data. ext4 is different: when an extent-mapped file is deleted, the kernel zeroes the extent information in the inode and sets dtime. The inode keeps mode, owner, size (often reset to zero) and timestamps, but not where the data lived. Your options:

  1. Journal-based recovery. Tools like extundelete and ext4magic search jbd2 for an older copy of the inode that still contains extents. This works only for recent deletions still inside the journal window, and both tools are old and poorly maintained; always work on a copy and validate output by hash or file type.
  2. Carving. Signature-based carving of unallocated blocks recovers contiguous files well and fragmented ones poorly. File names are lost.
  3. Directory entries. ext4 directory blocks may still hold the deleted name and inode number, which proves a file existed even when content is gone.

XFS is similar or worse: freed inodes lose their extent mapping, and practical recovery relies on carving. Btrfs is copy-on-write, so older versions of data and metadata may survive in snapshots or unreferenced tree blocks, but mainstream forensic suites support it poorly; Btrfs also stores a birth time (otime) that stat reports as Birth.

On a live system, remember the cheapest recovery path: if a process still holds the deleted file open, it can be copied from /proc, as shown in live response with /proc.

The Sleuth Kit workflow

The Sleuth Kit reads ext4 directly from an image, including deleted entries. Find the partition offset with mmls, then:

fls -r -d -p -o 2048 disk.raw                 # deleted entries with full paths
istat -o 2048 disk.raw 1311027                 # inode details, all timestamps
icat -o 2048 disk.raw 1311027 > kworkerd.bin   # content, if blocks are still mapped
fls -r -m / -o 2048 disk.raw > body.txt        # body file for timelining
mactime -b body.txt -d -z UTC > timeline.csv

Deleted ext4 entries in fls output typically show an inode whose istat lists no blocks, which is the extent zeroing described above. TSK's XFS and Btrfs coverage varies by release, so check your version and fall back to native tools when needed.

For a quick look at an ext4 or XFS image without installing anything, Disk Image Parser opens E01, raw, VMDK and VHD files in the browser and shows inodes, extents, timestamps (including XFS crtime and bigtime), unallocated space and slack. It does not list deleted XFS files, whose extent maps are wiped on deletion, so recover their content from unallocated space or with a carving tool.

Key takeaways

  • ctime is change time, not creation time, and it cannot be set with touch; it is the main timestomping tell.
  • Birth time exists on ext4 (256-byte inodes), XFS v5 and Btrfs; read it with stat, debugfs or xfs_db.
  • atime is throttled by relatime or disabled by noatime, so never treat it as a per-read log.
  • All on-disk timestamps are UTC; force TZ=UTC in every tool.
  • ext4 zeroes extents on delete; recovery depends on the jbd2 journal, carving, or an open file handle on the live host.
  • Mount images ro,noload (ext4) or ro,norecovery (XFS) to avoid destroying journal evidence.