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:
- 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.
- 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.
- 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.
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
SIGABRTinsidexamarin::android::TypeMapper::java_to_managed, aborted by scudo with invalid chunk state when deallocating. This is heap corruption — an invalidfree()— not a managed exception.What makes me fairly confident this is in
java_to_managedrather than corruption inflicted elsewhere and merely detected here:.soin 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.0x…75a3c02000and0x…6eb724b000(top byte is the pointer tag;tagged_addr_ctrlisPR_TAGGED_ADDR_ENABLE). Page-aligned pointers reachingfree()suggest something that was never a primary-allocator chunk.ThreadPoolForegthread — Chromium'sThreadPoolForegroundWorkerinside 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.soatoffset 0x2704000, no abort message, threadThread-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:
BlazorWebView, with heavy sustained JNI traffic: audio capture/playback, foreground services, Firebase messaging, and continuous WebView JS interop.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