Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1tJb6U-0045Ub-Rt for pgsql-hackers@arkaria.postgresql.org; Fri, 06 Dec 2024 16:22:55 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1tJb6R-00CuQM-9J for pgsql-hackers@arkaria.postgresql.org; Fri, 06 Dec 2024 16:22:52 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1tJb6Q-00CuQ5-NE for pgsql-hackers@lists.postgresql.org; Fri, 06 Dec 2024 16:22:52 +0000 Received: from mail-il1-x131.google.com ([2607:f8b0:4864:20::131]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.94.2) (envelope-from ) id 1tJb6O-001NIj-DZ for pgsql-hackers@lists.postgresql.org; Fri, 06 Dec 2024 16:22:50 +0000 Received: by mail-il1-x131.google.com with SMTP id e9e14a558f8ab-3a789d422bcso14372075ab.1 for ; Fri, 06 Dec 2024 08:22:48 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1733502167; x=1734106967; darn=lists.postgresql.org; h=in-reply-to:content-disposition:mime-version:references:message-id :subject:cc:to:from:date:from:to:cc:subject:date:message-id:reply-to; bh=9o3oZmlZLwVtnF2TG+aZhHbzlOu2zWsxBJqo0Lr5EYM=; b=NyoAbXrpwwTrMZx04yAWkzj4J6YxAXzfUpFmRmenHoEP8jTu0/WHmOo9wHIZYuFdbO Pv5e812aXE1EhceJzUmKc3EiA7fP3LtNJsDYZgylZQZGJvvUxEJSkdUJ2Q7drBqesxET 1ob7HJRmYR17CHCixEWqOx9G4v/gq9OlWJTAzNqx+rV8XUBYxv6khQkpl3NymJd4VtDg ADDYOjpQLgLMG8VWHBdFZxo0hHC4QbNab9powvAR9vg3o4BQdmH+u2pqqIkqxNGqAi1a N7b3VXc1zVU1qPBvoGieSA7U8H7Inh3DSGZEhgNyFb7ZYORk40LkJzO88NflGaWCkte2 t62A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1733502167; x=1734106967; h=in-reply-to:content-disposition:mime-version:references:message-id :subject:cc:to:from:date:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=9o3oZmlZLwVtnF2TG+aZhHbzlOu2zWsxBJqo0Lr5EYM=; b=EVzzKt5+YT+qCCRAVmikyMgBcyDHpO0YSCyD9LdfQgEnPQ4cGG+w5SotO25MAgMK5E wcHL4xq1mmd4jn5YENMYflhgHPqiHGlMq8Nl8RmDWeZmbdflZAt3LgxTErqcrHatSpCd FP/1kL9uOBwOJt9IITAJuHbEnQ+t6sJanl4bR45m1N9eojZB/hInOJ94Xapq9g6E9pts Fo3kwP5ig39inymKbMVMkjN+JjfXKILMo8QhNbYRFg+TQ1ry7/VQ4LPdTCxKkJfDw2g5 HGVyYI1Nfgf1NO08WbfvnCUISU4qN593BiurPZm6c10IttxzGd3N3MwjeXu164kRBfBe OzcQ== X-Forwarded-Encrypted: i=1; AJvYcCW0p+9gJin3MdI/nhLqGuWsVzgDGhuoqq1HyKR0yUrwznTQp6ASL1VP8NzXeMBFyXq5nwI4ZsVDYA2ZK9PK@lists.postgresql.org X-Gm-Message-State: AOJu0Yx5JSSeTniOLMkovTPKdLoP7o3YtrZ+5GLsRTLQ1Zr7EXUIoEI4 RWCbodhJ1E9eRCkre5UGI0vxEw7oYUyVxlRJqpTch9pA2QLP1d6s X-Gm-Gg: ASbGncsBogp3f3S/Wh1RxgmyMdySDR8MTrtbCCpQEJeO9lpJqDvOZPWlFAJ8l85sxY9 YYeniULUm/BzxMFjf8xebPnUaQK637PYnLgDEoSfK+kQ4sjjqmTtW5wYyUQSZenUikz2N2oY/ae AQABDG03gKCFBy5x8LAevq2FLdAH6FNypilysqOVMAbhAGKmE6ukV0KfTpgOJCDAG+z8bzg/s4h iCDgL5ezQJzThYq3PvVz/GkkeaySQKhpI8Y7RJXOlueRmFFR9AEHIam6iFitrsvRpaPfWtC1MPZ yQjjSZIafssC0WV6Yf79fiawUHDzEe+rHuG/qwnA X-Google-Smtp-Source: AGHT+IEJfVP8mHBplYr1bYuBEbeWhOM5TJWoX+Biy6G6X+IMXMD36mUjlKHAAOvKfn+2cc24LSpN+A== X-Received: by 2002:a92:c24b:0:b0:3a7:e528:6f1e with SMTP id e9e14a558f8ab-3a811db632dmr35978805ab.11.1733502167552; Fri, 06 Dec 2024 08:22:47 -0800 (PST) Received: from nathan (162-195-168-172.lightspeed.stlsmo.sbcglobal.net. [162.195.168.172]) by smtp.gmail.com with ESMTPSA id 8926c6da1cb9f-4e2861d7347sm885337173.85.2024.12.06.08.22.46 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 06 Dec 2024 08:22:46 -0800 (PST) Date: Fri, 6 Dec 2024 10:22:44 -0600 From: Nathan Bossart To: Joseph Koshakow Cc: Alexander Lakhin , jian he , Heikki Linnakangas , Matthew Kim , Tom Lane , PostgreSQL Hackers , Andres Freund Subject: Re: Remove dependence on integer wrapping Message-ID: References: <9f18f27f-8023-e72d-4c25-95a62117d9fa@gmail.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="ZrvjC8YqyQ/y41IP" Content-Disposition: inline In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --ZrvjC8YqyQ/y41IP Content-Type: text/plain; charset=us-ascii Content-Disposition: inline On Thu, Dec 05, 2024 at 09:58:29PM -0600, Nathan Bossart wrote: > Thanks for reviewing. In v28, I fixed a silly mistake revealed by cfbot's > Windows run. I also went ahead and tried to fix most of the issues > reported in a nearby thread [0]. The only one I haven't tracked down yet > is the "ISO week" one (case 7). The overflow hazard for the ISO week case seems to be in isoweek2j(), which would require quite a bit of restructuring to work with the soft-error handling from its callers. I'm not feeling particularly inspired to do that, so here's a v29 with a comment added above that function. -- nathan --ZrvjC8YqyQ/y41IP Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="v29-0001-Fix-various-overflow-hazards-in-date-and-timesta.patch" From 99032579840c75c15acb88e6d9fb9a8a76a39dcd Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Fri, 6 Dec 2024 10:16:51 -0600 Subject: [PATCH v29 1/1] Fix various overflow hazards in date and timestamp functions. Reported-by: Alexander Lakhin Author: Matthew Kim, Nathan Bossart Reviewed-by: Joseph Koshakow, Jian He Discussion: https://postgr.es/m/31ad2cd1-db94-bdb3-f91a-65ffdb4bef95%40gmail.com Discussion: https://postgr.es/m/18585-db646741dd649abd%40postgresql.org Backpatch-through: 13 --- src/backend/utils/adt/date.c | 9 ++- src/backend/utils/adt/formatting.c | 104 +++++++++++++++++++++++-- src/backend/utils/adt/timestamp.c | 4 + src/include/common/int.h | 48 ++++++++++++ src/test/regress/expected/date.out | 2 + src/test/regress/expected/horology.out | 16 ++++ src/test/regress/sql/date.sql | 1 + src/test/regress/sql/horology.sql | 8 ++ 8 files changed, 183 insertions(+), 9 deletions(-) diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index 8130f3e8ac..da61ac0e86 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -256,8 +256,15 @@ make_date(PG_FUNCTION_ARGS) /* Handle negative years as BC */ if (tm.tm_year < 0) { + int year = tm.tm_year; + bc = true; - tm.tm_year = -tm.tm_year; + if (pg_neg_s32_overflow(year, &year)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_FIELD_OVERFLOW), + errmsg("date field value out of range: %d-%02d-%02d", + tm.tm_year, tm.tm_mon, tm.tm_mday))); + tm.tm_year = year; } dterr = ValidateDate(DTK_DATE_M, false, false, bc, &tm); diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c index 2bcc185708..a9daa5c591 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -77,6 +77,7 @@ #include "catalog/pg_collation.h" #include "catalog/pg_type.h" +#include "common/int.h" #include "common/unicode_case.h" #include "common/unicode_category.h" #include "mb/pg_wchar.h" @@ -3826,7 +3827,14 @@ DCH_from_char(FormatNode *node, const char *in, TmFromChar *out, ereturn(escontext,, (errcode(ERRCODE_INVALID_DATETIME_FORMAT), errmsg("invalid input string for \"Y,YYY\""))); - years += (millennia * 1000); + + /* years += (millennia * 1000); */ + if (pg_mul_s32_overflow(millennia, 1000, &millennia) || + pg_add_s32_overflow(years, millennia, &years)) + ereturn(escontext,, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("value for \"Y,YYY\" in source string is out of range"))); + if (!from_char_set_int(&out->year, years, n, escontext)) return; out->yysz = 4; @@ -4785,10 +4793,35 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std, tm->tm_year = tmfc.year % 100; if (tm->tm_year) { + int tmp; + if (tmfc.cc >= 0) - tm->tm_year += (tmfc.cc - 1) * 100; + { + /* tm->tm_year += (tmfc.cc - 1) * 100; */ + tmp = tmfc.cc - 1; + if (pg_mul_s32_overflow(tmp, 100, &tmp) || + pg_add_s32_overflow(tm->tm_year, tmp, &tm->tm_year)) + { + DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL, + text_to_cstring(date_txt), "timestamp", + escontext); + goto fail; + } + } else - tm->tm_year = (tmfc.cc + 1) * 100 - tm->tm_year + 1; + { + /* tm->tm_year = (tmfc.cc + 1) * 100 - tm->tm_year + 1; */ + tmp = tmfc.cc + 1; + if (pg_mul_s32_overflow(tmp, 100, &tmp) || + pg_sub_s32_overflow(tmp, tm->tm_year, &tmp) || + pg_add_s32_overflow(tmp, 1, &tm->tm_year)) + { + DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL, + text_to_cstring(date_txt), "timestamp", + escontext); + goto fail; + } + } } else { @@ -4814,11 +4847,31 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std, if (tmfc.bc) tmfc.cc = -tmfc.cc; if (tmfc.cc >= 0) + { /* +1 because 21st century started in 2001 */ - tm->tm_year = (tmfc.cc - 1) * 100 + 1; + /* tm->tm_year = (tmfc.cc - 1) * 100 + 1; */ + if (pg_mul_s32_overflow(tmfc.cc - 1, 100, &tm->tm_year) || + pg_add_s32_overflow(tm->tm_year, 1, &tm->tm_year)) + { + DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL, + text_to_cstring(date_txt), "timestamp", + escontext); + goto fail; + } + } else + { /* +1 because year == 599 is 600 BC */ - tm->tm_year = tmfc.cc * 100 + 1; + /* tm->tm_year = tmfc.cc * 100 + 1; */ + if (pg_mul_s32_overflow(tmfc.cc, 100, &tm->tm_year) || + pg_add_s32_overflow(tm->tm_year, 1, &tm->tm_year)) + { + DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL, + text_to_cstring(date_txt), "timestamp", + escontext); + goto fail; + } + } fmask |= DTK_M(YEAR); } @@ -4843,11 +4896,35 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std, fmask |= DTK_DATE_M; } else - tmfc.ddd = (tmfc.ww - 1) * 7 + 1; + { + int tmp = 0; + + /* tmfc.ddd = (tmfc.ww - 1) * 7 + 1; */ + if (pg_sub_s32_overflow(tmfc.ww, 1, &tmp) || + pg_mul_s32_overflow(tmp, 7, &tmp) || + pg_add_s32_overflow(tmp, 1, &tmfc.ddd)) + { + DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL, + date_str, "timestamp", escontext); + goto fail; + } + } } if (tmfc.w) - tmfc.dd = (tmfc.w - 1) * 7 + 1; + { + int tmp = 0; + + /* tmfc.dd = (tmfc.w - 1) * 7 + 1; */ + if (pg_sub_s32_overflow(tmfc.w, 1, &tmp) || + pg_mul_s32_overflow(tmp, 7, &tmp) || + pg_add_s32_overflow(tmp, 1, &tmfc.dd)) + { + DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL, + date_str, "timestamp", escontext); + goto fail; + } + } if (tmfc.dd) { tm->tm_mday = tmfc.dd; @@ -4912,7 +4989,18 @@ do_to_timestamp(text *date_txt, text *fmt, Oid collid, bool std, } if (tmfc.ms) - *fsec += tmfc.ms * 1000; + { + int tmp = 0; + + /* *fsec += tmfc.ms * 1000; */ + if (pg_mul_s32_overflow(tmfc.ms, 1000, &tmp) || + pg_add_s32_overflow(*fsec, tmp, fsec)) + { + DateTimeParseError(DTERR_FIELD_OVERFLOW, NULL, + date_str, "timestamp", escontext); + goto fail; + } + } if (tmfc.us) *fsec += tmfc.us; if (fprec) diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 57fcfefdaf..18d7d8a108 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -5104,6 +5104,10 @@ interval_trunc(PG_FUNCTION_ARGS) * * Return the Julian day which corresponds to the first day (Monday) of the given ISO 8601 year and week. * Julian days are used to convert between ISO week dates and Gregorian dates. + * + * XXX: This function has integer overflow hazards, but restructuring it to + * work with the soft-error handling that its callers do is likely more + * trouble than it's worth. */ int isoweek2j(int year, int week) diff --git a/src/include/common/int.h b/src/include/common/int.h index 3b1590d676..6b50aa67b9 100644 --- a/src/include/common/int.h +++ b/src/include/common/int.h @@ -117,6 +117,22 @@ pg_mul_s16_overflow(int16 a, int16 b, int16 *result) #endif } +static inline bool +pg_neg_s16_overflow(int16 a, int16 *result) +{ +#if defined(HAVE__BUILTIN_OP_OVERFLOW) + return __builtin_sub_overflow(0, a, result); +#else + if (unlikely(a == PG_INT16_MIN)) + { + *result = 0x5EED; /* to avoid spurious warnings */ + return true; + } + *result = -a; + return false; +#endif +} + static inline uint16 pg_abs_s16(int16 a) { @@ -185,6 +201,22 @@ pg_mul_s32_overflow(int32 a, int32 b, int32 *result) #endif } +static inline bool +pg_neg_s32_overflow(int32 a, int32 *result) +{ +#if defined(HAVE__BUILTIN_OP_OVERFLOW) + return __builtin_sub_overflow(0, a, result); +#else + if (unlikely(a == PG_INT32_MIN)) + { + *result = 0x5EED; /* to avoid spurious warnings */ + return true; + } + *result = -a; + return false; +#endif +} + static inline uint32 pg_abs_s32(int32 a) { @@ -300,6 +332,22 @@ pg_mul_s64_overflow(int64 a, int64 b, int64 *result) #endif } +static inline bool +pg_neg_s64_overflow(int64 a, int64 *result) +{ +#if defined(HAVE__BUILTIN_OP_OVERFLOW) + return __builtin_sub_overflow(0, a, result); +#else + if (unlikely(a == PG_INT64_MIN)) + { + *result = 0x5EED; /* to avoid spurious warnings */ + return true; + } + *result = -a; + return false; +#endif +} + static inline uint64 pg_abs_s64(int64 a) { diff --git a/src/test/regress/expected/date.out b/src/test/regress/expected/date.out index c9cec70c38..dcab9e76f4 100644 --- a/src/test/regress/expected/date.out +++ b/src/test/regress/expected/date.out @@ -1528,6 +1528,8 @@ select make_date(2013, 13, 1); ERROR: date field value out of range: 2013-13-01 select make_date(2013, 11, -1); ERROR: date field value out of range: 2013-11--1 +SELECT make_date(-2147483648, 1, 1); +ERROR: date field value out of range: -2147483648-01-01 select make_time(10, 55, 100.1); ERROR: time field value out of range: 10:55:100.1 select make_time(24, 0, 2.1); diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out index 6d7dd5c988..b87b88f038 100644 --- a/src/test/regress/expected/horology.out +++ b/src/test/regress/expected/horology.out @@ -3743,6 +3743,14 @@ SELECT to_timestamp('2015-02-11 86000', 'YYYY-MM-DD SSSSS'); -- ok SELECT to_timestamp('2015-02-11 86400', 'YYYY-MM-DD SSSSS'); ERROR: date/time field value out of range: "2015-02-11 86400" +SELECT to_timestamp('1000000000,999', 'Y,YYY'); +ERROR: value for "Y,YYY" in source string is out of range +SELECT to_timestamp('0.-2147483648', 'SS.MS'); +ERROR: date/time field value out of range: "0.-2147483648" +SELECT to_timestamp('613566758', 'W'); +ERROR: date/time field value out of range: "613566758" +SELECT to_timestamp('2024 613566758 1', 'YYYY WW D'); +ERROR: date/time field value out of range: "2024 613566758 1" SELECT to_date('2016-13-10', 'YYYY-MM-DD'); ERROR: date/time field value out of range: "2016-13-10" SELECT to_date('2016-02-30', 'YYYY-MM-DD'); @@ -3783,6 +3791,14 @@ SELECT to_date('0000-02-01','YYYY-MM-DD'); -- allowed, though it shouldn't be 02-01-0001 BC (1 row) +SELECT to_date('100000000', 'CC'); +ERROR: date/time field value out of range: "100000000" +SELECT to_date('-100000000', 'CC'); +ERROR: date/time field value out of range: "-100000000" +SELECT to_date('-2147483648 01', 'CC YY'); +ERROR: date/time field value out of range: "-2147483648 01" +SELECT to_date('2147483647 01', 'CC YY'); +ERROR: date/time field value out of range: "2147483647 01" -- to_char's TZ format code produces zone abbrev if known SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS TZ'); to_char diff --git a/src/test/regress/sql/date.sql b/src/test/regress/sql/date.sql index 1c58ff6966..805aec706c 100644 --- a/src/test/regress/sql/date.sql +++ b/src/test/regress/sql/date.sql @@ -371,5 +371,6 @@ select make_date(0, 7, 15); select make_date(2013, 2, 30); select make_date(2013, 13, 1); select make_date(2013, 11, -1); +SELECT make_date(-2147483648, 1, 1); select make_time(10, 55, 100.1); select make_time(24, 0, 2.1); diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql index 0fe3c783e6..808083a6d8 100644 --- a/src/test/regress/sql/horology.sql +++ b/src/test/regress/sql/horology.sql @@ -650,6 +650,10 @@ SELECT to_timestamp('2015-02-11 86000', 'YYYY-MM-DD SSSS'); -- ok SELECT to_timestamp('2015-02-11 86400', 'YYYY-MM-DD SSSS'); SELECT to_timestamp('2015-02-11 86000', 'YYYY-MM-DD SSSSS'); -- ok SELECT to_timestamp('2015-02-11 86400', 'YYYY-MM-DD SSSSS'); +SELECT to_timestamp('1000000000,999', 'Y,YYY'); +SELECT to_timestamp('0.-2147483648', 'SS.MS'); +SELECT to_timestamp('613566758', 'W'); +SELECT to_timestamp('2024 613566758 1', 'YYYY WW D'); SELECT to_date('2016-13-10', 'YYYY-MM-DD'); SELECT to_date('2016-02-30', 'YYYY-MM-DD'); SELECT to_date('2016-02-29', 'YYYY-MM-DD'); -- ok @@ -660,6 +664,10 @@ SELECT to_date('2016 365', 'YYYY DDD'); -- ok SELECT to_date('2016 366', 'YYYY DDD'); -- ok SELECT to_date('2016 367', 'YYYY DDD'); SELECT to_date('0000-02-01','YYYY-MM-DD'); -- allowed, though it shouldn't be +SELECT to_date('100000000', 'CC'); +SELECT to_date('-100000000', 'CC'); +SELECT to_date('-2147483648 01', 'CC YY'); +SELECT to_date('2147483647 01', 'CC YY'); -- to_char's TZ format code produces zone abbrev if known SELECT to_char('2012-12-12 12:00'::timestamptz, 'YYYY-MM-DD HH:MI:SS TZ'); -- 2.39.5 (Apple Git-154) --ZrvjC8YqyQ/y41IP--