Tracking on our side. The fix belongs in obs-websocket; the analysis below is written so it can be handed over as-is.
What happens
GetOutputList reads each output's encoder without holding a reference. If an output is being destroyed concurrently, the encoder is freed between the null check and the read, and OBS crashes.
Reproduced by starting the replay buffer, saving a clip, stopping it, and calling GetOutputList shortly after. OBS 32.2.2, obs-websocket 5.7.
Fault address: 7FFACB1D5C67 (obs.dll)
Thread 6B8C: Thread (pooled) (Crashed)
obs.dll!obs_encoder_get_width+0x87
obs-websocket.dll!`Utils::Obs::ArrayHelper::GetOutputList'::`2'::<lambda_1>::<lambda_invoker_cdecl>+0x5d7
obs.dll!obs_enum_outputs+0x3a
obs-websocket.dll!RequestHandler::GetOutputList+0x4b
obs-websocket.dll!RequestHandler::ProcessRequest+0x196
obs-websocket.dll!WebSocketServer::ProcessMessage+0x115c
Root cause
src/utils/Obs_ArrayHelper.cpp, GetOutputList:
outputJson["outputWidth"] = obs_output_get_width(output);
outputJson["outputHeight"] = obs_output_get_height(output);
obs_output_get_width reaches obs_output_get_width2, which does:
if (flag_encoded(output)) {
if (output->video_encoders[idx])
return obs_encoder_get_width(output->video_encoders[idx]);
The null check and the dereference are not atomic with respect to teardown, and obs_enum_outputs gives the callback no reference to the encoder. The websocket thread reads it while the frontend thread frees it.
Also seen as a hang
Earlier in the same session, after SetOutputSettings on a live output, GetOutputList stopped responding for the rest of the OBS session, timing out in code paths unrelated to the call that preceded it. Same function, same field, so probably the same race landing on a lock rather than a free.
Impact for us
Any consumer calling GetOutputList around output lifecycle changes can take OBS down. Nothing a client can do about it: the request is a plain read.
What we do meanwhile
ObsWebSocket.Example run-transport-tests does not start or stop the replay buffer, so GetLastReplayBufferReplay is reported as untested rather than prepared for. SetOutputSettings is not sent either.
Tracking on our side. The fix belongs in obs-websocket; the analysis below is written so it can be handed over as-is.
What happens
GetOutputListreads each output's encoder without holding a reference. If an output is being destroyed concurrently, the encoder is freed between the null check and the read, and OBS crashes.Reproduced by starting the replay buffer, saving a clip, stopping it, and calling
GetOutputListshortly after. OBS 32.2.2, obs-websocket 5.7.Root cause
src/utils/Obs_ArrayHelper.cpp,GetOutputList:obs_output_get_widthreachesobs_output_get_width2, which does:The null check and the dereference are not atomic with respect to teardown, and
obs_enum_outputsgives the callback no reference to the encoder. The websocket thread reads it while the frontend thread frees it.Also seen as a hang
Earlier in the same session, after
SetOutputSettingson a live output,GetOutputListstopped responding for the rest of the OBS session, timing out in code paths unrelated to the call that preceded it. Same function, same field, so probably the same race landing on a lock rather than a free.Impact for us
Any consumer calling
GetOutputListaround output lifecycle changes can take OBS down. Nothing a client can do about it: the request is a plain read.What we do meanwhile
ObsWebSocket.Example run-transport-testsdoes not start or stop the replay buffer, soGetLastReplayBufferReplayis reported as untested rather than prepared for.SetOutputSettingsis not sent either.