From 9d1a1c37155a195d79ed8d8f6bfc8006f1ec576c Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Tue, 5 Aug 2025 19:38:15 +0200 Subject: [PATCH 1/3] Fix GH-19371: integer overflow in calendar.c --- ext/calendar/calendar.c | 25 ++++++++ .../tests/cal_days_in_month_error1.phpt | 2 +- ext/calendar/tests/gh19371.phpt | 59 +++++++++++++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 ext/calendar/tests/gh19371.phpt diff --git a/ext/calendar/calendar.c b/ext/calendar/calendar.c index 6da7e69529e2e..5d576de68778d 100644 --- a/ext/calendar/calendar.c +++ b/ext/calendar/calendar.c @@ -194,6 +194,16 @@ PHP_FUNCTION(cal_days_in_month) RETURN_THROWS(); } + if (UNEXPECTED(month <= 0 || month > INT32_MAX - 1)) { + zend_argument_value_error(2, "must be between 0 and %d", INT32_MAX - 1); + RETURN_THROWS(); + } + + if (UNEXPECTED(year > INT32_MAX - 1)) { + zend_argument_value_error(3, "must be less than %d", INT32_MAX - 1); + RETURN_THROWS(); + } + calendar = &cal_conversion_table[cal]; sdn_start = calendar->to_jd(year, month, 1); @@ -239,6 +249,21 @@ PHP_FUNCTION(cal_to_jd) RETURN_THROWS(); } + if (UNEXPECTED(month <= 0 || month > INT32_MAX - 1)) { + zend_argument_value_error(2, "must be between 0 and %d", INT32_MAX - 1); + RETURN_THROWS(); + } + + if (UNEXPECTED(ZEND_LONG_EXCEEDS_INT(day))) { + zend_argument_value_error(3, "must be between %d and %d", INT32_MIN, INT32_MAX); + RETURN_THROWS(); + } + + if (UNEXPECTED(year > INT32_MAX - 1)) { + zend_argument_value_error(4, "must be less than %d", INT32_MAX - 1); + RETURN_THROWS(); + } + RETURN_LONG(cal_conversion_table[cal].to_jd(year, month, day)); } /* }}} */ diff --git a/ext/calendar/tests/cal_days_in_month_error1.phpt b/ext/calendar/tests/cal_days_in_month_error1.phpt index f334888479f20..e110c13cc2a78 100644 --- a/ext/calendar/tests/cal_days_in_month_error1.phpt +++ b/ext/calendar/tests/cal_days_in_month_error1.phpt @@ -12,7 +12,7 @@ try { echo "{$ex->getMessage()}\n"; } try{ - cal_days_in_month(CAL_GREGORIAN,0, 2009); + cal_days_in_month(CAL_GREGORIAN,20, 2009); } catch (ValueError $ex) { echo "{$ex->getMessage()}\n"; } diff --git a/ext/calendar/tests/gh19371.phpt b/ext/calendar/tests/gh19371.phpt new file mode 100644 index 0000000000000..c747b0848a6f2 --- /dev/null +++ b/ext/calendar/tests/gh19371.phpt @@ -0,0 +1,59 @@ +--TEST-- +GH-19371 (integer overflow in calendar.c) +--EXTENSIONS-- +calendar +--FILE-- +getMessage(), "\n"; +} +try { + echo cal_days_in_month(CAL_GREGORIAN, PHP_INT_MIN, 1); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + echo cal_days_in_month(CAL_GREGORIAN, PHP_INT_MAX, 1); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +try { + echo cal_to_jd(CAL_GREGORIAN, PHP_INT_MIN, 1, 1); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + echo cal_to_jd(CAL_GREGORIAN, PHP_INT_MAX, 1, 1); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + echo cal_to_jd(CAL_GREGORIAN, 1, PHP_INT_MIN, 1); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + echo cal_to_jd(CAL_GREGORIAN, 1, PHP_INT_MAX, 1); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + echo cal_to_jd(CAL_GREGORIAN, 1, 1, PHP_INT_MAX); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +cal_days_in_month(): Argument #3 ($year) must be less than 2147483646 +cal_days_in_month(): Argument #2 ($month) must be between 0 and 2147483646 +cal_days_in_month(): Argument #2 ($month) must be between 0 and 2147483646 +cal_to_jd(): Argument #2 ($month) must be between 0 and 2147483646 +cal_to_jd(): Argument #2 ($month) must be between 0 and 2147483646 +cal_to_jd(): Argument #3 ($day) must be between -2147483648 and 2147483647 +cal_to_jd(): Argument #3 ($day) must be between -2147483648 and 2147483647 +cal_to_jd(): Argument #4 ($year) must be less than 2147483646 From 7af68f1a77b0a5ff4cd07256f801bd1b21872d38 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Tue, 5 Aug 2025 20:00:26 +0200 Subject: [PATCH 2/3] SKIPIF --- ext/calendar/tests/gh19371.phpt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/calendar/tests/gh19371.phpt b/ext/calendar/tests/gh19371.phpt index c747b0848a6f2..a1839b9fb5db7 100644 --- a/ext/calendar/tests/gh19371.phpt +++ b/ext/calendar/tests/gh19371.phpt @@ -1,5 +1,7 @@ --TEST-- GH-19371 (integer overflow in calendar.c) +--SKIPIF-- + --EXTENSIONS-- calendar --FILE-- From 7d9a061fde97deaf709e32cc84d40c08dff7a24c Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 6 Aug 2025 22:37:50 +0200 Subject: [PATCH 3/3] Tweak 'between' wording (our 'between' is inclusive) --- ext/calendar/calendar.c | 4 ++-- ext/calendar/tests/gh19371.phpt | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ext/calendar/calendar.c b/ext/calendar/calendar.c index 5d576de68778d..b387b20c09b18 100644 --- a/ext/calendar/calendar.c +++ b/ext/calendar/calendar.c @@ -195,7 +195,7 @@ PHP_FUNCTION(cal_days_in_month) } if (UNEXPECTED(month <= 0 || month > INT32_MAX - 1)) { - zend_argument_value_error(2, "must be between 0 and %d", INT32_MAX - 1); + zend_argument_value_error(2, "must be between 1 and %d", INT32_MAX - 1); RETURN_THROWS(); } @@ -250,7 +250,7 @@ PHP_FUNCTION(cal_to_jd) } if (UNEXPECTED(month <= 0 || month > INT32_MAX - 1)) { - zend_argument_value_error(2, "must be between 0 and %d", INT32_MAX - 1); + zend_argument_value_error(2, "must be between 1 and %d", INT32_MAX - 1); RETURN_THROWS(); } diff --git a/ext/calendar/tests/gh19371.phpt b/ext/calendar/tests/gh19371.phpt index a1839b9fb5db7..1d807a9838867 100644 --- a/ext/calendar/tests/gh19371.phpt +++ b/ext/calendar/tests/gh19371.phpt @@ -52,10 +52,10 @@ try { ?> --EXPECT-- cal_days_in_month(): Argument #3 ($year) must be less than 2147483646 -cal_days_in_month(): Argument #2 ($month) must be between 0 and 2147483646 -cal_days_in_month(): Argument #2 ($month) must be between 0 and 2147483646 -cal_to_jd(): Argument #2 ($month) must be between 0 and 2147483646 -cal_to_jd(): Argument #2 ($month) must be between 0 and 2147483646 +cal_days_in_month(): Argument #2 ($month) must be between 1 and 2147483646 +cal_days_in_month(): Argument #2 ($month) must be between 1 and 2147483646 +cal_to_jd(): Argument #2 ($month) must be between 1 and 2147483646 +cal_to_jd(): Argument #2 ($month) must be between 1 and 2147483646 cal_to_jd(): Argument #3 ($day) must be between -2147483648 and 2147483647 cal_to_jd(): Argument #3 ($day) must be between -2147483648 and 2147483647 cal_to_jd(): Argument #4 ($year) must be less than 2147483646