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 1tJPUG-002opw-2W for pgsql-hackers@arkaria.postgresql.org; Fri, 06 Dec 2024 03:58:40 +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 1tJPUD-009pFa-9L for pgsql-hackers@arkaria.postgresql.org; Fri, 06 Dec 2024 03:58:38 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1tJPUC-009pFR-Th for pgsql-hackers@lists.postgresql.org; Fri, 06 Dec 2024 03:58:38 +0000 Received: from mail-il1-x131.google.com ([2607:f8b0:4864:20::131]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.94.2) (envelope-from ) id 1tJPUA-001IIU-2C for pgsql-hackers@lists.postgresql.org; Fri, 06 Dec 2024 03:58:37 +0000 Received: by mail-il1-x131.google.com with SMTP id e9e14a558f8ab-3a7e108b491so11935175ab.3 for ; Thu, 05 Dec 2024 19:58:34 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1733457513; x=1734062313; darn=lists.postgresql.org; h=in-reply-to:content-transfer-encoding:content-disposition :mime-version:references:message-id:subject:cc:to:from:date:from:to :cc:subject:date:message-id:reply-to; bh=yODXIsiHLLhQScIWnTJBjjRPNiFSB376d12Tv6eDSUE=; b=iGfJj0YAMUfpnZCxmgJHPgwceSUuOVqE8+LArU/BqmKue3FVtBtEUKc5yJGuDRLDSQ RW2oyRvSaAvJ1SfF6Zzug3Ds0Uw1WG/lOv6BLnKVdFu94WwJ5TY8bDmDilKCalS7NCUu g+z/xH6yMX1lVS+ZSrn9DwthcYnbRNt2sLuiVs2z/vO4JgsQV4soxV5NfYdcOR8nyXJW th3VU0Uz74TfgJ6dippfbYQL4OLSWnxpbN6cdawJmngSiWaeYP/Ak1URNkaOmu00gB2P vxu5f2SLbnAkagYcRV8PlQM+4KYQLfjd7BM4QTA6wjQFQjGvqZjh/SWbIVQ7dvNIBfzh f3hQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1733457513; x=1734062313; h=in-reply-to:content-transfer-encoding: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=yODXIsiHLLhQScIWnTJBjjRPNiFSB376d12Tv6eDSUE=; b=BICQYXjCG70fkQKCb6zG/tiO+/ETqdvqZy5HT/lHTqoDcz9WY4n08YVNXM3oLWOOVM 50ZEjPBzK8Wn2oRoTCtSDyWC8Io0zdLtiLKs7ex86YJUqvK5bzZItyMQclxcA9P+R7S5 AzuUcdTFAhTKTZmd/itu2OzYMJxGZ/CyIoxA46qToFjqacg38cz4Q51Rwz1pRfNrTQhW 14ZYV8nUzUfhsF0GFNDYNL9nPDzOKrIPKASwlniaKTWCVOEDRS+s7pAMx7Am1Gh4ZH1x NuoYmQCfnHmiMdqhJL7HjsUzW95Xnc7Do7iwjl129iH5jdj/3Mtl8Xu/TyvsiVSFfsnp 8bXA== X-Forwarded-Encrypted: i=1; AJvYcCWwCxtaPtpjhHvZ408ti1RJ2Lx+ekVZmhPCd737ngTUSyT4oin92o+VuKU34vN2XH/SRwzTlZTFmrZAy5Jk@lists.postgresql.org X-Gm-Message-State: AOJu0Yy3iqQUBw89nw6z6Uy5YljE+PWDh6f0K2qRK1g/OdsuFkL/fijD bQQxCY0WPcPAPheNwxcFEOwtfeuaRKxIEKAhyYD+zKXQrHIDsFhO X-Gm-Gg: ASbGncs5Ze9QAg1tKyRPwSeAkWOQ56SbQ2MVvQ2ISHEPC+N8vo2w2YTEe3ZxSDM4ff1 d2EZpKrZ7Eib4NYMs+8/Q9Efak8XSfamORVGQQ51Isw6BzhqmcVa2ptY2XTgImZqRh1SzphBvN6 4cB+LK/sd8AWrfcvACb9ys9yswRI4OPLU+Q+J0Dk1202JZA/7vtQ++qyjI92I/Zuph+d7EpiR4y zqu/IPION/oiFWv9h+2Hka4JKkIZEXGyQWjwQH3ZBPC+dMz0e0H5HdGDC8xlL3VEG+PQvYW957F ftUKxHkuYqQsYSqFia8+vgnDMR7oWn6znAgQAaoz X-Google-Smtp-Source: AGHT+IGbxMU2R1O75GjnaZrSzGdmVDnuvHgH5ClQGL3FA5qhCg6xqVa9sObSuldGvsXc3eoggcEc+A== X-Received: by 2002:a05:6e02:1c4e:b0:3a7:d792:d6e9 with SMTP id e9e14a558f8ab-3a811e14c3bmr18416635ab.22.1733457512731; Thu, 05 Dec 2024 19:58:32 -0800 (PST) Received: from nathan (162-195-168-172.lightspeed.stlsmo.sbcglobal.net. [162.195.168.172]) by smtp.gmail.com with ESMTPSA id e9e14a558f8ab-3a808d8f49esm8294695ab.2.2024.12.05.19.58.31 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 05 Dec 2024 19:58:31 -0800 (PST) Date: Thu, 5 Dec 2024 21:58:29 -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="9xOb9WEPcGfQOAb9" Content-Disposition: inline Content-Transfer-Encoding: 8bit In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --9xOb9WEPcGfQOAb9 Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: 8bit On Thu, Dec 05, 2024 at 08:00:12PM -0500, Joseph Koshakow wrote: > On Thu, Dec 5, 2024 at 5:50 PM Nathan Bossart wrote: >> Good point. Here is a v27 patch that extracts the bug fix portions of the >> v26 patch. If/when this is committed, I think we should close the >> commitfest entry. > > I looked through this patch and it looks good to me, assuming cfbot is > happy. Also I agree, we should close the commitfest entry. 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). [0] https://postgr.es/m/18585-db646741dd649abd%40postgresql.org -- nathan --9xOb9WEPcGfQOAb9 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="v28-0001-Fix-various-overflow-hazards-in-date-and-timesta.patch" From c96de41b95f8380ba7ff15c9baef36713be6a43c Mon Sep 17 00:00:00 2001 From: Nathan Bossart Date: Thu, 5 Dec 2024 21:50:50 -0600 Subject: [PATCH v28 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 --- src/backend/utils/adt/date.c | 9 ++- src/backend/utils/adt/formatting.c | 104 +++++++++++++++++++++++-- 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 ++ 7 files changed, 179 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/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) --9xOb9WEPcGfQOAb9--