Skip to content

Commit 3e3f941

Browse files
summeroffclaude
andauthored
Fix UAF crash from dangling output signal handlers (#1716)
* Fix UAF crash from dangling output signal handlers osn::Output connected the "stop" handler and per-signal OutputSignalCallback (each with a heap CallbackData carrying `this`) but never disconnected them or freed the CallbackData. When the underlying obs_output_t outlived the wrapper -- delay buffer, reconnect, or a shared SetOutput ref -- a later signal fired into freed CallbackData/this, crashing in signal_handler_signal (seen in Sentry as EXCEPTION_ILLEGAL_INSTRUCTION via ISimpleReplayBuffer::Stop -> obs_output_stop). Heavy start/stop churn (e.g. recording repeatedly failing to write) widened the window. - Add DisconnectSignals(): disconnects OnStopped and every OutputSignalCallback, then frees the CallbackData (also fixes the pre-existing leak). - Track allocated CallbackData in m_signalCallbackData. - Promote the anonymous "stop" lambda to a named static OnStopped so it has a stable address to disconnect. - Call DisconnectSignals() in DeleteOutput after the stop-wait and before obs_output_release, so OnStopped stays live during the wait and no connection survives the release. All output types tear down through DeleteOutput (Simple/Advanced -> Streaming/Recording/ReplayBuffer dtors, plus CreateOutput/SetOutput recreate), so this covers every path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Make ConnectSignals private Only called internally from InitOutput; addresses review feedback. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent fb1658d commit 3e3f941

2 files changed

Lines changed: 51 additions & 19 deletions

File tree

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

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,6 @@
2020

2121
#include "nodeobs_api.h"
2222

23-
namespace {
24-
25-
struct CallbackData {
26-
std::string signal;
27-
osn::Output *outputClass;
28-
};
29-
30-
} // namespace
31-
3223
osn::Output::Output(const std::vector<std::string> &signals) : m_signals(signals) {}
3324

3425
osn::Output::~Output() {}
@@ -40,19 +31,20 @@ void osn::Output::InitOutput(obs_output_t *output)
4031
m_outputStopped = false;
4132
}
4233

43-
auto onStopped = [](void *data, calldata_t *) {
44-
osn::Output *context = reinterpret_cast<osn::Output *>(data);
45-
std::unique_lock lock(context->m_mtxOutputStop);
46-
context->m_outputStopped = true;
47-
context->m_cvStop.notify_one();
48-
};
49-
5034
signal_handler *sh = obs_output_get_signal_handler(output);
51-
signal_handler_connect(sh, "stop", onStopped, this);
35+
signal_handler_connect(sh, "stop", osn::Output::OnStopped, this);
5236

5337
ConnectSignals();
5438
}
5539

40+
void osn::Output::OnStopped(void *data, calldata_t *)
41+
{
42+
osn::Output *context = reinterpret_cast<osn::Output *>(data);
43+
std::unique_lock lock(context->m_mtxOutputStop);
44+
context->m_outputStopped = true;
45+
context->m_cvStop.notify_one();
46+
}
47+
5648
void osn::Output::CreateOutput(const std::string &type, const std::string &name)
5749
{
5850
DeleteOutput();
@@ -89,13 +81,19 @@ void osn::Output::DeleteOutput()
8981
blog(LOG_WARNING, "Timed out waiting for output stop before release.");
9082
}
9183
}
84+
85+
// Disconnect before release: the obs_output_t can outlive this object
86+
// (delay buffer, reconnect, or a shared ref), and a later signal firing
87+
// into freed CallbackData/this would crash in signal_handler_signal.
88+
DisconnectSignals();
89+
9290
obs_output_release(m_output);
9391
m_output = nullptr;
9492
}
9593

9694
void osn::OutputSignalCallback(void *data, calldata_t *params)
9795
{
98-
auto info = reinterpret_cast<CallbackData *>(data);
96+
auto info = reinterpret_cast<osn::Output::CallbackData *>(data);
9997

10098
if (!info)
10199
return;
@@ -122,10 +120,28 @@ void osn::Output::ConnectSignals()
122120
auto *cd = new CallbackData();
123121
cd->signal = signal;
124122
cd->outputClass = this;
123+
m_signalCallbackData.push_back(cd);
125124
signal_handler_connect(handler, signal.c_str(), osn::OutputSignalCallback, cd);
126125
}
127126
}
128127

128+
void osn::Output::DisconnectSignals()
129+
{
130+
if (!m_output)
131+
return;
132+
133+
signal_handler *handler = obs_output_get_signal_handler(m_output);
134+
if (handler) {
135+
signal_handler_disconnect(handler, "stop", osn::Output::OnStopped, this);
136+
for (auto *cd : m_signalCallbackData)
137+
signal_handler_disconnect(handler, cd->signal.c_str(), osn::OutputSignalCallback, cd);
138+
}
139+
140+
for (auto *cd : m_signalCallbackData)
141+
delete cd;
142+
m_signalCallbackData.clear();
143+
}
144+
129145
void osn::Output::StartOutput()
130146
{
131147
if (!m_output)

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ class Output {
3939
Output(const std::vector<std::string> &signals);
4040
virtual ~Output();
4141

42-
void ConnectSignals();
4342
void CreateOutput(const std::string &type, const std::string &name);
4443
void SetOutput(obs_output_t *output);
4544
virtual void DeleteOutput();
@@ -63,8 +62,24 @@ class Output {
6362
private:
6463
friend void OutputSignalCallback(void *data, calldata_t *params);
6564

65+
// One per connected signal; carries the signal name and owner so the
66+
// forwarding callback can identify them. Heap-allocated in ConnectSignals
67+
// and freed in DisconnectSignals.
68+
struct CallbackData {
69+
std::string signal;
70+
osn::Output *outputClass = nullptr;
71+
};
72+
6673
void InitOutput(obs_output_t *output);
6774

75+
void ConnectSignals();
76+
77+
// Disconnects every handler ConnectSignals/InitOutput wired up and frees
78+
// the CallbackData. Must run while m_output is still valid.
79+
void DisconnectSignals();
80+
81+
static void OnStopped(void *data, calldata_t *params);
82+
6883
obs_video_info *m_canvas = nullptr;
6984
obs_output_t *m_output = nullptr;
7085

@@ -76,6 +91,7 @@ class Output {
7691
bool m_outputStopped = false;
7792

7893
const std::vector<std::string> m_signals;
94+
std::vector<CallbackData *> m_signalCallbackData;
7995
};
8096

8197
}

0 commit comments

Comments
 (0)