File timestamps are crucial forensics artifacts when investigating a machine during a security incident, they are regularly modified and can provide both primitive information (when the file was last modified) and inferred information (when the file was probably moved there from another file system).

The “Windows Time Rules” from SANS [1] is an excellent resource on which MACB timestamp is updated by each common operation (file creation, file copy…) on Windows, and it did not have an equivalent in the Unix world.

POSIX (Portable Operating System Interface) is a set of specifications for Unix-like OSes. It defines interfaces (system calls) and utilities behavior, including MAC updates, for consistency and compatibility across the Unix world.

In this post we decribe how timestamps updates are specified within POSIX, how they are implemented on Linux, OpenBSD and FreeBSD, provide code to find this out automatically and tables with MAC(B) updates for each OS.

POSIX and MAC(B) timestamps

This study is based on the POSIX “draft” C181 from 2018 (“Base Specifications, Issue 7, 2018 Edition”) [2].

POSIX specifies MAC timestamps:

Each file has three distinct associated timestamps: the time of last data access, the time of last data modification, and the time the file status last changed. These values are returned in the file characteristics structure struct stat, as described in <sys/stat.h>.

Data access (A) is when the file data is read, data modification (M) when the file data is modified, and file status changed (C) when the file metadata is changed (chown, chmod, new hardlink updating the link count…).

The <sys/stat.h> header shall define the stat structure, which shall include at least the following members:

struct timespec st_atim - Last data access timestamp.

struct timespec st_mtim - Last data modification timestamp.

struct timespec st_ctim - Last file status change timestamp.

The <time.h> header shall declare the timespec structure, which shall include at least the following members:

time_t tv_sec Seconds.

long tv_nsec Nanoseconds.

The fourth timestamp (called “B” for birth, or “cr” for creation) used by some file systems to store the date of file creation is not at all discussed in POSIX. There is SANS series describing implementation in EXT4 for example [3].

General behavior

POSIX specifies some general update rules, for instance:

When a file that does not exist is created […] the last data access, last data modification, and last file status change timestamps of the file shall be updated.

Thus a new file shall get updated MAC.

Interfaces and utilities

POSIX specifies both interfaces (system calls) and utilities (commands such as mv). For instance for a file move such as mv source_file target_file performed locally (source_file and target_file on same filesystem):

Utility — mv [−if] source_file target_file

The mv utility shall perform actions equivalent to the rename( ) function […] with the following arguments […]: the source_file operand is used as the old argument […], the destination path is used as the new argument.

Interface — rename(const char *old, const char *new)

Upon successful completion, rename( ) shall mark for update the last data modification and last file status change timestamps of the parent directory of each file. Some implementations mark for update the last file status change timestamp of renamed files and some do not.

So the POSIX way to move a file locally implies updating MC of the parent directory of both source_file and target_file, and possibly (depending on the implementation) update C of the moved file. What happens to the other timestamps is not described: they shall not be modified.

POSIX (Non-)Compliance

In addition to some freedom left to implementations in POSIX specification, the OSes tested do not attempt to be strictly POSIX-compliant.

For instance on Linux, the LSB (Linux Standard Base) requires POSIX compliance but it is not followed by a lot of distribution and very few are certified: https://en.wikipedia.org/wiki/Linux_Standard_Base#Reception

Cases of non-compliance with some MAC updates from POSIX are not bugs but achitecture and implementation choices, for instance for performance reasons.

B Timestamp

Though not specified by POSIX, Linux on EXT4 and FreeBSD on UFS2 store the date of creation (B).

The B field exists and can be read in OpenBSD on FFS1 but it is never filled and is always 0.

Impact of mount options

Mount options exist to improve performance by restricting or disabling the update of some timestamps.

Linux

(default) — MCB updates are all performed

relatime (default) — A updates are performed if A was earlier or equal to M or C, or at least 1 day old

noatime — A updates are never performed

nodiratime — A updates are never performed for directories

strictatime — A updates are always performed

Since Linux 2.6.30 (released in 2009), the default option is relatime, this means that reading twice the same old file on the same day will update A the first time but not update it again.

The 1 day delay is hardcoded in kernel code: https://github.com/torvalds/linux/blob/master/fs/inode.c#L1654

OpenBSD

(default) — MAC updates are all performed

noatime — A updates are performed only if M or C is also marked for update

With the noatime option, modifying a file with a simple text editor would first open it for read (A) then for write (MC), thus A will not be marked for update at the same time as M or C and will not be updated. Thus A will mostly be updated at file creation.

FreeBSD

(default) — MACB updates are all performed

noatime — A is never updated

Timestamp Resolution

POSIX specifies a minimum resolution of 1s:

The resolution of timestamps of files in a file system is implementation-defined, but shall be no coarser than one-second resolution.

Linux — nanosecond:

$ stat file

Access: 2019–05–20 09:03:37.574284871

OpenBSD — nanosecond:

$ stat -f “Access: %Fa” file

Access: 1558333479.574284871

FreeBSD — microsecond

$ stat -f “Access: %Fa” file

Access: 1558333479.574284000

Both Linux on EXT4 and OpenBSD on FFS store the timestamps to the maximum resolution of the file system, the nanosecond.

FreeBSD on UFS2 by default stores the timestamps to the microsecond resolution even though the file system supports nanosecond resolution.

This can be changed to nanosecond precision with:

# sysctl vfs.timestamp_precision=3

Default is 2, as explained in man vfs_timestamp: