Description
A race condition in janus_plugin_close_pc() / janus_plugin_close_pc_internal() can cause a crash (SIGSEGV) due to use-after-free of ice_handle.
Environment
- Janus version: 0.14.1
- OS: Linux (RHEL/CentOS)
- Plugin: VideoRoom
Note: This issue exists in both 0.x and 1.x branches as the affected code is identical.
Crash Information
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x0000000000452b1a in janus_plugin_close_pc_internal (user_data=0x7f92e8049f00) at janus.c:4114
plugin_session = 0x7f92e8049f00
ice_handle = 0x7f95113f9ff9 <-- invalid/freed pointer (not 8-byte aligned)
Root Cause
In janus_plugin_close_pc(), only plugin_session->ref is increased before registering the timeout callback, but ice_handle->ref is NOT increased:
void janus_plugin_close_pc(janus_plugin_session *plugin_session) {
// ...
janus_refcount_increase(&plugin_session->ref);
// ice_handle->ref is NOT increased here!
GSource *timeout_source = g_timeout_source_new_seconds(0);
g_source_set_callback(timeout_source, janus_plugin_close_pc_internal, plugin_session, NULL);
g_source_attach(timeout_source, sessions_watchdog_context);
// ...
}
This allows ice_handle to be freed by another thread (e.g., handling a "detach" request) before janus_plugin_close_pc_internal() is executed.
Race Condition Scenario
VideoRoom Handler Thread Requests Thread Watchdog Thread
| | |
close_pc() | |
| | |
timeout registered | |
| | |
| "detach" request received |
| | |
| janus_ice_handle_destroy() |
| | |
| ice_handle freed |
| | |
| | close_pc_internal()
| | |
| | ice_handle access
| | |
| | SIGSEGV 💥
Proposed Fix
Add janus_refcount_increase(&ice_handle->ref) in janus_plugin_close_pc() before registering the timeout:
void janus_plugin_close_pc(janus_plugin_session *plugin_session) {
if(!janus_plugin_session_is_alive(plugin_session))
return;
janus_ice_handle *ice_handle = (janus_ice_handle *)plugin_session->gateway_handle;
if(!ice_handle || !g_atomic_int_compare_and_exchange(&ice_handle->closepc, 0, 1))
return;
janus_refcount_increase(&plugin_session->ref);
janus_refcount_increase(&ice_handle->ref); // <-- ADD THIS
GSource *timeout_source = g_timeout_source_new_seconds(0);
g_source_set_callback(timeout_source, janus_plugin_close_pc_internal, plugin_session, NULL);
g_source_attach(timeout_source, sessions_watchdog_context);
g_source_unref(timeout_source);
}
And update janus_plugin_close_pc_internal() to handle the early return case:
static gboolean janus_plugin_close_pc_internal(gpointer user_data) {
janus_plugin_session *plugin_session = (janus_plugin_session *) user_data;
janus_ice_handle *ice_handle = (janus_ice_handle *)plugin_session->gateway_handle;
if(!ice_handle || !g_atomic_int_compare_and_exchange(&ice_handle->closepc, 1, 0)) {
janus_refcount_decrease(&plugin_session->ref);
if(ice_handle)
janus_refcount_decrease(&ice_handle->ref); // <-- ADD THIS
return G_SOURCE_REMOVE;
}
// Remove the existing increase since we already did it in close_pc()
// janus_refcount_increase(&ice_handle->ref); // <-- REMOVE THIS (line 4118)
// ... rest of the function remains the same ...
}
Additional Notes
- The server was running with ~1000 threads at the time of crash (high load)
- The invalid pointer 0x7f95113f9ff9 is not 8-byte aligned, indicating memory corruption/reuse after free
Description
A race condition in
janus_plugin_close_pc()/janus_plugin_close_pc_internal()can cause a crash (SIGSEGV) due to use-after-free ofice_handle.Environment
Note: This issue exists in both 0.x and 1.x branches as the affected code is identical.
Crash Information
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x0000000000452b1a in janus_plugin_close_pc_internal (user_data=0x7f92e8049f00) at janus.c:4114
plugin_session = 0x7f92e8049f00
ice_handle = 0x7f95113f9ff9 <-- invalid/freed pointer (not 8-byte aligned)
Root Cause
In
janus_plugin_close_pc(), onlyplugin_session->refis increased before registering the timeout callback, butice_handle->refis NOT increased: