Skip to content

ICU-23353 Fix double-delete in Region::cleanupRegionData#3922

Open
hirorogo wants to merge 1 commit intounicode-org:maint/maint-54from
hirorogo:fix/uaf-003-region
Open

ICU-23353 Fix double-delete in Region::cleanupRegionData#3922
hirorogo wants to merge 1 commit intounicode-org:maint/maint-54from
hirorogo:fix/uaf-003-region

Conversation

@hirorogo
Copy link
Copy Markdown

@hirorogo hirorogo commented Mar 31, 2026

Summary

Region::cleanupRegionData() in region.cpp deletes/closes availableRegions[], regionAliases, numericCodeMap, and regionIDMap but does not nullify the pointers afterward. If cleanup is called multiple times (e.g., via repeated u_cleanup() calls), the same memory is freed twice, causing a double-delete / use-after-free.

Fix: Set each pointer to NULL immediately after delete/uhash_close so subsequent calls are safe no-ops.

PoC

// Reproduction: cleanupRegionData double-delete
// Calling cleanup twice without nullifying pointers -> double-free
#include <cstdio>
#include <cstdlib>

struct Hashtable {
    int dummy;
};

Hashtable* regionAliases = nullptr;
Hashtable* numericCodeMap = nullptr;
void** availableRegions[5] = {};
int regionCount = 3;

void initRegionData() {
    regionAliases = (Hashtable*)malloc(sizeof(Hashtable));
    numericCodeMap = (Hashtable*)malloc(sizeof(Hashtable));
    for (int i = 0; i < regionCount; i++)
        availableRegions[i] = (void**)malloc(64);
    printf("  Allocated: regionAliases=%p, numericCodeMap=%p\n",
           regionAliases, numericCodeMap);
}

// BUGGY: pointers not nullified after free
void cleanupRegionData_buggy() {
    printf("  cleanup: deleting regionAliases=%p\n", regionAliases);
    free(regionAliases);        // freed but pointer still non-NULL
    // regionAliases = nullptr;  // MISSING!
    free(numericCodeMap);
    for (int i = 0; i < regionCount; i++)
        free(availableRegions[i]);
}

// FIXED: nullify after free
void cleanupRegionData_fixed() {
    printf("  cleanup: deleting regionAliases=%p\n", regionAliases);
    free(regionAliases);
    regionAliases = nullptr;
    free(numericCodeMap);
    numericCodeMap = nullptr;
    for (int i = 0; i < regionCount; i++) {
        free(availableRegions[i]);
        availableRegions[i] = nullptr;
    }
}

int main() {
    printf("=== BUGGY: double cleanup -> double-free ===\n");
    initRegionData();
    cleanupRegionData_buggy();
    printf("  Calling cleanup again (pointers still non-NULL!)...\n");
    printf("  regionAliases=%p (dangling!)\n", regionAliases);
    // Second call would double-free:
    // cleanupRegionData_buggy();  // CRASH / heap corruption
    printf("  Double-free would occur here\n");

    printf("\n=== FIXED: double cleanup -> safe no-op ===\n");
    initRegionData();
    cleanupRegionData_fixed();
    printf("  Calling cleanup again...\n");
    printf("  regionAliases=%p (NULL, safe)\n", regionAliases);
    cleanupRegionData_fixed();  // safe: all pointers are NULL
    printf("  Second cleanup completed safely\n");

    return 0;
}

Output:

=== BUGGY: double cleanup -> double-free ===
  Allocated: regionAliases=0x..., numericCodeMap=0x...
  cleanup: deleting regionAliases=0x...
  Calling cleanup again (pointers still non-NULL!)...
  regionAliases=0x... (dangling!)
  Double-free would occur here

=== FIXED: double cleanup -> safe no-op ===
  Allocated: regionAliases=0x..., numericCodeMap=0x...
  cleanup: deleting regionAliases=0x...
  Calling cleanup again...
  regionAliases=0x0 (NULL, safe)
  Second cleanup completed safely

Test plan

  • Existing ICU region tests pass
  • Calling u_cleanup() twice no longer crashes

Nullify pointers after delete/uhash_close so that a repeated call
to cleanupRegionData does not double-free the same allocations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@hirorogo hirorogo changed the title Fix double-delete in Region::cleanupRegionData ICU-23353 Fix double-delete in Region::cleanupRegionData Apr 1, 2026
@hirorogo
Copy link
Copy Markdown
Author

hirorogo commented Apr 1, 2026

Could a maintainer please transition the Jira ticket ICU-23353 from New to Accepted? The jira-ticket CI check requires the ticket to be in "Accepted" status, but as a new contributor I don't have permission to change the ticket status. Thank you!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant