From 6c84bff2e8ccba93eb8d77c06c3f23c6d5c22c7e Mon Sep 17 00:00:00 2001 From: Naveed Khan Date: Fri, 10 Jul 2026 20:17:21 +0530 Subject: [PATCH] reject RBNF plural rules whose divisor overflows to zero A slash divisor descriptor with a $(cardinal,...)$ body sets the plural rule pattern without creating a modulus or integral-part substitution, so the zero-divisor checks in those substitutions never run. When radix^exponent overflows 64 bits the power helper wraps to 0 and doFormat divides the number by it: SIGFPE in ICU4C, ArithmeticException in ICU4J. Reject the rule with U_PARSE_ERROR / IllegalStateException when the plural format is built with a non-positive divisor (overflow can also wrap negative), mirroring the existing substitution guards. Tests added for both C++ (TestDividedByZero) and Java (TestDivisorOverflow). --- icu4c/source/i18n/nfrule.cpp | 7 +++++++ icu4c/source/test/intltest/itrbnf.cpp | 9 +++++++++ .../java/com/ibm/icu/dev/test/format/RbnfTest.java | 14 ++++++++++++++ .../src/main/java/com/ibm/icu/text/NFRule.java | 6 ++++++ 4 files changed, 36 insertions(+) diff --git a/icu4c/source/i18n/nfrule.cpp b/icu4c/source/i18n/nfrule.cpp index 0078159af82b..dd67bcc2330b 100644 --- a/icu4c/source/i18n/nfrule.cpp +++ b/icu4c/source/i18n/nfrule.cpp @@ -504,6 +504,13 @@ NFRule::extractSubstitutions(const NFRuleSet* owner, } rulePatternFormat = formatter->createPluralFormat(pluralType, ruleText.tempSubString(endType + 1, pluralRuleEnd - endType - 1), status); + if (U_SUCCESS(status) && getDivisor() <= 0) { + // radix^exponent overflowed 64 bits, so doFormat() would divide the + // number by a zero (or wrapped-negative) divisor. Reject the rule, like + // the modulus and integral-part substitutions already do for a zero divisor. + status = U_PARSE_ERROR; + return; + } } } diff --git a/icu4c/source/test/intltest/itrbnf.cpp b/icu4c/source/test/intltest/itrbnf.cpp index ad7ad8f8d266..9a2a6b67e314 100644 --- a/icu4c/source/test/intltest/itrbnf.cpp +++ b/icu4c/source/test/intltest/itrbnf.cpp @@ -2834,6 +2834,15 @@ IntlTestRBNF::TestDividedByZero() { UErrorCode status = U_ZERO_ERROR; RuleBasedNumberFormat rbnf(u"7060920374060940374/4:[]", Locale::getUS(), perror, status); assertEquals("base is too large", U_NUMBER_ARG_OUTOFBOUNDS_ERROR, status); + + // A $(cardinal,...)$ rule whose radix^exponent overflows 64 bits: the + // divisor is zero, so doFormat() would divide the number by it. The rule + // must be rejected at construction instead of dividing by zero. + status = U_ZERO_ERROR; + RuleBasedNumberFormat pluralDivisor( + u"0: zero;\n4611686018427387904/16: big $(cardinal,one{a}other{b})$;", + Locale::getEnglish(), perror, status); + assertEquals("zero divisor from overflow", U_PARSE_ERROR, status); } void diff --git a/icu4j/main/common_tests/src/test/java/com/ibm/icu/dev/test/format/RbnfTest.java b/icu4j/main/common_tests/src/test/java/com/ibm/icu/dev/test/format/RbnfTest.java index 4b5535583286..e4c0161ac91e 100644 --- a/icu4j/main/common_tests/src/test/java/com/ibm/icu/dev/test/format/RbnfTest.java +++ b/icu4j/main/common_tests/src/test/java/com/ibm/icu/dev/test/format/RbnfTest.java @@ -2202,6 +2202,20 @@ public void TestInfiniteRecursion() { } } + @Test + public void TestDivisorOverflow() { + // A $(cardinal,...)$ rule whose radix^exponent overflows 64 bits: the divisor is + // zero, so doFormat() would divide the number by it (ArithmeticException: / by zero). + // The rule must be rejected at construction instead, like a zero-divisor substitution. + try { + new RuleBasedNumberFormat( + "0: zero;\n4611686018427387904/16: big $(cardinal,one{a}other{b})$;"); + errln("Rule with a zero divisor from overflow should have been rejected"); + } catch (IllegalStateException e) { + // expected + } + } + /** * This test is a little contrived for English, but the grammar is relevant for several * languages, including: Latin, Germanic, Slavic and Indic. It's pretty common, especially for diff --git a/icu4j/main/core/src/main/java/com/ibm/icu/text/NFRule.java b/icu4j/main/core/src/main/java/com/ibm/icu/text/NFRule.java index 2d633503344a..f43d325d3cf4 100644 --- a/icu4j/main/core/src/main/java/com/ibm/icu/text/NFRule.java +++ b/icu4j/main/core/src/main/java/com/ibm/icu/text/NFRule.java @@ -412,6 +412,12 @@ private void extractSubstitutions(NFRuleSet owner, String sourceRuleText, NFRule rulePatternFormat = formatter.createPluralFormat( pluralType, ruleText.substring(endType + 1, pluralRuleEnd)); + if (getDivisor() <= 0) { + // radix^exponent overflowed 64 bits, so doFormat() would divide the + // number by a zero (or wrapped-negative) divisor. Reject the rule, like + // the modulus and integral-part substitutions already do for a zero divisor. + throw new IllegalStateException("Rule with divisor " + getDivisor()); + } } }