Skip to content

Commit 303b7e8

Browse files
committed
ICU-22897 Fix memory leak and int overflow
1. Rewrite to use LocalPointer to prevent memory leak 2. Rewrite the if/else to switch to make the logic clear 3. Delete the rule if not remember inside the rule set to fix memory leak. 4. Check base value calculation to avoid int64_t overflow. 5. Add memory leak test
1 parent ce4b90e commit 303b7e8

File tree

4 files changed

+58
-32
lines changed

4 files changed

+58
-32
lines changed

icu4c/source/i18n/nfrs.cpp

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -267,27 +267,35 @@ NFRuleSet::parseRules(UnicodeString& description, UErrorCode& status)
267267
* @param rule The rule to set.
268268
*/
269269
void NFRuleSet::setNonNumericalRule(NFRule *rule) {
270-
int64_t baseValue = rule->getBaseValue();
271-
if (baseValue == NFRule::kNegativeNumberRule) {
272-
delete nonNumericalRules[NEGATIVE_RULE_INDEX];
273-
nonNumericalRules[NEGATIVE_RULE_INDEX] = rule;
274-
}
275-
else if (baseValue == NFRule::kImproperFractionRule) {
276-
setBestFractionRule(IMPROPER_FRACTION_RULE_INDEX, rule, true);
277-
}
278-
else if (baseValue == NFRule::kProperFractionRule) {
279-
setBestFractionRule(PROPER_FRACTION_RULE_INDEX, rule, true);
280-
}
281-
else if (baseValue == NFRule::kDefaultRule) {
282-
setBestFractionRule(DEFAULT_RULE_INDEX, rule, true);
283-
}
284-
else if (baseValue == NFRule::kInfinityRule) {
285-
delete nonNumericalRules[INFINITY_RULE_INDEX];
286-
nonNumericalRules[INFINITY_RULE_INDEX] = rule;
287-
}
288-
else if (baseValue == NFRule::kNaNRule) {
289-
delete nonNumericalRules[NAN_RULE_INDEX];
290-
nonNumericalRules[NAN_RULE_INDEX] = rule;
270+
switch (rule->getBaseValue()) {
271+
case NFRule::kNegativeNumberRule:
272+
delete nonNumericalRules[NEGATIVE_RULE_INDEX];
273+
nonNumericalRules[NEGATIVE_RULE_INDEX] = rule;
274+
return;
275+
case NFRule::kImproperFractionRule:
276+
setBestFractionRule(IMPROPER_FRACTION_RULE_INDEX, rule, true);
277+
return;
278+
case NFRule::kProperFractionRule:
279+
setBestFractionRule(PROPER_FRACTION_RULE_INDEX, rule, true);
280+
return;
281+
case NFRule::kDefaultRule:
282+
setBestFractionRule(DEFAULT_RULE_INDEX, rule, true);
283+
return;
284+
case NFRule::kInfinityRule:
285+
delete nonNumericalRules[INFINITY_RULE_INDEX];
286+
nonNumericalRules[INFINITY_RULE_INDEX] = rule;
287+
return;
288+
case NFRule::kNaNRule:
289+
delete nonNumericalRules[NAN_RULE_INDEX];
290+
nonNumericalRules[NAN_RULE_INDEX] = rule;
291+
return;
292+
case NFRule::kNoBase:
293+
case NFRule::kOtherRule:
294+
default:
295+
// If we do not remember the rule inside the object.
296+
// delete it here to prevent memory leak.
297+
delete rule;
298+
return;
291299
}
292300
}
293301

icu4c/source/i18n/nfrule.cpp

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#if U_HAVE_RBNF
2121

22+
#include <limits>
2223
#include "unicode/localpointer.h"
2324
#include "unicode/rbnf.h"
2425
#include "unicode/tblcoll.h"
@@ -116,9 +117,9 @@ NFRule::makeRules(UnicodeString& description,
116117
// new it up and initialize its basevalue and divisor
117118
// (this also strips the rule descriptor, if any, off the
118119
// description string)
119-
NFRule* rule1 = new NFRule(rbnf, description, status);
120+
LocalPointer<NFRule> rule1(new NFRule(rbnf, description, status));
120121
/* test for nullptr */
121-
if (rule1 == nullptr) {
122+
if (rule1.isNull()) {
122123
status = U_MEMORY_ALLOCATION_ERROR;
123124
return;
124125
}
@@ -144,7 +145,7 @@ NFRule::makeRules(UnicodeString& description,
144145
else {
145146
// if the description does contain a matched pair of brackets,
146147
// then it's really shorthand for two rules (with one exception)
147-
NFRule* rule2 = nullptr;
148+
LocalPointer<NFRule> rule2;
148149
UnicodeString sbuf;
149150

150151
// we'll actually only split the rule into two rules if its
@@ -160,9 +161,9 @@ NFRule::makeRules(UnicodeString& description,
160161
// set, they both have the same base value; otherwise,
161162
// increment the original rule's base value ("rule1" actually
162163
// goes SECOND in the rule set's rule list)
163-
rule2 = new NFRule(rbnf, UnicodeString(), status);
164+
rule2.adoptInstead(new NFRule(rbnf, UnicodeString(), status));
164165
/* test for nullptr */
165-
if (rule2 == nullptr) {
166+
if (rule2.isNull()) {
166167
status = U_MEMORY_ALLOCATION_ERROR;
167168
return;
168169
}
@@ -217,20 +218,20 @@ NFRule::makeRules(UnicodeString& description,
217218
// BEFORE rule1 in the list: in all cases, rule2 OMITS the
218219
// material in the brackets and rule1 INCLUDES the material
219220
// in the brackets)
220-
if (rule2 != nullptr) {
221+
if (!rule2.isNull()) {
221222
if (rule2->baseValue >= kNoBase) {
222-
rules.add(rule2);
223+
rules.add(rule2.orphan());
223224
}
224225
else {
225-
owner->setNonNumericalRule(rule2);
226+
owner->setNonNumericalRule(rule2.orphan());
226227
}
227228
}
228229
}
229230
if (rule1->baseValue >= kNoBase) {
230-
rules.add(rule1);
231+
rules.add(rule1.orphan());
231232
}
232233
else {
233-
owner->setNonNumericalRule(rule1);
234+
owner->setNonNumericalRule(rule1.orphan());
234235
}
235236
}
236237

@@ -289,7 +290,14 @@ NFRule::parseRuleDescriptor(UnicodeString& description, UErrorCode& status)
289290
while (p < descriptorLength) {
290291
c = descriptor.charAt(p);
291292
if (c >= gZero && c <= gNine) {
292-
val = val * ll_10 + static_cast<int32_t>(c - gZero);
293+
int32_t single_digit = static_cast<int32_t>(c - gZero);
294+
if ((val > 0 && val > (std::numeric_limits<int64_t>::max() - single_digit) / 10) ||
295+
(val < 0 && val < (std::numeric_limits<int64_t>::min() - single_digit) / 10)) {
296+
// out of int64_t range
297+
status = U_PARSE_ERROR;
298+
return;
299+
}
300+
val = val * ll_10 + single_digit;
293301
}
294302
else if (c == gSlash || c == gGreaterThan) {
295303
break;

icu4c/source/test/intltest/itrbnf.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ void IntlTestRBNF::runIndexedTest(int32_t index, UBool exec, const char* &name,
8080
TESTCASE(28, TestNorwegianSpellout);
8181
TESTCASE(29, TestNumberingSystem);
8282
TESTCASE(30, TestDFRounding);
83+
TESTCASE(31, TestMemoryLeak22899);
8384
#else
8485
TESTCASE(0, TestRBNFDisabled);
8586
#endif
@@ -1340,6 +1341,14 @@ void IntlTestRBNF::TestDFRounding()
13401341
}
13411342
}
13421343

1344+
void IntlTestRBNF::TestMemoryLeak22899()
1345+
{
1346+
UErrorCode status = U_ZERO_ERROR;
1347+
UParseError perror;
1348+
icu::UnicodeString str(u"0,31,01,30,01,0,01,01,30,01,30,31,01,30,01,30,30,00,01,0:");
1349+
icu::RuleBasedNumberFormat rbfmt(str, icu::Locale::getEnglish(), perror, status);
1350+
}
1351+
13431352
void
13441353
IntlTestRBNF::TestSpanishSpellout()
13451354
{

icu4c/source/test/intltest/itrbnf.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ class IntlTestRBNF : public IntlTest {
161161
void TestParseFailure();
162162
void TestMinMaxIntegerDigitsIgnored();
163163
void TestNumberingSystem();
164+
void TestMemoryLeak22899();
164165

165166
protected:
166167
virtual void doTest(RuleBasedNumberFormat* formatter, const char* const testData[][2], UBool testParsing);

0 commit comments

Comments
 (0)