dotnet / dotnet/runtime

[mono] Secondary SIGSEGV crash in `dump_memory_around_ip` when IP is a near-NULL invalid address (e.g. 0xb5) on iOS

Open
#126,983 0 comments 0 reactions 0 assignees View on GitHub
area-VM-meta-mono
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

### Description

When a C++ crash (e.g. a jump through a corrupted vtable or a null-offset virtual call) results in a Program Counter (PC) value that is a small but non-zero address such as `0xb5`, the crash handler `dump_memory_around_ip` in `mini-posix.c` causes a **secondary SIGSEGV** inside the signal handler itself.

This is triggered because the current NULL guard only checks for exact zero (`if (native_ip)`), but a near-NULL address like `0xb5` passes the check and is subsequently used as the base for a memory dump that reads from address `0xa5` (i.e., `0xb5 - 0x10`), which falls within the null guard page and is not mapped.

### Reproduction Steps

1. Embed Mono runtime on iOS.
2. Trigger a C++ crash that results in an invalid near-NULL PC value, for example:
- A virtual call through a null or corrupted `this` pointer where the vtable offset lands at a small integer (e.g., `((Foo*)nullptr)->virtual_method()` with vtable slot offset `0xb5`)
- A function pointer call through a corrupted/uninitialized variable

3. Observe that the Mono crash handler is entered via:
```
mono_handle_native_crash
→ mono_dump_native_crash_info
→ dump_memory_around_ip
```

### Expected behavior

The crash handler should detect that the PC value is not a valid/readable address, print a diagnostic message, and skip the memory dump gracefully.

### Actual behavior

`dump_memory_around_ip` receives `native_ip = 0xb5`, which passes the `if (native_ip)` null check. It then calls:

```c
mono_dump_mem(((guint8 *) native_ip) - 0x10, 0x40);
// equivalent to: mono_dump_mem(0xa5, 0x40)
```

Inside `mono_dump_mem`, the loop attempts to read memory at addresses `0xa5` through `0xe4`, all of which fall within the null guard page (unmapped memory). This triggers a **secondary SIGSEGV** inside the signal handler, making the original crash effectively undebuggable.

### Regression?

_No response_

### Known Workarounds

_No response_

### Configuration

- **OS**: iOS
- **Architecture**: arm64
- - **Runtime**: 9.0.11

### Other information

## Root Cause

In `mini-posix.c`, `dump_memory_around_ip`:

```c
gpointer native_ip = MONO_CONTEXT_GET_IP (mctx);
if (native_ip) { // ← only guards against exact NULL (0x0)
native_ip = MINI_FTNPTR_TO_ADDR (native_ip);
g_async_safe_printf ("Memory around native instruction pointer (%p):", native_ip);
mono_dump_mem (((guint8 *) native_ip) - 0x10, 0x40); // ← reads 0xb5-0x10 = 0xa5, SIGSEGV
}
```

The guard `if (native_ip)` only rejects `0x0`. Any non-zero but invalid address in the null guard page (typically `0x1` – `0xFFF`) will silently proceed to `mono_dump_mem` and cause a second crash.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.