Reject out-of-range numeric timezone specifications.
authorTom Lane <[email protected]>
Tue, 22 Jul 2014 02:41:36 +0000 (22:41 -0400)
committerTom Lane <[email protected]>
Tue, 22 Jul 2014 02:41:36 +0000 (22:41 -0400)
In commit 631dc390f49909a5c8ebd6002cfb2bcee5415a9d, we started to handle
simple numeric timezone offsets via the zic library instead of the old
CTimeZone/HasCTZSet kluge.  However, we overlooked the fact that the zic
code will reject UTC offsets exceeding a week (which seems a bit arbitrary,
but not because it's too tight ...).  This led to possibly setting
session_timezone to NULL, which results in crashes in most timezone-related
operations as of 9.4, and crashes in a small number of places even before
that.  So check for NULL return from pg_tzset_offset() and report an
appropriate error message.  Per bug #11014 from Duncan Gillis.

Back-patch to all supported branches, like the previous patch.
(Unfortunately, as of today that no longer includes 8.4.)

src/backend/commands/variable.c
src/timezone/pgtz.c

index 26378da8279c4c50878e2ad3084676a5b9f3fe1e..74f5437f01c0f0914c34ce995905941b0b1ea4c5 100644 (file)
@@ -241,6 +241,8 @@ assign_timezone(const char *value, bool doit, GucSource source)
    char       *result;
    char       *endptr;
    double      hours;
+   int         new_ctimezone;
+   pg_tz      *new_tz;
 
    /*
     * Check for INTERVAL 'foo'
@@ -294,16 +296,28 @@ assign_timezone(const char *value, bool doit, GucSource source)
            pfree(interval);
            return NULL;
        }
-       if (doit)
-       {
-           /* Here we change from SQL to Unix sign convention */
+
+       /* Here we change from SQL to Unix sign convention */
 #ifdef HAVE_INT64_TIMESTAMP
-           CTimeZone = -(interval->time / USECS_PER_SEC);
+       new_ctimezone = -(interval->time / USECS_PER_SEC);
 #else
-           CTimeZone = -interval->time;
+       new_ctimezone = -interval->time;
 #endif
-           session_timezone = pg_tzset_offset(CTimeZone);
+       new_tz = pg_tzset_offset(new_ctimezone);
 
+       if (!new_tz)
+       {
+           ereport(GUC_complaint_elevel(source),
+                   (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                    errmsg("invalid interval value for time zone: out of range")));
+           pfree(interval);
+           return NULL;
+       }
+
+       if (doit)
+       {
+           CTimeZone = new_ctimezone;
+           session_timezone = new_tz;
            HasCTZSet = true;
        }
        pfree(interval);
@@ -316,11 +330,22 @@ assign_timezone(const char *value, bool doit, GucSource source)
        hours = strtod(value, &endptr);
        if (endptr != value && *endptr == '\0')
        {
+           /* Here we change from SQL to Unix sign convention */
+           new_ctimezone = -hours * SECS_PER_HOUR;
+           new_tz = pg_tzset_offset(new_ctimezone);
+
+           if (!new_tz)
+           {
+               ereport(GUC_complaint_elevel(source),
+                       (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                        errmsg("invalid value for time zone: out of range")));
+               return NULL;
+           }
+
            if (doit)
            {
-               /* Here we change from SQL to Unix sign convention */
-               CTimeZone = -hours * SECS_PER_HOUR;
-               session_timezone = pg_tzset_offset(CTimeZone);
+               CTimeZone = new_ctimezone;
+               session_timezone = new_tz;
                HasCTZSet = true;
            }
        }
@@ -352,8 +377,6 @@ assign_timezone(const char *value, bool doit, GucSource source)
            /*
             * Otherwise assume it is a timezone name, and try to load it.
             */
-           pg_tz      *new_tz;
-
            new_tz = pg_tzset(value);
 
            if (!new_tz)
index 5c28c5c483d74f8968d893f37cc463d72c242764..de1832ab6d2d6a79d625aebfdb30a7b1b26ce4ab 100644 (file)
@@ -1333,6 +1333,9 @@ pg_tzset(const char *name)
  * The GMT offset is specified in seconds, positive values meaning west of
  * Greenwich (ie, POSIX not ISO sign convention).  However, we use ISO
  * sign convention in the displayable abbreviation for the zone.
+ *
+ * Caution: this can fail (return NULL) if the specified offset is outside
+ * the range allowed by the zic library.
  */
 pg_tz *
 pg_tzset_offset(long gmtoffset)