Subject: [PATCH v2] date: detect underflow when parsing dates with positive timezone offset

From: "darcy via GitGitGadget" <[email-masked]>
Date: Sun, 02 Jun 2024 23:06:48 +0000

From: darcy <[email-masked]>

Overriding the date of a commit to be close to "1970-01-01 00:00:00"
with a large enough timezone for the equivelant GMT time to be before
the epoch is considered valid by `parse_date_basic`.

This leads to an integer underflow in the commit timestamp, which is not
caught by `git-commit`, but will cause other services to fail, such as
`git-fsck`, which reports "badDateOverflow: invalid author/committer
line - date causes integer overflow".

Instead check the timezone offset and fail if the resulting time comes
before the epoch, "1970-01-01T00:00:00Z", when parsing.

Signed-off-by: Darcy Burke <[email-masked]>
---
    fix: prevent date underflow when using positive timezone offset
    
    cc: Patrick Steinhardt [email-masked] cc: Phillip Wood
    [email-masked]

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1726%2Fdxrcy%2Fmaster-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1726/dxrcy/master-v2
Pull-Request: https://github.com/git/git/pull/1726

Range-diff vs v1:

 1:  4542d984aab ! 1:  db508b2f533 fix: prevent date underflow when using positive timezone offset
     @@ Metadata
      Author: darcy <[email-masked]>
      
       ## Commit message ##
     -    fix: prevent date underflow when using positive timezone offset
     +    date: detect underflow when parsing dates with positive timezone offset
      
     -    Overriding the date of a commit to be `1970-01-01` with a large enough
     -    timezone for the equivalent GMT time to before 1970 is no longer
     -    accepted.
     +    Overriding the date of a commit to be close to "1970-01-01 00:00:00"
     +    with a large enough timezone for the equivelant GMT time to be before
     +    the epoch is considered valid by `parse_date_basic`.
      
     -    Example: `GIT_COMMITTER_DATE='1970-01-01T00:00:00+10' git commit` would
     -    previously be accepted, only to unexpectedly fail in other parts of the
     -    code, such as `git push`. The timestamp is now checked against postitive
     -    timezone values.
     +    This leads to an integer underflow in the commit timestamp, which is not
     +    caught by `git-commit`, but will cause other services to fail, such as
     +    `git-fsck`, which reports "badDateOverflow: invalid author/committer
     +    line - date causes integer overflow".
      
     -    Signed-off-by: darcy <[email-masked]>
     +    Instead check the timezone offset and fail if the resulting time comes
     +    before the epoch, "1970-01-01T00:00:00Z", when parsing.
     +
     +    Signed-off-by: Darcy Burke <[email-masked]>
      
       ## date.c ##
     -@@ date.c: int parse_date_basic(const char *date, timestamp_t *timestamp, int *offset)
     - 			match = match_alpha(date, &tm, offset);
     - 		else if (isdigit(c))
     - 			match = match_digit(date, &tm, offset, &tm_gmt);
     --		else if ((c == '-' || c == '+') && isdigit(date[1]))
     -+		else if ((c == '-' || c == '+') && isdigit(date[1]) && tm.tm_hour != -1)
     - 			match = match_tz(date, offset);
     - 
     - 		if (!match) {
      @@ date.c: int parse_date_basic(const char *date, timestamp_t *timestamp, int *offset)
       		}
       	}
       
      -	if (!tm_gmt)
      +	if (!tm_gmt) {
     -+		if (*offset > 0 && *offset * 60 > *timestamp) {
     ++		if (*offset > 0 && *offset * 60 > *timestamp)
      +			return -1;
     -+		}
       		*timestamp -= *offset * 60;
      +	}
      +
       	return 0; /* success */
       }
       
     +
     + ## t/t0006-date.sh ##
     +@@ t/t0006-date.sh: check_parse '2008-02-14 20:30:45 -:30' '2008-02-14 20:30:45 +0000'
     + check_parse '2008-02-14 20:30:45 -05:00' '2008-02-14 20:30:45 -0500'
     + check_parse '2008-02-14 20:30:45' '2008-02-14 20:30:45 -0500' EST5
     + check_parse 'Thu, 7 Apr 2005 15:14:13 -0700' '2005-04-07 15:14:13 -0700'
     ++check_parse '1970-01-01 00:00:00' '1970-01-01 00:00:00 +0000'
     ++check_parse '1970-01-01 00:00:00 +00' '1970-01-01 00:00:00 +0000'
     ++check_parse '1970-01-01 00:00:00 Z' '1970-01-01 00:00:00 +0000'
     ++check_parse '1970-01-01 00:00:00 -01' '1970-01-01 00:00:00 -0100'
     ++check_parse '1970-01-01 00:00:00 +01' bad
     ++check_parse '1970-01-01 00:00:00 +11' bad
     ++check_parse '1970-01-01 00:59:59 +01' bad
     ++check_parse '1970-01-01 01:00:00 +01' '1970-01-01 01:00:00 +0100'
     ++check_parse '1970-01-01 01:00:00 +11' bad
     ++check_parse '1970-01-02 00:00:00 +11' '1970-01-02 00:00:00 +1100'
     ++check_parse '1969-12-31 23:59:59' bad
     ++check_parse '1969-12-31 23:59:59 +00' bad
     ++check_parse '1969-12-31 23:59:59 Z' bad
     ++check_parse '1969-12-31 23:59:59 +11' bad
     ++check_parse '1969-12-31 23:59:59 -11' bad
     + 
     + check_approxidate() {
     + 	echo "$1 -> $2 +0000" >expect

 date.c          |  6 +++++-
 t/t0006-date.sh | 15 +++++++++++++++
 2 files changed, 20 insertions(+), 1 deletion(-)

diff --git a/date.c b/date.c
index 7365a4ad24f..8e3ec1bcb00 100644
--- a/date.c
+++ b/date.c
@@ -937,8 +937,12 @@ int parse_date_basic(const char *date, timestamp_t *timestamp, int *offset)
 		}
 	}
 
