Forum
    • Categories
    • Register
    • Login

    Use-after-free in DelayedRenderer::processWrapped - crashes the DAW on dense MIDI (patch included)

    Scheduled Pinned Locked Moved Bug Reports
    1 Posts 1 Posters 39 Views
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • V
      versusjona
      last edited by versusjona

      Hi all,

      I've been building a multi-mic drum kit, and for days it randomly crashed my DAW whenever I played fast, busy patterns (quick hi-hats, fast toms). It happened in both Ableton and Reaper, but never in the HISE standalone, which is what made it so hard to pin down. Windows reported heap corruption (0xC0000374), and the denser the MIDI, the faster it died, sometimes seconds, sometimes 10 minutes.

      I spent past Sunday debugging it with Claude Code, mostly to read the crash dumps for me.

      Things we ruled out first, in case it saves anyone time: my scripts (it still crashed with everything disabled), round-robin, choke scripts, voice limits, monoliths vs streamed samples, CPU affinity, deferCallbacks, and the number of samplers. It even crashed with a stripped-down project and an empty script, so it clearly wasn't my code.

      We eventually ran it under Application Verifier (PageHeap), which turns the delayed heap corruption into an immediate access violation, and symbolicated the dump. Here's what Claude Code found, pasting its explanation:

      The crash is in hi_core/hi_core/MainControllerHelpers.cpp, in DelayedRenderer::processWrapped.

      When the host sends a buffer larger than the prepared block size (the "FL Studio violates the block size" workaround), the function chunks the buffer and calls itself recursively — passing its own member delayedMidiBuffer as the midiMessages argument:

      processWrapped(chunk, delayedMidiBuffer);   // passes the member
      

      So inside the recursive call, midiMessages is delayedMidiBuffer. Then in the shortBuffer paths:

      MidiBuffer::Iterator it(midiMessages);   // iterator into delayedMidiBuffer
      delayedMidiBuffer.clear();               // clears the buffer being iterated
      for (auto& e : shortBuffer)
          delayedMidiBuffer.addEvent(...);     // and reallocs it
      while (it.getNextEvent(m, pos))          // dangling iterator -> use-after-free
      

      The clear() and reallocating addEvent() invalidate the iterator, which is then dereferenced. It only appears in a host because the standalone uses a fixed block size, so that recursive path never runs.


      The fix (give the recursion its own buffer so the two can't alias):

      // MainControllerHelpers.h — next to delayedMidiBuffer:
      MidiBuffer chunkMidiBuffer;
      
      // MainControllerHelpers.cpp — in the oversized-buffer branch:
      chunkMidiBuffer.clear();
      chunkMidiBuffer.addEvents(midiMessages, thisOffset, numThisTime, -thisOffset);
      ...
      processWrapped(chunk, chunkMidiBuffer);   // was: delayedMidiBuffer
      

      There are two shortBuffer paths that clear/regrow delayedMidiBuffer while iterating midiMessages, so this closes both.

      Result: my full kit used to crash within seconds or minutes. After the patch it ran +25 minutes in Reaper with PageHeap still on (so any remaining bad access would fault immediately) and +25 minutes in Ableton on the exact project that always crashed. No crash.

      Minor side note it also spotted: alloca(numChannels * sizeof(float*) * numChannels) in the same function allocates numChannels² — harmless, but looks like a typo.

      Hope it's useful information. Thanks for HISE!
      Jon

      1 Reply Last reply Reply Quote 3
      • First post
        Last post

      13

      Online

      2.4k

      Users

      13.9k

      Topics

      120.6k

      Posts