From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Tue, 22 Jul 2014 02:41:36 +0000 (-0400)
Subject: Reject out-of-range numeric timezone specifications.
X-Git-Tag: REL9_0_19~113
X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=6e5a39c9e6e17c55570e246373c55f3896f52e5e;p=postgresql

Reject out-of-range numeric timezone specifications.

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.)
---

diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 26378da827..74f5437f01 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -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)
diff --git a/src/timezone/pgtz.c b/src/timezone/pgtz.c
index 5c28c5c483..de1832ab6d 100644
--- a/src/timezone/pgtz.c
+++ b/src/timezone/pgtz.c
@@ -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)