Skip to content

Wrapper.Cleanup() leaves outstanding ICU handles dangling, causing AccessViolationException #237

Description

@imnasnainaec

Describe the bug

Wrapper.Cleanup() unloads the native ICU libraries but leaves every outstanding ICU
handle pointing into the unloaded module. Using any object created before the call — a
RuleBasedCollator, a break iterator, a transliterator — then dereferences freed memory
and takes down the process with an AccessViolationException, which managed code cannot
catch.

Nothing marks those objects as invalid, so the failure surfaces as a hard crash inside
native ICU rather than as ObjectDisposedException.

To Reproduce

Wrapper.Init();
var collator = new RuleBasedCollator("&b < a");
Console.WriteLine(collator.Compare("a", "b"));   // 1

Wrapper.Cleanup();                               // unloads icuuc/icuin

Console.WriteLine(collator.Compare("a", "b"));   // AccessViolationException
Fatal error. System.AccessViolationException: Attempted to read or write protected memory.
   at Icu.NativeMethods.ucol_strcoll(SafeRuleBasedCollatorHandle, System.String, Int32, System.String, Int32)
   at Icu.Collation.RuleBasedCollator.Compare(System.String, System.String)

Reproduced on ICU 54 and 62, on net8.0 and net48, x64 and x86 — it is not
version-dependent.

Expected behavior

Use of an ICU object after Wrapper.Cleanup() should throw ObjectDisposedException, not
corrupt the process. Cleanup() could track outstanding SafeHandles and invalidate them
before unloading, or at minimum the handle wrappers could check whether the library
generation they were created under is still loaded.

Why it matters

NativeMethods.Cleanup()
unloads both libraries; a later call re-resolves the delegate against a freshly loaded ICU
and hands it a UCollator* from the previous load.

Easy to hit when one component tears ICU down while another still holds objects:
FieldWorks' PUAInstaller calls Wrapper.Cleanup() to regenerate nfc_fw.nrm, while
libpalaso's IcuRulesCollator reuses collators from a static dictionary with no disposed
check.

The stack above is also frame-for-frame identical to #130, open since 2020 as an
intermittent collation crash. I could not reproduce that on any ICU from 44 to 67; this
path reproduces it exactly, and would be intermittent in the field since it depends on the
module's re-mapped base address.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions