Bug 23732

Summary: ext4 timestamp range contains 68-year gaps
Product: File System Reporter: Mark Harris (mh8928)
Component: ext4Assignee: fs_ext4 (fs_ext4)
Status: RESOLVED CODE_FIX    
Severity: normal CC: alan, cse.cem, szg00000, thumperward, tytso, xiangyu.zhou
Priority: P1    
Hardware: All   
OS: Linux   
Kernel Version: 3.6-rc1 Subsystem:
Regression: No Bisected commit-id:

Description Mark Harris 2010-11-25 11:28:44 UTC
When 256-byte inodes are used, ext4 attempts to extend the tv_sec
portion of file timestamps from 32 to 34 bits on 64-bit systems,
presumably to defer the Y2038 problem.  However the code to do
this still fails in 2038.  Nevertheless, some timestamps beyond
the year 2038 work correctly.

Using 2.6.35/x86_64 on ext4 with 256-byte inodes:

markh@zacc:~$ touch -d 2038-01-31 /test/2038
markh@zacc:~$ touch -d 2106-02-22 /test/2106
markh@zacc:~$ ls -l /test/2038 /test/2106
-rw-r--r-- 1 markh markh 0 2038-01-31 00:00 /test/2038
-rw-r--r-- 1 markh markh 0 2106-02-22 00:00 /test/2106
markh@zacc:~$ sudo umount /test
markh@zacc:~$ sudo mount /dev/sda2 /test
markh@zacc:~$ ls -l /test/2038 /test/2106
-rw-r--r-- 1 markh markh 0 1901-12-25 17:31 /test/2038
-rw-r--r-- 1 markh markh 0 2106-02-22 00:00 /test/2106
markh@zacc:~$ 

The problem is in fs/ext4/ext.h.  The macros EXT4_INODE_GET_XTIME
and EXT4_EINODE_GET_XTIME sign-extend the low 32 bits of tv_sec,
and then ext4_decode_extra_time uses "|=" to tack on the 2
additional bits.  However if bit 31 is 1, bits 32..63 will always
be 1 due to the sign extension regardless of the 2 extra bits:

static inline void ext4_decode_extra_time(struct timespec *time, __le32 extra)
{
       if (sizeof(time->tv_sec) > 4)
	       time->tv_sec |= (__u64)(le32_to_cpu(extra) & EXT4_EPOCH_MASK)
			       << 32;
       time->tv_nsec = (le32_to_cpu(extra) & EXT4_NSEC_MASK) >> EXT4_EPOCH_BITS;
}

...

#define EXT4_INODE_GET_XTIME(xtime, inode, raw_inode)			       \
do {									       \
	(inode)->xtime.tv_sec = (signed)le32_to_cpu((raw_inode)->xtime);       \
	if (EXT4_FITS_IN_INODE(raw_inode, EXT4_I(inode), xtime ## _extra))     \
		ext4_decode_extra_time(&(inode)->xtime,			       \
				       raw_inode->xtime ## _extra);	       \
} while (0)


It is not clear what time range was intended to be handled,
but the range should be continuous.  Preferably it would be the
range -0x80000000 (1901-12-13) to 0x37fffffff (2446-05-10),
in order to handle all legacy 32-bit timestamps and as many
consecutive future dates as possible.
Comment 1 Conrad Meyer 2014-03-30 13:47:11 UTC
Impressively, this bug is still present in v3.14-rc8.

$ touch -d 2038-01-31 test-123
$ sudo sh -c "echo 3 > /proc/sys/vm/drop_caches"
$ ls -ld test-123
drwxrwxr-x 2 cmeyer cmeyer 4096 Dec 25  1901 test-123
Comment 2 Conrad Meyer 2014-03-30 15:08:08 UTC
Patch submitted, seems to fix the issue on my system.

https://lkml.org/lkml/2014/3/30/40
Comment 3 Conrad Meyer 2014-03-30 15:18:20 UTC
34 (signed) bits of seconds gets us to Y2242:

$ touch -d 2242-01-01 test-123
$ sync ;sudo sh -c "echo 3 > /proc/sys/vm/drop_caches"; sleep 1; sudo sh -c "echo 3 > /proc/sys/vm/drop_caches"; sleep 1; ls -ld test-123
drwxrwxr-x 2 cmeyer cmeyer 4096 Jan  1  2242 test-123
$ touch -d 2243-01-01 test-123
$ sync ;sudo sh -c "echo 3 > /proc/sys/vm/drop_caches"; sleep 1; sudo sh -c "echo 3 > /proc/sys/vm/drop_caches"; sleep 1; ls -ld test-123
drwxrwxr-x 2 cmeyer cmeyer 4096 Aug  3  1698 test-123
Comment 4 Conrad Meyer 2014-03-30 15:19:07 UTC
(Which we expect, given:)

$ echo $((1970+(2038-1970)*4))
2242
Comment 5 Conrad Meyer 2014-04-01 03:32:57 UTC
Hm, ignore my patch I guess. Some historical context:

http://thread.gmane.org/gmane.comp.file-systems.ext4/40978
Comment 6 Conrad Meyer 2015-02-19 16:15:02 UTC
> RESOLVED CODE_FIX

Alan, what patch fixed it (sha1)? Thanks!
Comment 7 Alan 2015-02-19 19:22:42 UTC
I don't have the number. I re-ran your test case

[root@localhost next]# touch -d 2038-01-31 test-123
[root@localhost next]# ls -l test-123
-rw-r--r--. 1 root root 0 Jan 31  2038 test-123
[root@localhost next]# echo 3 >/proc/sys/vm/drop_caches 
[root@localhost next]# ls -l test-123
-rw-r--r--. 1 root root 0 Jan 31  2038 test-123
Comment 8 Conrad Meyer 2015-02-19 20:16:30 UTC
The test case is 'touch -d 2243-01-01 test-123'... 2038 works fine.
Comment 9 Conrad Meyer 2015-02-19 20:18:03 UTC
Hm, 2243 appears to work now too. *Shrug*.
Comment 10 Theodore Tso 2015-02-21 01:15:57 UTC
I'm not sure why the test cases have been passing but the true fix hasn't been applied yet.  This is due to my not having time and the need for us to have better test cases in e2fsprogs (for both the old and new encodings, etc.)   As usual, the highly urgent problems with short-term deadlines tend to get higher priority from the important long-term items.

Specifically, I want to be able to convert time strings to integers using both the old the new encodings, and then have test cases to make sure that e2fsck and the kernel are doing the right thing and coverting as necessary as we had determined in the migration plan for this bug.
Comment 11 Theodore Tso 2016-03-20 16:54:16 UTC
This has been fixed in the latest kernel and e2fsprogs