ICU-23359 Fix use-after-free race in TransliteratorAlias compoundFilter#3913
ICU-23359 Fix use-after-free race in TransliteratorAlias compoundFilter#3913hirorogo wants to merge 1 commit intounicode-org:mainfrom
Conversation
|
@hirorogo Could you please sign the CLA -- otherwise we can't look at your changes -- and create a Jira ticket with the problem description? |
b2507fc to
b8391c6
Compare
|
Hooray! The files in the branch are the same across the force-push. 😃 ~ Your Friendly Jira-GitHub PR Checker Bot |
b8391c6 to
9a89dab
Compare
|
Hooray! The files in the branch are the same across the force-push. 😃 ~ Your Friendly Jira-GitHub PR Checker Bot |
9a89dab to
856bf9c
Compare
|
Hooray! The files in the branch are the same across the force-push. 😃 ~ Your Friendly Jira-GitHub PR Checker Bot |
|
I've restored the PR template, added a unit test ( |
76d69f2 to
fcf4324
Compare
TransliteratorAlias::compoundFilter was stored as a raw alias to the caller-owned filter, leading to use-after-free when the caller destroys its copy. Clone the filter into an owned instance instead. Adds concurrency test exercising concurrent createInstance() + unregister() with a compound filter to verify the fix. See unicode-org#3913
|
Hooray! The files in the branch are the same across the force-push. 😃 ~ Your Friendly Jira-GitHub PR Checker Bot |
|
@markusicu CLA is signed, and I've created the Jira ticket: ICU-23359. The PR title and commit message are updated with the ticket ID. A unit test ( Could the Technical Committee please accept ICU-23359 when you get a chance? The Thank you! |
hirorogo
left a comment
There was a problem hiding this comment.
Reviewed the changes. The fix correctly addresses the root cause — the ownership ambiguity of compoundFilter.
Before this patch, TransliteratorAlias held a raw non-owning pointer to the entry's compoundFilter. Once the registry mutex is released between get() and create(), a concurrent unregister() can delete the entry and leave the alias with a dangling pointer. Cloning in the constructor and taking ownership eliminates this dependency entirely.
A few minor observations:
-
static_cast<UnicodeSet*>(compoundFilter->clone())— sinceclone()returnsUnicodeFilter*, adynamic_castwith a null check would be slightly more defensive. That said, the original code did the same, so this is pre-existing. -
No handling if
clone()returnsnullptron OOM. Looks consistent with other ICU code, just noting it. -
The test's success criterion is "no crash / no sanitizer report", which means it may pass silently without ASan/TSan. Fine if CI runs sanitizer builds.
None of these are blockers.
icu4c/source/i18n/transreg.h
Outdated
| @@ -105,14 +105,14 @@ class TransliteratorAlias : public UMemory { | |||
| // Here ID is the ID, aliasID is the idBlock, trans is the | |||
| // contained RBT, and idSplitPoint is the offset in aliasID | |||
| // where the contained RBT goes. compoundFilter is the | |||
| // compound filter, and it is _not_ owned. | |||
| // compound filter, and it is owned (cloned from entry). | |||
There was a problem hiding this comment.
suggestion: Reword comment to "(cloned from input)", with "input" being clearer than "entry" as referring to the ctor's input parameter.
There was a problem hiding this comment.
Done — updated to "(cloned from input)".
There was a problem hiding this comment.
Done — updated to "(cloned from input)".
3fda95b to
7c545c5
Compare
|
Hooray! The files in the branch are the same across the force-push. 😃 ~ Your Friendly Jira-GitHub PR Checker Bot |
Checklist
Summary
TransliteratorAliasstored a raw non-owning pointer to theTransliteratorEntry'scompoundFilter. When the registry mutex is released betweenregistry->get()andalias->create(), a concurrentTransliterator::unregister()call can delete the entry (and itscompoundFilter) while the alias still holds a dangling pointer, causing a use-after-free.The source comment at
transreg.cpplines 30-36 notes that BoundsChecker has reported dangling pointer errors with these entry objects, corroborating this race condition.Changes
compoundFilterin bothTransliteratorAliasconstructors instead of storing a raw alias pointercompoundFilterin~TransliteratorAlias()const UnicodeSet*(alias) toUnicodeSet*(owned)TestTransliteratorAliasCompoundFilterRaceintsmthred.cppto exercise concurrentcreateInstance()+unregister()with a compound-filter transliteratorTest plan
createInstance+unregisteron the same ID no longer triggers UAF (new test:TestTransliteratorAliasCompoundFilterRace)