Heap corruption in TypeMapper::java_to_managed — "Scudo ERROR: invalid chunk state when deallocating address"
- Dominant language
- C#
- Stars
- 2.1k
- Forks
- 579
- Avg merge
- 1d 21h
- Merged PRs (30d)
- 257
Description
### 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::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
```shell
=== 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::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.
```
Contributor guide
No contributing guide indexed for this repository
Research direction
Start at the native xamarin::android::TypeMapper::java_to_managed entry point named in both tombstones, then trace the Java-to-managed transitions and deallocation paths involved. Use adb shell dumpsys dropbox --print data_app_native_crash and a stress test with WebView-owned threads, JNI traffic, and sustained JS interop; done means the heap-corruption cause is identified and the Scudo crash no longer reproduces.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- android, csharp
- Domain
- mobile-dev
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 28/100