Tighten codepaths that parse timestamps in commit objects.
* jk/commit-dates-parsing-fix:
show_ident_date: fix tz range check
log: do not segfault on gmtime errors
log: handle integer overflow in timestamps
date: check date overflow against time_t
fsck: report integer overflow in author timestamps
t4212: test bogus timestamps with git-log
unsigned long approxidate_careful(const char *, int *);
unsigned long approxidate_relative(const char *date, const struct timeval *now);
enum date_mode parse_date_format(const char *format);
+int date_overflows(unsigned long date);
#define IDENT_STRICT 1
#define IDENT_NO_DATE 2
tz = local_tzoffset(time);
tm = time_to_tm(time, tz);
- if (!tm)
- return NULL;
+ if (!tm) {
+ tm = time_to_tm(0, 0);
+ tz = 0;
+ }
strbuf_reset(&timebuf);
if (mode == DATE_SHORT)
gettimeofday(&tv, NULL);
return approxidate_str(date, &tv, error_ret);
}
+
+int date_overflows(unsigned long t)
+{
+ time_t sys;
+
+ /* If we overflowed our unsigned long, that's bad... */
+ if (t == ULONG_MAX)
+ return 1;
+
+ /*
+ * ...but we also are going to feed the result to system
+ * functions that expect time_t, which is often "signed long".
+ * Make sure that we fit into time_t, as well.
+ */
+ sys = t;
+ return t != sys || (t < 1) != (sys < 1);
+}
static int fsck_ident(char **ident, struct object *obj, fsck_error error_func)
{
+ char *end;
+
if (**ident == '<')
return error_func(obj, FSCK_ERROR, "invalid author/committer line - missing space before email");
*ident += strcspn(*ident, "<>\n");
(*ident)++;
if (**ident == '0' && (*ident)[1] != ' ')
return error_func(obj, FSCK_ERROR, "invalid author/committer line - zero-padded date");
- *ident += strspn(*ident, "0123456789");
- if (**ident != ' ')
+ if (date_overflows(strtoul(*ident, &end, 10)))
+ return error_func(obj, FSCK_ERROR, "invalid author/committer line - date causes integer overflow");
+ if (end == *ident || *end != ' ')
return error_func(obj, FSCK_ERROR, "invalid author/committer line - bad date");
- (*ident)++;
+ *ident = end + 1;
if ((**ident != '+' && **ident != '-') ||
!isdigit((*ident)[1]) ||
!isdigit((*ident)[2]) ||
int parents = 0;
int err;
- if (commit->date == ULONG_MAX)
- return error_func(&commit->object, FSCK_ERROR, "invalid author/committer line");
-
if (memcmp(buffer, "tree ", 5))
return error_func(&commit->object, FSCK_ERROR, "invalid format - expected 'tree' line");
if (get_sha1_hex(buffer+5, tree_sha1) || buffer[45] != '\n')
enum date_mode mode)
{
unsigned long date = 0;
- int tz = 0;
+ long tz = 0;
if (ident->date_begin && ident->date_end)
date = strtoul(ident->date_begin, NULL, 10);
- if (ident->tz_begin && ident->tz_end)
- tz = strtol(ident->tz_begin, NULL, 10);
+ if (date_overflows(date))
+ date = 0;
+ else {
+ if (ident->tz_begin && ident->tz_end)
+ tz = strtol(ident->tz_begin, NULL, 10);
+ if (tz >= INT_MAX || tz <= INT_MIN)
+ tz = 0;
+ }
return show_date(date, tz, mode);
}
grep "error in commit $new" out
'
+# date is 2^64 + 1
+test_expect_success 'integer overflow in timestamps is reported' '
+ git cat-file commit HEAD >basis &&
+ sed "s/^\\(author .*>\\) [0-9]*/\\1 18446744073709551617/" \
+ <basis >bad-timestamp &&
+ new=$(git hash-object -t commit -w --stdin <bad-timestamp) &&
+ test_when_finished "remove_object $new" &&
+ git update-ref refs/heads/bogus "$new" &&
+ test_when_finished "git update-ref -d refs/heads/bogus" &&
+ git fsck 2>out &&
+ cat out &&
+ grep "error in commit $new.*integer overflow" out
+'
+
test_expect_success 'tag pointing to nonexistent' '
cat >invalid-tag <<-\EOF &&
object ffffffffffffffffffffffffffffffffffffffff
test_cmp expect.err actual.err
'
+munge_author_date () {
+ git cat-file commit "$1" >commit.orig &&
+ sed "s/^\(author .*>\) [0-9]*/\1 $2/" <commit.orig >commit.munge &&
+ git hash-object -w -t commit commit.munge
+}
+
+test_expect_success 'unparsable dates produce sentinel value' '
+ commit=$(munge_author_date HEAD totally_bogus) &&
+ echo "Date: Thu Jan 1 00:00:00 1970 +0000" >expect &&
+ git log -1 $commit >actual.full &&
+ grep Date <actual.full >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'unparsable dates produce sentinel value (%ad)' '
+ commit=$(munge_author_date HEAD totally_bogus) &&
+ echo >expect &&
+ git log -1 --format=%ad $commit >actual
+ test_cmp expect actual
+'
+
+# date is 2^64 + 1
+test_expect_success 'date parser recognizes integer overflow' '
+ commit=$(munge_author_date HEAD 18446744073709551617) &&
+ echo "Thu Jan 1 00:00:00 1970 +0000" >expect &&
+ git log -1 --format=%ad $commit >actual &&
+ test_cmp expect actual
+'
+
+# date is 2^64 - 2
+test_expect_success 'date parser recognizes time_t overflow' '
+ commit=$(munge_author_date HEAD 18446744073709551614) &&
+ echo "Thu Jan 1 00:00:00 1970 +0000" >expect &&
+ git log -1 --format=%ad $commit >actual &&
+ test_cmp expect actual
+'
+
+# date is within 2^63-1, but enough to choke glibc's gmtime
+test_expect_success 'absurdly far-in-future dates produce sentinel' '
+ commit=$(munge_author_date HEAD 999999999999999999) &&
+ echo "Thu Jan 1 00:00:00 1970 +0000" >expect &&
+ git log -1 --format=%ad $commit >actual &&
+ test_cmp expect actual
+'
+
test_done