ICU-21060 Fix heap-use-after-free bug.
diff --git a/icu4c/source/i18n/measunit_extra.cpp b/icu4c/source/i18n/measunit_extra.cpp
index b997167..e43ee65 100644
--- a/icu4c/source/i18n/measunit_extra.cpp
+++ b/icu4c/source/i18n/measunit_extra.cpp
@@ -321,6 +321,14 @@
class Parser {
public:
+ /**
+ * Factory function for parsing the given identifier.
+ *
+ * @param source The identifier to parse. This function does not make a copy
+ * of source: the underlying string that source points at, must outlive the
+ * parser.
+ * @param status ICU error code.
+ */
static Parser from(StringPiece source, UErrorCode& status) {
if (U_FAILURE(status)) {
return Parser();
@@ -340,6 +348,10 @@
private:
int32_t fIndex = 0;
+
+ // Since we're not owning this memory, whatever is passed to the constructor
+ // should live longer than this Parser - and the parser shouldn't return any
+ // references to that string.
StringPiece fSource;
UCharsTrie fTrie;
@@ -399,7 +411,6 @@
// 1 = power token seen (will not accept another power token)
// 2 = SI prefix token seen (will not accept a power or SI prefix token)
int32_t state = 0;
- int32_t previ = fIndex;
// Maybe read a compound part
if (fIndex != 0) {
@@ -432,7 +443,6 @@
fAfterPer = false;
break;
}
- previ = fIndex;
}
// Read a unit
@@ -449,7 +459,6 @@
return;
}
result.dimensionality *= token.getPower();
- previ = fIndex;
state = 1;
break;
@@ -459,7 +468,6 @@
return;
}
result.siPrefix = token.getSIPrefix();
- previ = fIndex;
state = 2;
break;
@@ -469,7 +477,6 @@
case Token::TYPE_SIMPLE_UNIT:
result.index = token.getSimpleUnitIndex();
- result.identifier = fSource.substr(previ, fIndex - previ);
return;
default:
@@ -576,7 +583,7 @@
return;
}
- output.append(singleUnit.identifier, status);
+ output.appendInvariantChars(gSimpleUnits[singleUnit.index], status);
}
/**
diff --git a/icu4c/source/i18n/measunit_impl.h b/icu4c/source/i18n/measunit_impl.h
index cf0ea63..9657ff2 100644
--- a/icu4c/source/i18n/measunit_impl.h
+++ b/icu4c/source/i18n/measunit_impl.h
@@ -69,9 +69,6 @@
/** Simple unit index, unique for every simple unit. */
int32_t index = 0;
- /** Simple unit identifier; memory not owned by the SimpleUnit. */
- StringPiece identifier;
-
/** SI prefix. **/
UMeasureSIPrefix siPrefix = UMEASURE_SI_PREFIX_ONE;
diff --git a/icu4c/source/test/intltest/measfmttest.cpp b/icu4c/source/test/intltest/measfmttest.cpp
index 9306a56..79eb9b4 100644
--- a/icu4c/source/test/intltest/measfmttest.cpp
+++ b/icu4c/source/test/intltest/measfmttest.cpp
@@ -82,6 +82,7 @@
void TestInvalidIdentifiers();
void TestCompoundUnitOperations();
void TestIdentifiers();
+ void Test21060_AddressSanitizerProblem();
void verifyFormat(
const char *description,
@@ -204,6 +205,7 @@
TESTCASE_AUTO(TestInvalidIdentifiers);
TESTCASE_AUTO(TestCompoundUnitOperations);
TESTCASE_AUTO(TestIdentifiers);
+ TESTCASE_AUTO(Test21060_AddressSanitizerProblem);
TESTCASE_AUTO_END;
}
@@ -3445,6 +3447,36 @@
}
}
+// ICU-21060
+void MeasureFormatTest::Test21060_AddressSanitizerProblem() {
+ UErrorCode status = U_ZERO_ERROR;
+ MeasureUnit first = MeasureUnit::forIdentifier("one", status);
+
+ // Experimentally, a compound unit like "kilogram-meter" failed. A single
+ // unit like "kilogram" or "meter" did not fail, did not trigger the
+ // problem.
+ MeasureUnit crux = MeasureUnit::forIdentifier("one-per-meter", status);
+
+ // Heap allocation of a new CharString for first.identifier happens here:
+ first = first.product(crux, status);
+
+ // Constructing second from first's identifier resulted in a failure later,
+ // as second held a reference to a substring of first's identifier:
+ MeasureUnit second = MeasureUnit::forIdentifier(first.getIdentifier(), status);
+
+ // Heap is freed here, as an old first.identifier CharString is deallocated
+ // and a new CharString is allocated:
+ first = first.product(crux, status);
+
+ // Proving we've had no failure yet:
+ if (U_FAILURE(status)) return;
+
+ // heap-use-after-free failure happened here, since a SingleUnitImpl had
+ // held onto a StringPiece pointing at a substring of an identifier that was
+ // freed above:
+ second = second.product(crux, status);
+}
+
void MeasureFormatTest::verifyFieldPosition(
const char *description,