Page MenuHomeFreeBSD

Handle out-of-bounds file timestamps in ls(1) and find(1)
ClosedPublic

Authored by mckusick on Sep 7 2022, 1:00 AM.
Tags
None
Referenced Files
Unknown Object (File)
Fri, Apr 26, 6:38 AM
Unknown Object (File)
Fri, Apr 26, 6:38 AM
Unknown Object (File)
Thu, Apr 25, 11:41 PM
Unknown Object (File)
Thu, Apr 25, 11:40 PM
Unknown Object (File)
Thu, Apr 25, 10:55 PM
Unknown Object (File)
Apr 6 2024, 11:06 AM
Unknown Object (File)
Apr 5 2024, 1:51 AM
Unknown Object (File)
Mar 27 2024, 9:09 PM
Subscribers

Details

Summary

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

Test Plan

Have Peter Holm run his usual battery of filesystem tests.

Diff Detail

Repository
rG FreeBSD src repository
Lint
Lint Not Applicable
Unit
Tests Not Applicable

Event Timeline

bin/ls/print.c
457

ls_strftime() should return nul-terminated string, strncpy() does not provide this guarantee. Look at the only use of ls_strfrime(), where it immediately followed by puts(). I believe strlcpy() use is appropriate there.

Also, the return value of ls_strftime() is ignored, it might make sense to change it return type to void in preparational commit.

usr.bin/find/ls.c
110

Same comment about strncpy/strlcpy.

Further investigation shows that there are over 100 utilities and libraries that use localtime(3) and none of the ones I have looked at are prepared for a NULL return. They all need to be fixed along the lines of ls(1) and find(1) or there has to be some defined `bad date' value that is returned in the tm structure. Here is a list of places that call localtime(3):

src/bin/date/date.c
src/bin/pax/sel_subs.c
src/bin/ps/print.c
src/cddl/contrib/opensolaris/cmd/stat/common/timestamp.c
src/contrib/bmake/var.c
src/contrib/bsddialog/examples_library/timebox.c
src/contrib/dialog/timebox.c
src/contrib/diff/src/context.c
src/contrib/dma/util.c
src/contrib/elftoolchain/ar/read.c
src/contrib/file/src/localtime_r.c
src/contrib/googletest/googletest/src/gtest-internal-inl.h
src/contrib/ldns/compat/localtime_r.c
src/contrib/libarchive/cpio/cpio.c
src/contrib/libarchive/libarchive/archive_write_set_format_zip.c
src/contrib/libarchive/tar/util.c
src/contrib/libarchive/test_utils/test_main.c
src/contrib/lua/src/loslib.c
src/contrib/mandoc/mandoc.c
src/contrib/netbsd-tests/lib/libc/time/t_mktime.c
src/contrib/ntp/lib/isc/unix/time.c
src/contrib/ntp/libntp/prettydate.c
src/contrib/ntp/ntpd/refclock_jjy.c
src/contrib/ntp/parseutil/dcfd.c
src/contrib/ntp/sntp/libopts/makeshell.c
src/contrib/one-true-awk/run.c
src/contrib/opie/opiesu.c
src/contrib/sendmail/src/arpadate.c
src/contrib/sqlite3/sqlite3.c
src/contrib/tcpdump/tcpdump.c
src/contrib/tcsh/tc.sched.c
src/contrib/telnet/telnetd/utility.c
src/contrib/tnftp/src/util.c
src/contrib/tzcode/stdtime/tzfile.h
src/contrib/tzcode/zic/zic.c
src/contrib/unbound/compat/gmtime_r.c
src/contrib/wpa/src/utils/os_win32.c
src/crypto/heimdal/appl/telnet/telnetd/utility.c
src/crypto/heimdal/lib/kadm5/iprop-log.c
src/crypto/openssh/krl.c
src/lib/libbe/be.c
src/lib/libc/gen/timezone.c
src/lib/libutil/login_ok.c
src/libexec/bootpd/tzone.c
src/libexec/ftpd/popen.c
src/libexec/getty/main.c
src/libexec/rbootd/utils.c
src/libexec/talkd/announce.c
src/libexec/tftpd/tftpd.c
src/sbin/adjkerntz/adjkerntz.c
src/sbin/bectl/bectl_list.c
src/sbin/camcontrol/timestamp.c
src/sbin/dump/optr.c
src/sbin/ipf/ipfstat/ipfstat.c
src/sbin/ipf/ipmon/ipmon.c
src/sbin/newfs_msdos/mkfs_msdos.c
src/sbin/setkey/setkey.c
src/sbin/shutdown/shutdown.c
src/sys/contrib/openzfs/cmd/zed/zed_event.c
src/sys/dev/ips/ips_commands.c
src/sys/netinet/tcp_stacks/rack.c
src/tools/test/stress2/testcases/run/run.c
src/tools/tools/net80211/stumbler/stumbler.c
src/tools/tools/net80211/wesside/wesside/wesside.c
src/usr.bin/ar/read.c
src/usr.bin/at/at.c
src/usr.bin/chat/chat.c
src/usr.bin/chpass/util.c
src/usr.bin/finger/lprint.c
src/usr.bin/grdc/grdc.c
src/usr.bin/ipcs/ipcs.c
src/usr.bin/kdump/kdump.c
src/usr.bin/last/last.c
src/usr.bin/lastcomm/lastcomm.c
src/usr.bin/leave/leave.c
src/usr.bin/lock/lock.c
src/usr.bin/ncal/ncal.c
src/usr.bin/pr/pr.c
src/usr.bin/rup/rup.c
src/usr.bin/rwall/rwall.c
src/usr.bin/rwho/rwho.c
src/usr.bin/script/script.c
src/usr.bin/stat/stat.c
src/usr.bin/systat/vmstat.c
src/usr.bin/touch/touch.c
src/usr.bin/unzip/unzip.c
src/usr.bin/w/w.c
src/usr.bin/wall/wall.c
src/usr.bin/who/who.c
src/usr.sbin/ac/ac.c
src/usr.sbin/apm/apm.c
src/usr.sbin/cron/cron/cron.c
src/usr.sbin/gstat/gstat.c
src/usr.sbin/lpr/common_source/common.c
src/usr.sbin/mfiutil/mfi_evt.c
src/usr.sbin/newsyslog/ptimes.c
src/usr.sbin/pw/pw_user.c
src/usr.sbin/route6d/route6d.c
src/usr.sbin/syslogd/syslogd.c
src/usr.sbin/tzsetup/tzsetup.c
src/usr.sbin/usbdump/usbdump.c
src/usr.sbin/watch/watch.c

Follow Kostik's suggestion to use strlcpy(3) rather than strncpy(3).

Update strncpy(3) to strlcpy(3).

The strlen() call in ls_strftime() is no useful: it's result is dropped immediately. Please note my suggestion about voiding return type for ls_strftime().

This revision is now accepted and ready to land.Sep 8 2022, 5:25 AM
In D36474#828714, @kib wrote:

The strlen() call in ls_strftime() is no useful: it's result is dropped immediately. Please note my suggestion about voiding return type for ls_strftime().

I did make this change before checking in.