Skip to content

Commit 88b484b

Browse files
summeroffclaude
andcommitted
Fix volmeter use-after-free on audio thread during teardown
The volmeter held only a weak reference to its attached source. When the source was concurrently going to refcount 0, obs_volmeter_detach_source's weak->strong upgrade returned NULL and it skipped obs_source_remove_audio_capture_callback, leaving a dangling {volmeter_source_data_received, freed-volmeter} entry in the source's audio_cb_list. The WASAPI RT capture thread could then call into the freed volmeter before obs_source_destroy frees that list, crashing in signal_levels_updated (EXCEPTION_ACCESS_VIOLATION_EXEC) or the peak path (EXCEPTION_ACCESS_VIOLATION_READ). Hold a strong reference to the attached source for the lifetime of the attachment (via findAndRef), released only after detach. The source can no longer reach refcount 0 while attached, so the detach always removes the audio capture callback before the volmeter is freed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 1c54906 commit 88b484b

2 files changed

Lines changed: 14 additions & 1 deletion

File tree

obs-studio-server/source/osn-volmeter.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ void osn::Volmeter::Attach(void *data, const int64_t id, const std::vector<ipc::
116116
PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Invalid Meter reference.");
117117
}
118118

119-
auto source = osn::Source::Manager::GetInstance().find(uid_source);
119+
OBSSourceAutoRelease source = osn::Source::Manager::GetInstance().findAndRef(uid_source);
120120
if (!source) {
121121
PRETTY_ERROR_RETURN(ErrorCode::InvalidReference, "Invalid Source reference.");
122122
}
@@ -126,6 +126,9 @@ void osn::Volmeter::Attach(void *data, const int64_t id, const std::vector<ipc::
126126
}
127127

128128
meter->uid_source = uid_source;
129+
// Hold the source alive while attached (replaces any previous attachment's ref,
130+
// which obs_volmeter_attach_source has already detached above).
131+
meter->source_ref = std::move(source);
129132

130133
rval.push_back(ipc::value((uint64_t)ErrorCode::Ok));
131134
AUTO_DEBUG;
@@ -143,6 +146,9 @@ void osn::Volmeter::Detach(void *data, const int64_t id, const std::vector<ipc::
143146

144147
meter->uid_source = INVALID_ID;
145148
obs_volmeter_detach_source(meter->self);
149+
// Release only after detach, so the weak-ref upgrade inside detach succeeds and the
150+
// audio capture callback is removed cleanly.
151+
meter->source_ref = nullptr;
146152

147153
rval.push_back(ipc::value((uint64_t)ErrorCode::Ok));
148154
AUTO_DEBUG;

obs-studio-server/source/osn-volmeter.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <queue>
2323
#include <array>
2424
#include "obs.h"
25+
#include <obs.hpp>
2526
#include "utility.hpp"
2627

2728
extern std::mutex mtx;
@@ -52,6 +53,12 @@ class Volmeter {
5253
utility::unique_id::id_t id = INVALID_ID;
5354
utility::unique_id::id_t uid_source = INVALID_ID;
5455

56+
// Strong reference to the attached source, held for the lifetime of the attachment.
57+
// Keeps the source alive so obs_volmeter_detach_source can always remove the audio
58+
// capture callback before the volmeter is freed, avoiding a dangling entry that the
59+
// audio thread would call into.
60+
OBSSourceAutoRelease source_ref;
61+
5562
struct AudioData {
5663
std::array<float, MAX_AUDIO_CHANNELS> magnitude{};
5764
std::array<float, MAX_AUDIO_CHANNELS> peak{};

0 commit comments

Comments
 (0)