>From 9447baaf5a111dbecf03f2e2be470be02acad2f1 Mon Sep 17 00:00:00 2001 From: David Turner Date: Thu, 18 Feb 2016 12:57:56 -0700 Subject: [PATCH] e2fsck: Correct ext4 dates generated by old kernels Older kernels on 64-bit machines would incorrectly encode pre-1970 ext4 dates as post-2311 dates. Detect and correct this (assuming the current date is before 2242). Include tests for this, as well as changes to debugfs to correctly set crtimes. Fix an off-by-one bug in debugfs's literal unix time parsing. Signed-off-by: David Turner - ext2_fs.h: declare EXT4_EPOCH_BITS/EXT4_EPOCH_MASK like the kernel instead of in a separate header in an unusual location - problem.h: move PR_1_EA_TIME_OUT_OF_RANGE to avoid master conflict - problem.c: fix PR_1_EA_TIME_OUT_OF_RANGE PR_*_OK flag usage - f_pre_1970_date_encoding/script: run debugfs less often, use $MKE2FS instead of mkfs.ext4, fit within 80 columns Signed-off-by: Andreas Dilger --- debugfs/util.c | 2 +- e2fsck/pass1.c | 41 +++++++++++++++ e2fsck/problem.c | 5 ++ e2fsck/problem.h | 3 ++ lib/ext2fs/ext2_fs.h | 3 ++ tests/f_pre_1970_date_encoding/expect | 45 +++++++++++++++++ tests/f_pre_1970_date_encoding/name | 1 + tests/f_pre_1970_date_encoding/script | 94 +++++++++++++++++++++++++++++++++++ 8 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 tests/f_pre_1970_date_encoding/expect create mode 100644 tests/f_pre_1970_date_encoding/name create mode 100644 tests/f_pre_1970_date_encoding/script diff --git a/debugfs/util.c b/debugfs/util.c index 770e7e1..bd5de79 100644 --- a/debugfs/util.c +++ b/debugfs/util.c @@ -232,7 +232,7 @@ extern __s64 string_to_time(const char *arg) /* interpret it as an integer */ arg++; fallback: - ret = strtoll(arg+1, &tmp, 0); + ret = strtoll(arg, &tmp, 0); if (*tmp) return -1; return ret; diff --git a/e2fsck/pass1.c b/e2fsck/pass1.c index 631d735..60ed41f 100644 --- a/e2fsck/pass1.c +++ b/e2fsck/pass1.c @@ -448,6 +448,21 @@ fix: EXT2_INODE_SIZE(sb), "pass1"); } +static int check_inode_extra_negative_epoch(__u32 xtime, __u32 extra) { + return (xtime & (1 << 31)) != 0 && + (extra & EXT4_EPOCH_MASK) == EXT4_EPOCH_MASK; +} + +#define CHECK_INODE_EXTRA_NEGATIVE_EPOCH(inode, xtime) \ + check_inode_extra_negative_epoch(inode->i_##xtime, \ + inode->i_##xtime##_extra) + +/* When today's date is earlier than 2242, we assume that atimes, + * ctimes, crtimes, and mtimes with years in the range 2310..2378 are + * actually pre-1970 dates mis-encoded. + */ +#define EXT4_EXTRA_NEGATIVE_DATE_CUTOFF 2 * (1LL << 32) + static void check_inode_extra_space(e2fsck_t ctx, struct problem_context *pctx) { struct ext2_super_block *sb = ctx->fs->super; @@ -492,6 +507,32 @@ static void check_inode_extra_space(e2fsck_t ctx, struct problem_context *pctx) /* it seems inode has an extended attribute(s) in body */ check_ea_in_inode(ctx, pctx); } + + /* + * If the inode's extended atime (ctime, crtime, mtime) is stored in + * the old, invalid format, repair it. + */ + if (sizeof(time_t) > 4 && ctx->now < EXT4_EXTRA_NEGATIVE_DATE_CUTOFF && + (CHECK_INODE_EXTRA_NEGATIVE_EPOCH(inode, atime) || + CHECK_INODE_EXTRA_NEGATIVE_EPOCH(inode, ctime) || + CHECK_INODE_EXTRA_NEGATIVE_EPOCH(inode, crtime) || + CHECK_INODE_EXTRA_NEGATIVE_EPOCH(inode, mtime))) { + + if (!fix_problem(ctx, PR_1_EA_TIME_OUT_OF_RANGE, pctx)) + return; + + if (CHECK_INODE_EXTRA_NEGATIVE_EPOCH(inode, atime)) + inode->i_atime_extra &= ~EXT4_EPOCH_MASK; + if (CHECK_INODE_EXTRA_NEGATIVE_EPOCH(inode, ctime)) + inode->i_ctime_extra &= ~EXT4_EPOCH_MASK; + if (CHECK_INODE_EXTRA_NEGATIVE_EPOCH(inode, crtime)) + inode->i_crtime_extra &= ~EXT4_EPOCH_MASK; + if (CHECK_INODE_EXTRA_NEGATIVE_EPOCH(inode, mtime)) + inode->i_mtime_extra &= ~EXT4_EPOCH_MASK; + e2fsck_write_inode_full(ctx, pctx->ino, pctx->inode, + EXT2_INODE_SIZE(sb), "pass1"); + } + } /* diff --git a/e2fsck/problem.c b/e2fsck/problem.c index b39eab2..1e645e4 100644 --- a/e2fsck/problem.c +++ b/e2fsck/problem.c @@ -1124,6 +1124,11 @@ static struct e2fsck_problem problem_table[] = { N_("@i %i has corrupt @x header. "), PROMPT_CLEAR_INODE, 0 }, + /* Timestamp(s) on inode beyond 2310-04-04 are likely pre-1970. */ + { PR_1_EA_TIME_OUT_OF_RANGE, + N_("Timestamp(s) on @i %i beyond 2310-04-04 are likely pre-1970.\n"), + PROMPT_FIX, PR_PREEN_OK | PR_NO_OK }, + /* Pass 1b errors */ /* Pass 1B: Rescan for duplicate/bad blocks */ diff --git a/e2fsck/problem.h b/e2fsck/problem.h index b3f5b8f..edc381d 100644 --- a/e2fsck/problem.h +++ b/e2fsck/problem.h @@ -657,6 +657,9 @@ struct problem_context { /* Missing extent header */ #define PR_1_MISSING_EXTENT_HEADER 0x010081 +/* Timestamp(s) on inode beyond 2310-04-04 are likely pre-1970. */ +#define PR_1_EA_TIME_OUT_OF_RANGE 0x010082 + /* * Pass 1b errors */ diff --git a/lib/ext2fs/ext2_fs.h b/lib/ext2fs/ext2_fs.h index cdb68e8..9918356 100644 --- a/lib/ext2fs/ext2_fs.h +++ b/lib/ext2fs/ext2_fs.h @@ -481,6 +481,9 @@ struct ext2_inode_large { (offsetof(struct ext2_inode_large, i_checksum_hi) + sizeof(__u16) - \ EXT2_GOOD_OLD_INODE_SIZE) +#define EXT4_EPOCH_BITS 2 +#define EXT4_EPOCH_MASK ((1 << EXT4_EPOCH_BITS) - 1) + #define i_dir_acl i_size_high #define i_checksum_lo osd2.linux2.l_i_checksum_lo diff --git a/tests/f_pre_1970_date_encoding/expect b/tests/f_pre_1970_date_encoding/expect new file mode 100644 index 0000000..1a71571 --- /dev/null +++ b/tests/f_pre_1970_date_encoding/expect @@ -0,0 +1,45 @@ +times for year-1909 = + ctime: 0x8e475440:00000003 + atime: 0x8e475440:00000003 + mtime: 0x8e475440:00000003 +crtime: 0x8e475440:00000003 +times for year-1979 = + ctime: 0x11db6940:00000000 + atime: 0x11db6940:00000000 + mtime: 0x11db6940:00000000 +crtime: 0x11db6940:00000000 +times for year-2039 = + ctime: 0x82a37b40:00000001 + atime: 0x82a37b40:00000001 + mtime: 0x82a37b40:00000001 +crtime: 0x82a37b40:00000001 +times for year-2139 = + ctime: 0x3e9b9940:00000001 + atime: 0x3e9b9940:00000001 + mtime: 0x3e9b9940:00000001 +crtime: 0x3e9b9940:00000001 +times for year-1909 = + ctime: 0x8e475440:00000000 + atime: 0x8e475440:00000000 + mtime: 0x8e475440:00000000 +crtime: 0x8e475440:00000000 +times for year-1979 = + ctime: 0x11db6940:00000000 + atime: 0x11db6940:00000000 + mtime: 0x11db6940:00000000 +crtime: 0x11db6940:00000000 +times for year-2039 = + ctime: 0x82a37b40:00000001 + atime: 0x82a37b40:00000001 + mtime: 0x82a37b40:00000001 +crtime: 0x82a37b40:00000001 +times for year-2139 = + ctime: 0x3e9b9940:00000001 + atime: 0x3e9b9940:00000001 + mtime: 0x3e9b9940:00000001 +crtime: 0x3e9b9940:00000001 +times for year-1909 = + ctime: 0x8e475440:00000003 + atime: 0x8e475440:00000003 + mtime: 0x8e475440:00000003 +crtime: 0x8e475440:00000003 diff --git a/tests/f_pre_1970_date_encoding/name b/tests/f_pre_1970_date_encoding/name new file mode 100644 index 0000000..9805324 --- /dev/null +++ b/tests/f_pre_1970_date_encoding/name @@ -0,0 +1 @@ +correct mis-encoded pre-1970 dates diff --git a/tests/f_pre_1970_date_encoding/script b/tests/f_pre_1970_date_encoding/script new file mode 100644 index 0000000..e6d7bbd --- /dev/null +++ b/tests/f_pre_1970_date_encoding/script @@ -0,0 +1,94 @@ +if ! test -x $DEBUGFS_EXE; then + echo "$test_name: $test_description: skipped (no debugfs)" + return 0 +fi + +OUT=$test_name.log +TIMESTAMPS=$test_name.timestamps.log +EXP=$test_dir/expect +FSCK_OPT=-yf + +create_file_with_xtime_and_extra() { + name=$1 + time=$2 + extra=$3 + { + echo "write /dev/null $name" + for xtime in atime ctime mtime crtime; do + echo "set_inode_field $name $xtime @$time" + echo "set_inode_field $name ${xtime}_extra $extra" + done + } | $DEBUGFS -w -f /dev/stdin $TMPFILE >> $OUT 2>&1 +} + +get_file_xtime_and_extra() { + name=$1 + echo "times for $name =" >> $TIMESTAMPS + $DEBUGFS -R "stat $name" $TMPFILE 2>&1 | egrep '^( a| c| m|cr)time:' | + sed 's/ --.*//' >> $TIMESTAMPS +} + +rm -f $OUT $TIMESTAMPS + +# create an empty ext4 filesystem with 256-byte inodes for testing +> $TMPFILE +echo mkfs.ext4 -b 1024 -q -I 256 $TMPFILE 5000 >> $OUT +$MKE2FS -t ext4 -b 1024 -q -I 256 -F $TMPFILE 5000 >> $OUT 2>&1 + +# this is a pre-1970 file encoded with the old encoding. +# fsck should repair this +create_file_with_xtime_and_extra year-1909 -1907928000 3 + +# these are all already encoded correctly +create_file_with_xtime_and_extra year-1979 299592000 0 +create_file_with_xtime_and_extra year-2039 2191752000 1 +create_file_with_xtime_and_extra year-2139 5345352000 1 + +# confirm that the xtime is wrong on the pre-1970 file +get_file_xtime_and_extra year-1909 + +# and confirm that it is right on the remaining files +get_file_xtime_and_extra year-1979 +get_file_xtime_and_extra year-2039 +get_file_xtime_and_extra year-2139 + +# before we repair the filesystem, save off a copy so that +# we can use it later + +cp -a $TMPFILE $TMPFILE.sav + +# repair the filesystem +E2FSCK_TIME=1386393539 $FSCK $FSCK_OPT $TMPFILE >> $OUT 2>&1 + +# check that the dates and xtime_extra on the file is now correct +get_file_xtime_and_extra year-1909 + +# check that the remaining dates have not been altered +get_file_xtime_and_extra year-1979 +get_file_xtime_and_extra year-2039 +get_file_xtime_and_extra year-2139 + +# now we need to check that after the year 2242, e2fsck does not +# modify dates with extra_xtime=3 + +# restore the unrepaired filesystem +mv $TMPFILE.sav $TMPFILE + +#retry the repair +E2FSCK_TIME=9270393539 $FSCK $FSCK_OPT $TMPFILE >> $OUT 2>&1 + +# check that the 1909 file is unaltered (i.e. it has a post-2378 date) +get_file_xtime_and_extra year-1909 + +cmp -s $TIMESTAMPS $EXP +status=$? + +if [ "$status" = 0 ]; then + echo "$test_name: $test_description: ok" + touch $test_name.ok +else + echo "$test_name: $test_description: failed" + diff $DIFF_OPTS $EXP $TIMESTAMPS > $test_name.failed +fi + +unset OUT TIMESTAMPS EXP FSCK_OPT -- 1.9.1