-	if (!tm_gmt)
+	if (!tm_gmt) {
+		if (*offset > 0 && *offset * 60 > *timestamp)
+			return -1;
 		*timestamp -= *offset * 60;
+	}
+
 	return 0; /* success */
 }
 
diff --git a/t/t0006-date.sh b/t/t0006-date.sh
index 3031256d143..cdbb40bec01 100755
--- a/t/t0006-date.sh
+++ b/t/t0006-date.sh
@@ -116,6 +116,21 @@ check_parse '2008-02-14 20:30:45 -:30' '2008-02-14 20:30:45 +0000'
 check_parse '2008-02-14 20:30:45 -05:00' '2008-02-14 20:30:45 -0500'
 check_parse '2008-02-14 20:30:45' '2008-02-14 20:30:45 -0500' EST5
 check_parse 'Thu, 7 Apr 2005 15:14:13 -0700' '2005-04-07 15:14:13 -0700'
+check_parse '1970-01-01 00:00:00' '1970-01-01 00:00:00 +0000'
+check_parse '1970-01-01 00:00:00 +00' '1970-01-01 00:00:00 +0000'
+check_parse '1970-01-01 00:00:00 Z' '1970-01-01 00:00:00 +0000'
+check_parse '1970-01-01 00:00:00 -01' '1970-01-01 00:00:00 -0100'
+check_parse '1970-01-01 00:00:00 +01' bad
+check_parse '1970-01-01 00:00:00 +11' bad
+check_parse '1970-01-01 00:59:59 +01' bad
+check_parse '1970-01-01 01:00:00 +01' '1970-01-01 01:00:00 +0100'
+check_parse '1970-01-01 01:00:00 +11' bad
+check_parse '1970-01-02 00:00:00 +11' '1970-01-02 00:00:00 +1100'
+check_parse '1969-12-31 23:59:59' bad
+check_parse '1969-12-31 23:59:59 +00' bad
+check_parse '1969-12-31 23:59:59 Z' bad
+check_parse '1969-12-31 23:59:59 +11' bad
+check_parse '1969-12-31 23:59:59 -11' bad
 
 check_approxidate() {
 	echo "$1 -> $2 +0000" >expect

base-commit: 9eaef5822cd76bbeb53b6479ce0ddaad34ee2b14
-- 
gitgitgadget