From f11ca0d3630caa149a2c71391536f16574c5d054 Mon Sep 17 00:00:00 2001 From: Shane Carr <shane@unicode.org> Date: Thu, 19 Apr 2018 01:13:17 +0000 Subject: [PATCH] ICU-13634 Fixing address sanitizer issue involving backwards-compatible UChar* behavior in CurrencyUnit constructor. The string passed to the constructor need not be NUL-terminated. X-SVN-Rev: 41248 --- icu4c/source/i18n/currunit.cpp | 17 ++++++++++++----- icu4c/source/i18n/decimfmt.cpp | 1 - icu4c/source/i18n/number_skeletons.cpp | 8 ++++++-- icu4c/source/i18n/unicode/currunit.h | 5 +++-- .../test/intltest/numbertest_skeletons.cpp | 2 ++ .../icu/dev/test/number/NumberSkeletonTest.java | 2 ++ 6 files changed, 25 insertions(+), 10 deletions(-) diff --git a/icu4c/source/i18n/currunit.cpp b/icu4c/source/i18n/currunit.cpp index 003ce612109..5a6ff6e7e19 100644 --- a/icu4c/source/i18n/currunit.cpp +++ b/icu4c/source/i18n/currunit.cpp @@ -17,6 +17,7 @@ #include "unicode/currunit.h" #include "unicode/ustring.h" #include "cstring.h" +#include "uinvchar.h" static constexpr char16_t kDefaultCurrency[] = u"XXX"; @@ -24,14 +25,20 @@ U_NAMESPACE_BEGIN CurrencyUnit::CurrencyUnit(ConstChar16Ptr _isoCode, UErrorCode& ec) { // The constructor always leaves the CurrencyUnit in a valid state (with a 3-character currency code). + // Note: in ICU4J Currency.getInstance(), we check string length for 3, but in ICU4C we allow a + // non-NUL-terminated string to be passed as an argument, so it is not possible to check length. + const char16_t* isoCodeToUse; if (U_FAILURE(ec) || _isoCode == nullptr) { - u_strcpy(isoCode, kDefaultCurrency); - } else if (u_strlen(_isoCode) != 3) { - u_strcpy(isoCode, kDefaultCurrency); - ec = U_ILLEGAL_ARGUMENT_ERROR; + isoCodeToUse = kDefaultCurrency; + } else if (!uprv_isInvariantUString(_isoCode, 3)) { + // TODO: Perform a more strict ASCII check like in ICU4J isAlpha3Code? + isoCodeToUse = kDefaultCurrency; + ec = U_INVARIANT_CONVERSION_ERROR; } else { - u_strcpy(isoCode, _isoCode); + isoCodeToUse = _isoCode; } + u_strncpy(isoCode, isoCodeToUse, 3); + isoCode[3] = 0; char simpleIsoCode[4]; u_UCharsToChars(isoCode, simpleIsoCode, 4); initCurrency(simpleIsoCode); diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index c7192dc4691..42cd90233bf 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -992,7 +992,6 @@ void DecimalFormat::setCurrency(const char16_t* theCurrency, UErrorCode& ec) { void DecimalFormat::setCurrency(const char16_t* theCurrency) { ErrorCode localStatus; - NumberFormat::setCurrency(theCurrency, localStatus); // to set field for compatibility setCurrency(theCurrency, localStatus); } diff --git a/icu4c/source/i18n/number_skeletons.cpp b/icu4c/source/i18n/number_skeletons.cpp index d921b547e13..e998e8e51c3 100644 --- a/icu4c/source/i18n/number_skeletons.cpp +++ b/icu4c/source/i18n/number_skeletons.cpp @@ -800,8 +800,12 @@ blueprint_helpers::parseExponentSignOption(const StringSegment& segment, MacroPr void blueprint_helpers::parseCurrencyOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status) { - // Can't use toTempUnicodeString() because getTerminatedBuffer is non-const - const UChar* currencyCode = segment.toUnicodeString().getTerminatedBuffer(); + // Unlike ICU4J, have to check length manually because ICU4C CurrencyUnit does not check it for us + if (segment.length() != 3) { + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return; + } + const UChar* currencyCode = segment.toTempUnicodeString().getBuffer(); UErrorCode localStatus = U_ZERO_ERROR; CurrencyUnit currency(currencyCode, localStatus); if (U_FAILURE(localStatus)) { diff --git a/icu4c/source/i18n/unicode/currunit.h b/icu4c/source/i18n/unicode/currunit.h index 5ad23b1f58a..ca90acb7918 100644 --- a/icu4c/source/i18n/unicode/currunit.h +++ b/icu4c/source/i18n/unicode/currunit.h @@ -44,8 +44,9 @@ class U_I18N_API CurrencyUnit: public MeasureUnit { /** * Construct an object with the given ISO currency code. - * @param isoCode the 3-letter ISO 4217 currency code; must not be - * NULL and must have length 3 + * @param isoCode the 3-letter ISO 4217 currency code; must have + * length 3 and need not be NUL-terminated. If NULL, the currency + * is initialized to the unknown currency XXX. * @param ec input-output error code. If the isoCode is invalid, * then this will be set to a failing value. * @stable ICU 3.0 diff --git a/icu4c/source/test/intltest/numbertest_skeletons.cpp b/icu4c/source/test/intltest/numbertest_skeletons.cpp index 33de5c311b2..59ad630ce9c 100644 --- a/icu4c/source/test/intltest/numbertest_skeletons.cpp +++ b/icu4c/source/test/intltest/numbertest_skeletons.cpp @@ -68,6 +68,7 @@ void NumberSkeletonTest::validTokens() { u"measure-unit/energy-joule per-measure-unit/length-meter", u"currency/XXX", u"currency/ZZZ", + u"currency/usd", u"group-off", u"group-min2", u"group-auto", @@ -137,6 +138,7 @@ void NumberSkeletonTest::invalidTokens() { u"scale/0.1.2", u"scale/français", // non-invariant characters for C++ u"currency/dummy", + u"currency/ççç", // three characters but not ASCII u"measure-unit/foo", u"integer-width/xxx", u"integer-width/0+", diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java index 7c7349b5e99..6e854dfbf2c 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java @@ -60,6 +60,7 @@ public class NumberSkeletonTest { "measure-unit/energy-joule per-measure-unit/length-meter", "currency/XXX", "currency/ZZZ", + "currency/usd", "group-off", "group-min2", "group-auto", @@ -131,6 +132,7 @@ public class NumberSkeletonTest { "scale/0.1.2", "scale/français", // non-invariant characters for C++ "currency/dummy", + "currency/ççç", // three characters but not ASCII "measure-unit/foo", "integer-width/xxx", "integer-width/0+", -- 2.40.0