Subject: bug#16872: `date -d 'TZ="America/Los_Angeles" "00:00 + 1 hour"'` crashes

From: Pádraig Brady <[email-masked]>
Date: Tue, 25 Feb 2014 11:11:26 +0000

On 02/25/2014 08:50 AM, Pádraig Brady wrote:
> On 02/25/2014 08:13 AM, Mike Frysinger wrote:
>> as reported by Bertrand Jacquin, this crashes:
>> $ date -d 'TZ="America/Los_Angeles" "00:00 + 1 hour"'
>> Segmentation fault
>>
>> (gdb) bt
>> #0  0x00007ffff7ab1014 in __GI___libc_free (mem=0x7fffffffc8b0) at malloc.c:2942
>> #1  0x0000000000406730 in parse_datetime (result=result@entry=0x7fffffffcab0, p=<optimized out>, p@entry=0x7fffffffd04a "TZ=\"America/Los_Angeles\" \"00:00 + 1 hour\"", now=<optimized out>, now
>> @entry=0x0) at ./lib/parse-datetime.y:1307
>> #2  0x00000000004023c7 in main (argc=0x3, argv=0x7fffffffcc68) at src/date.c:522
>>
>> that's 15fca2a02e38d69915c52ef41eee3c7d52b67f3e i happened to have already
>> built, but seems to reproduce easily across older versions.
>> -mike
>>
> 
> Ugh. Reproducible here.
> I'll fix up the issue in gnulib.

Proposed patch attached.

thanks,
Pádraig.

>From 6959d3a75b298a36c021e022216a27810948634f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <[email-masked]>
Date: Tue, 25 Feb 2014 10:58:48 +0000
Subject: [PATCH] parse-datetime: fix crash or infloop in TZ="" parsing

This was reported in http://bugs.gnu.org/16872
from the coreutils command: date -d 'TZ="""'

The infinite loop for this case was present since the
initial TZ="" parsing support in commit de95bdc2 29-10-2004.
This was changed to a crash or heap corruption depending
on the platform with commit 2e3e4195 18-01-2010.

* lib/parse-datetime.y (parse_datetime): Break out of the
TZ="" parsing loop once the second significant " is found.
Also skip over any subsequent whitespace to be consistent
with the non TZ= case.
* tests/test-parse-datetime.c: Add test cases for TZ="" parsing.
---
 ChangeLog                   |    9 +++++++++
 lib/parse-datetime.y        |    7 +++++--
 tests/test-parse-datetime.c |   16 ++++++++++++++++
 3 files changed, 30 insertions(+), 2 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index fcb9afc..4f20fda 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2014-02-25  Pádraig Brady <[email-masked]>
+
+	parse-datetime: fix crash or infloop in TZ="" parsing
+	* lib/parse-datetime.y (parse_datetime): Break out of the
+	TZ="" parsing loop once the second significant " is found.
+	Also skip over any subsequent whitespace to be consistent
+	with the non TZ= case.
+	* tests/test-parse-datetime.c: Add test cases for TZ="" parsing.
+
 2014-02-23  Paul Eggert  <[email-masked]>
 
 	diffseq: remove TOO_EXPENSIVE heuristic
diff --git a/lib/parse-datetime.y b/lib/parse-datetime.y
index 6ece765..0ba0a52 100644
--- a/lib/parse-datetime.y
+++ b/lib/parse-datetime.y
@@ -1303,8 +1303,6 @@ parse_datetime (struct timespec *result, char const *p,
             char tz1buf[TZBUFSIZE];
             bool large_tz = TZBUFSIZE < tzsize;
             bool setenv_ok;
-            /* Free tz0, in case this is the 2nd or subsequent time through. */
-            free (tz0);
             tz0 = get_tz (tz0buf);
             z = tz1 = large_tz ? xmalloc (tzsize) : tz1buf;
             for (s = tzbase; *s != '"'; s++)
@@ -1316,7 +1314,12 @@ parse_datetime (struct timespec *result, char const *p,
             if (!setenv_ok)
               goto fail;
             tz_was_altered = true;
+
             p = s + 1;
+            while (c = *p, c_isspace (c))
+              p++;
+
+            break;
           }
     }
 
diff --git a/tests/test-parse-datetime.c b/tests/test-parse-datetime.c
index a410d3d..25385ca 100644
--- a/tests/test-parse-datetime.c
+++ b/tests/test-parse-datetime.c
@@ -419,5 +419,21 @@ main (int argc _GL_UNUSED, char **argv)
      starting with a high-bit-set byte would be treated like "0".  */
   ASSERT ( ! parse_datetime (&result, "\xb0", &now));
 
+  /* Exercise TZ="" parsing code.  */
+  /* These two would infloop or segfault before Feb 2014.  */
+  ASSERT ( ! parse_datetime (&result, "TZ=\"\"\"", &now));
+  ASSERT ( ! parse_datetime (&result, "TZ=\"\" \"", &now));
+  /* Exercise invalid patterns.  */
+  ASSERT ( ! parse_datetime (&result, "TZ=\"", &now));
+  ASSERT ( ! parse_datetime (&result, "TZ=\"\\\"", &now));
+  ASSERT ( ! parse_datetime (&result, "TZ=\"\\n", &now));
+  ASSERT ( ! parse_datetime (&result, "TZ=\"\\n\"", &now));
+  /* Exercise valid patterns.  */
+  ASSERT (   parse_datetime (&result, "TZ=\"\"", &now));
+  ASSERT (   parse_datetime (&result, "TZ=\"\" ", &now));
+  ASSERT (   parse_datetime (&result, " TZ=\"\"", &now));
+  ASSERT (   parse_datetime (&result, "TZ=\"\\\\\"", &now));
+  ASSERT (   parse_datetime (&result, "TZ=\"\\\"\"", &now));
+
   return 0;
 }
-- 
1.7.7.6