The ls(1) (with -l option) and find(1) (with -ls option) utilties segment fault when operating on files with very large modification times. A recent disk corruption set a spurious bit in the mtime field of one of my files to 0x8000000630b0167 (576460753965089127) which is in year 18,266,940,962. I discovered the problem when running fsck_ffs(8) which uses ctime(3) to convert it to a readable format. Ctime cannot fit the year into its four character field, so returns ??? ??? ?? ??:??:?? ???? (typically Thu Nov 24 18:22:48 2021).
With the filesystem mounted, I used `ls -l' to see how it would report the modification time and it segment faulted. The find(1) program also segment faulted (see script below). Both these utilities call the localtime(3) function to decode the modification time. Localtime(3) returns a pointer to a struct tm (which breaks things out into its component pieces: year, month, day, hour, minute, second). The ls(1) and find(1) utilities then print out the date based on the appropriate fields in the returned tm structure.
Although not documented in the localtime(3) manual page, localtime(3) returns a NULL pointer if the passed in time translates to a year that will not fit in an "int" (which if "int" is 32-bits cannot hold the year 18,266,940,962). Since ls(1) and find(1) do not check for a NULL struct tm * return from localtime(3), they segment fault when they try to dereference it.
When localtime(3) returns NULL, the attached patches produce a date string of "bad date val". This string is chosen because it has the same number of characters (12) and white spaces (2) as the usual date string, for example "Sep 3 22:06" or "May 15 2017".
Here is a quick demonstration:
Script started on Tue Sep 6 15:41:48 2022
chez % cat setmodtime.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/file.h> #include <sys/stat.h> int main(ac, av) int ac; char *av[]; { int fd; int64_t t; struct timespec times[2]; if (ac != 3) { printf("Usage: %s file mtime\n", av[0]); exit(1); } if ((fd = open(av[1], O_RDWR | O_CREAT, 0664)) < 0) { perror(av[1]); exit(2); } t = strtol(av[2], NULL, 0); times[0].tv_sec = 0; times[0].tv_nsec = UTIME_OMIT; times[1].tv_sec = t; times[1].tv_nsec = 0; if (futimens(fd, times) < 0) { perror("futimens failed"); exit(3); } printf("Set mod time of %s to 0x%lx (%ld)\n", av[1], t, t); close(fd); exit(0); }
chez % ./setmodtime
Usage: ./setmodtime file mtime
chez % ./setmodtime foo 0x8000000630b0167
Set mod time of foo to 0x8000000630b0167 (576460753965089127)
chez % ls -l foo xxx
Segmentation fault (core dumped)
chez % find foo -ls
Segmentation fault (core dumped)
chez % ls.new -l foo xxx
-rw-r--r-- 1 root mckusick 0 bad date val foo -rwxr-xr-x 1 root mckusick 16088 Sep 6 17:34 xxx
chez % find.new . -ls
692450 8 drwxr-xr-x 2 root mckusick 512 Sep 6 17:48 . 965477 32 -rwxr-xr-x 1 root mckusick 16088 Sep 6 17:34 ./xxx 965866 0 -rw-r--r-- 1 root mckusick 0 bad date val ./foo
chez % exit
Script done on Tue Sep 6 15:42:50 2022