From c112ca61d0d87f42f2c9b0b4d85ae73dcaa26d14 Mon Sep 17 00:00:00 2001 From: Tom Herbert <18316812+taherbert@users.noreply.github.com> Date: Sat, 14 Mar 2026 10:19:57 -0700 Subject: [PATCH] [DH] Fix pick_up_fragment use-after-free crash pick_up_event_t stored a raw pointer to the selected soul_fragment_t. If another ability consumed and deleted that fragment during the movement delay, the event would dereference freed memory. Only surfaces on Vengeance because fragments spawn at ~10.6 yards (vs Havoc's ~4.6), giving a non-zero movement time where spenders can consume the fragment before the event fires. Fix: store the fragment type instead of a raw pointer and re-select an active fragment when the event fires. Also remove the vestigial consume_soul_greater null guard which was always true. --- engine/class_modules/sc_demon_hunter.cpp | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/engine/class_modules/sc_demon_hunter.cpp b/engine/class_modules/sc_demon_hunter.cpp index d0b1f77db36..054332e0b96 100644 --- a/engine/class_modules/sc_demon_hunter.cpp +++ b/engine/class_modules/sc_demon_hunter.cpp @@ -4998,11 +4998,11 @@ struct pick_up_fragment_t : public demon_hunter_spell_t struct pick_up_event_t : public event_t { demon_hunter_t* dh; - soul_fragment_t* frag; + soul_fragment type; expr_t* expr; - pick_up_event_t( soul_fragment_t* f, timespan_t time, expr_t* e ) - : event_t( *f->dh, time ), dh( f->dh ), frag( f ), expr( e ) + pick_up_event_t( demon_hunter_t* p, soul_fragment t, timespan_t time, expr_t* e ) + : event_t( *p, time ), dh( p ), type( t ), expr( e ) { } @@ -5013,8 +5013,20 @@ struct pick_up_fragment_t : public demon_hunter_spell_t void execute() override { - // Evaluate if_expr to make sure the actor still wants to consume. - if ( frag && frag->active() && ( !expr || expr->eval() ) && dh->active.consume_soul_greater ) + // Re-select a fragment at execution time rather than storing a pointer + // from when the action fired. The original fragment may have been consumed + // and deleted during the movement delay. + soul_fragment_t* frag = nullptr; + for ( auto f : dh->soul_fragments ) + { + if ( f->is_type( type ) && f->active() ) + { + frag = f; + break; + } + } + + if ( frag && ( !expr || expr->eval() ) ) { frag->consume( false ); } @@ -5204,7 +5216,7 @@ struct pick_up_fragment_t : public demon_hunter_spell_t timespan_t time = calculate_movement_time( frag ); assert( p()->soul_fragment_pick_up == nullptr ); - p()->soul_fragment_pick_up = make_event( *sim, frag, time, if_expr.get() ); + p()->soul_fragment_pick_up = make_event( *sim, p(), type, time, if_expr.get() ); } bool ready() override