Skip to content

Heap corruption in TypeMapper::java_to_managed — "Scudo ERROR: invalid chunk state when deallocating address" #12473

Description

@alexyakunin

Android framework version

net11.0-android (Preview)

Affected platform version

.NET 11.0.100-preview.7.26381.103 / .NET MAUI 11.0.0-preview.7.26406.9

Description

Two separately built apps from the same codebase crashed with SIGABRT inside xamarin::android::TypeMapper::java_to_managed, aborted by scudo with invalid chunk state when deallocating. This is heap corruption — an invalid free() — not a managed exception.

Abort message: 'Scudo ERROR: invalid chunk state when deallocating address 0x2000075a3c02000'
  #04 scudo::reportInvalidChunkState(scudo::AllocatorAction, void const*)+120
  #05 scudo::Allocator<scudo::AndroidNormalConfig, &scudo_malloc_postinit>::deallocate(...)+296
  #06 xamarin::android::TypeMapper::java_to_managed(char const*, char const**, unsigned int*)+5752

What makes me fairly confident this is in java_to_managed rather than corruption inflicted elsewhere and merely detected here:

  • Two independently built binaries, same function. One is a .so in an alpha build (libmonodroid.so, java_to_managed+5752), the other is the release build's linked runtime (libnet-android.release.so, java_to_managed+5748). Different BuildIds, essentially the same offset into the same function.
  • Both freed addresses are 4 KiB page-aligned: 0x…75a3c02000 and 0x…6eb724b000 (top byte is the pointer tag; tagged_addr_ctrl is PR_TAGGED_ADDR_ENABLE). Page-aligned pointers reaching free() suggest something that was never a primary-allocator chunk.
  • Both crashed on a ThreadPoolForeg thread — Chromium's ThreadPoolForegroundWorker inside the app process, i.e. a Java→managed transition originating from WebView work, not from the app's own threads.

The two differ sharply in everything else, which argues against a single triggering code path in our app: one crashed 38 seconds after process start, the other after 28,676 seconds of uptime; one was a debug-signed alpha, the other a Play release build.

A third crash on the same device two days later may or may not be related: a bare abort() with only two frames, inside the app's own .so at offset 0x2704000, no abort message, thread Thread-202, process in the background. I have that tombstone too if it's useful.

Consequence is a hard process kill with no managed stack, so nothing reaches Sentry or Play Vitals beyond a native crash record — which is what makes this hard to act on from the app side.

Steps to Reproduce

I do not have a minimal repro, and I don't think I can construct one — this is heap corruption surfacing roughly once a day across two installed apps on one device.

What I can describe is the shape of the app and how to look for it:

  1. A .NET MAUI app whose UI is a BlazorWebView, with heavy sustained JNI traffic: audio capture/playback, foreground services, Firebase messaging, and continuous WebView JS interop.
  2. Run it on a device with scudo's allocator checks active (stock Android 16 here) and leave it installed and used normally for a few days.
  3. Collect native crash records via adb shell dumpsys dropbox --print data_app_native_crash.

Both captured crashes came from a Chromium thread-pool thread, so a stress test that drives many Java→managed transitions from WebView-owned threads seems the most likely way to reproduce deliberately.

Without the allocator guard, the same corruption would usually pass silently — so the crash rate is a lower bound on how often this actually happens.

Did you find any workaround?

No.

Relevant log output

=== Crash 1 - alpha build, 38 s uptime ===

Package: chat.actual.dev.app v34603519 (2.16.511-alpha)
Process uptime: 38s
pid: 25117, tid: 25222, name: ThreadPoolForeg  >>> chat.actual.dev.app <<<
tagged_addr_ctrl: 0000000000000001 (PR_TAGGED_ADDR_ENABLE)
signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
Abort message: 'Scudo ERROR: invalid chunk state when deallocating address 0x2000075a3c02000'
backtrace:
  #00 pc 00000000000757a8  /apex/com.android.runtime/lib64/bionic/libc.so (abort+160)
  #01 pc 000000000005cf50  /apex/com.android.runtime/lib64/bionic/libc.so (scudo::die()+12)
  #02 pc 000000000005d9b0  /apex/com.android.runtime/lib64/bionic/libc.so (scudo::reportRawError(char const*)+32)
  #03 pc 000000000005d924  /apex/com.android.runtime/lib64/bionic/libc.so (scudo::ScopedErrorReport::~ScopedErrorReport()+16)
  #04 pc 000000000005dd18  /apex/com.android.runtime/lib64/bionic/libc.so (scudo::reportInvalidChunkState(scudo::AllocatorAction, void const*)+120)
  #05 pc 000000000005f7d0  /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidNormalConfig, &scudo_malloc_postinit>::deallocate(void*, scudo::Chunk::Origin, unsigned long, unsigned long)+296)
  #06 pc 000000000008ca34  .../lib/arm64/libmonodroid.so (xamarin::android::TypeMapper::java_to_managed(char const*, char const**, unsigned int*)+5752) (BuildId: 70f58331dacd21ca6d77f2dc3509f04f230d7695)

=== Crash 2 - release build, 28,676 s uptime ===

Package: chat.actual.app v34537619 (2.15.147)
Process uptime: 28676s
pid: 9932, tid: 24563, name: ThreadPoolForeg  >>> chat.actual.app <<<
signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
Abort message: 'Scudo ERROR: invalid chunk state when deallocating address 0x200006eb724b000'
backtrace:
  #00..#05  identical to Crash 1 (abort -> scudo::die -> reportRawError -> ~ScopedErrorReport -> reportInvalidChunkState -> deallocate)
  #06 pc 000000000009a154  .../split_config.arm64_v8a.apk!libnet-android.release.so (offset 0x2b30000) (xamarin::android::TypeMapper::java_to_managed(char const*, char const**, unsigned int*)+5748) (BuildId: 178d61490514d27a5a4300a7c8390848713a139a)

Device for both: samsung/m3quew/m3q:16/BP4A.251205.006 (SM-S948U1), Android 16, arm64, kernel 6.12.30.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area: App RuntimeIssues in `libmonodroid.so`.need-attentionA xamarin-android contributor needs to review

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions