gzip: diagnose out-of-range MTIME
authorPaul Eggert <eggert@cs.ucla.edu>
Wed, 8 Nov 2017 05:01:13 +0000 (21:01 -0800)
committerPaul Eggert <eggert@cs.ucla.edu>
Wed, 8 Nov 2017 05:02:19 +0000 (21:02 -0800)
This seems to be the problem reported by Bruno Haible for GNU/Hurd
i386 with touch 8.26 and glibc 2.24 (Bug#29033#20).
* NEWS: Document this.
* gzip.c (get_method): If MTIME is out of range for
this platform, warn and substitute the nearest in-range
value, instead of silently ignoring it.
(do_list): Remove no-longer-needed test for unknown time stamp.

NEWS
gzip.c

diff --git a/NEWS b/NEWS
index 2b7a168c20c4de021185eb061996beddb00378fe..34eae0ddc687bf3745e63fc0f09178b3c5b1dd55 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -13,10 +13,14 @@ GNU gzip NEWS                                    -*- outline -*-
   When decompressing data in 'pack' format, gzip no longer mishandles
   leading zeros in the end-of-block code.  [bug introduced in gzip-1.6]
 
-  When converting timestamps to gzip file format (32-bit unsigned) or
-  to time_t format (system-dependent), gzip now ignores out-of-range
-  values instead of shoehorning them into the destination format,
-  sometimes with undefined behavior.  This affects timestamps before
+  When converting from system-dependent time_t format to the 32-bit
+  unsigned MTIME format used in gzip files, if a timestamp does not
+  fit gzip now substitutes zero instead of the timestamp's low-order
+  32 bits, as per Internet RFC 1952.  When converting from MTIME to
+  time_t format, if a timestamp does not fit gzip now warns and
+  substitutes the nearest in-range value instead of crashing or
+  silently substituting an implementation-defined value (typically,
+  the timestamp's low-order bits).  This affects timestamps before
   1970 and after 2106, and timestamps after 2038 on platforms with
   32-bit signed time_t.  [bug present since the beginning]
 
diff --git a/gzip.c b/gzip.c
index cfc4fe3a2a7ff70ea28662cd52aef87c4b9257d7..637b7913ec9e5d7fa18a822846fa07ab2bc3be07 100644 (file)
--- a/gzip.c
+++ b/gzip.c
@@ -195,10 +195,12 @@ static char *env;            /* contents of GZIP env variable */
 static char const *z_suffix; /* default suffix (can be set with --suffix) */
 static size_t z_len;         /* strlen(z_suffix) */
 
-/* The original timestamp (modification time).  Its tv_nsec component
-   is negative if the original time is unknown or is out of time_t
-   range; the latter can happen on hosts with 32-bit signed time_t
-   because the gzip format's MTIME is 32-bit unsigned.  */
+/* The original timestamp (modification time).  If the original is
+   unknown, TIME_STAMP.tv_nsec is negative.  If the original is
+   greater than struct timespec range, TIME_STAMP is the maximal
+   struct timespec value; this can happen on hosts with 32-bit signed
+   time_t because the gzip format's MTIME is 32-bit unsigned.
+   The original cannot be less than struct timespec range.  */
 struct timespec time_stamp;
 
 /* The set of signals that are caught.  */
@@ -1546,10 +1548,21 @@ local int get_method(in)
         stamp |= ((ulg)get_byte()) << 8;
         stamp |= ((ulg)get_byte()) << 16;
         stamp |= ((ulg)get_byte()) << 24;
-        if (!no_time && 0 < stamp && stamp <= TYPE_MAXIMUM (time_t))
+        if (stamp != 0 && !no_time)
           {
-            time_stamp.tv_sec = stamp;
-            time_stamp.tv_nsec = 0;
+            if (stamp <= TYPE_MAXIMUM (time_t))
+              {
+                time_stamp.tv_sec = stamp;
+                time_stamp.tv_nsec = 0;
+              }
+            else
+              {
+                WARN ((stderr,
+                       "%s: %s: MTIME %lu out of range for this platform\n",
+                       program_name, ifname, stamp));
+                time_stamp.tv_sec = TYPE_MAXIMUM (time_t);
+                time_stamp.tv_nsec = TIMESPEC_RESOLUTION - 1;
+              }
           }
 
         magic[8] = get_byte ();  /* Ignore extra flags.  */
@@ -1778,9 +1791,7 @@ local void do_list(ifd, method)
         static char const month_abbr[][4]
           = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
               "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
-        struct tm *tm = (time_stamp.tv_nsec < 0
-                         ? NULL
-                         : localtime (&time_stamp.tv_sec));
+        struct tm *tm = localtime (&time_stamp.tv_sec);
         printf ("%5s %08lx ", methods[method], crc);
         if (tm)
           printf ("%s%3d %02d:%02d ", month_abbr[tm->tm_mon],