Describe the bug
clear_ice() in src/switch_core_media.c zeroes engine->ice_in without holding
the RTP session's ice_mutex, while the RTP receive thread reads that exact
memory under ice_mutex in handle_ice(). The reader can therefore observe a
half-cleared candidate table and dereference a NULL con_addr, segfaulting at
address 0 inside libc's strcmp.
Writer — src/switch_core_media.c, clear_ice(), holding no lock:
engine = &smh->engines[type];
engine->ice_in.chosen[0] = 0;
...
engine->ice_in.cand_idx[0] = 0;
engine->ice_in.cand_idx[1] = 0;
memset(&engine->ice_in, 0, sizeof(engine->ice_in)); /* <-- unlocked */
engine->remote_rtcp_port = 0;
if (engine->rtp_session) {
switch_rtp_reset(engine->rtp_session);
}
Reader — src/switch_rtp.c, handle_ice(), holding rtp_session->ice_mutex:
for (i = 0; i < ice->ice_params->cand_idx[ice->proto]; i++) {
if (!strcmp(ice->ice_params->cands[i][ice->proto].con_addr, from_host) &&
ice->ice_params->cands[i][ice->proto].con_port == from_port) {
ice->ice_params is &engine->ice_in, so the two touch the same object. Crucially
con_addr is declared char * (src/include/switch_rtp.h), so the memset
sets it to NULL rather than to an empty string, and strcmp(NULL, from_host)
faults.
The interleaving required is: the reader evaluates cand_idx[proto] as non-zero,
then the writer's memset lands, then the reader dereferences
cands[i][proto].con_addr — now NULL.
switch_rtp_reset(), called from clear_ice(), also mutates rtp_session->ice
outside the lock. Same path, smaller exposure.
Trigger path
verto.attach → switch_core_media_clear_ice() → clear_ice(), i.e. a client
re-attaching to an existing session while media is flowing. Any caller of
switch_core_media_clear_ice() on a live session with an active RTP thread can
hit it.
Expected behavior
clear_ice() should hold the same ice_mutex that handle_ice() holds before
mutating the candidate tables, so the reader never observes a partially cleared
ice_in.
Package version or git hash
1.11.1-release. The code is unchanged on current master, and identical in
v1.10.10 and v1.10.12, so this is long-standing rather than a regression.
Impact
Observed twice in five days on production WebRTC media nodes carrying ~150
concurrent channels, each occurrence dropping every call on the affected node
(57 and 68 calls respectively). Because the faulting thread dies holding
ice_mutex, other threads touching that session block behind it and the process
limps for several minutes before exiting, rather than dying immediately.
Trace logs / backtrace from core file
I have a core dump but the distro libc is stripped in the region the top frame
falls in, so I cannot provide a usefully symbolised thread apply all bt — the
top frame resolves only to libc.so.6 + 0x167132, which sits in the IFUNC string
-routine area. What I can state from the crash handler output:
Received signal 11 SEGV_MAPERR 000000000000
Fault address is exactly 0, consistent with strcmp(NULL, ...).
Happy to attempt a better backtrace if there is a preferred way to get symbols
for the string routines.
Suggested fix
Same shape as #2915 ("[core] Fix - add missing ice_mutex to protect dtls"): export
lock helpers from switch_rtp.c (the mutex is SWITCH_MUTEX_NESTED, so
recursion is safe) and take the lock across clear_ice(), which also covers the
switch_rtp_reset() exposure.
SWITCH_DECLARE(void) switch_rtp_ice_lock(switch_rtp_t *rtp_session)
{
if (rtp_session && rtp_session->ice_mutex) {
switch_mutex_lock(rtp_session->ice_mutex);
}
}
SWITCH_DECLARE(void) switch_rtp_ice_unlock(switch_rtp_t *rtp_session)
{
if (rtp_session && rtp_session->ice_mutex) {
switch_mutex_unlock(rtp_session->ice_mutex);
}
}
then in clear_ice():
engine = &smh->engines[type];
if (engine->rtp_session) {
switch_rtp_ice_lock(engine->rtp_session);
}
engine->ice_in.chosen[0] = 0;
...
if (engine->rtp_session) {
switch_rtp_reset(engine->rtp_session);
switch_rtp_ice_unlock(engine->rtp_session);
}
I'm happy to open a PR with this if the approach looks right. One thing worth a
maintainer's eye: this introduces a new acquisition of ice_mutex from
switch_core_media.c. I found no lock-order inversion on the verto.attach
path, but I don't have full visibility of every caller of
switch_core_media_clear_ice().
Describe the bug
clear_ice()insrc/switch_core_media.czeroesengine->ice_inwithout holdingthe RTP session's
ice_mutex, while the RTP receive thread reads that exactmemory under
ice_mutexinhandle_ice(). The reader can therefore observe ahalf-cleared candidate table and dereference a NULL
con_addr, segfaulting ataddress 0 inside libc's
strcmp.Writer —
src/switch_core_media.c,clear_ice(), holding no lock:Reader —
src/switch_rtp.c,handle_ice(), holdingrtp_session->ice_mutex:ice->ice_paramsis&engine->ice_in, so the two touch the same object. Cruciallycon_addris declaredchar *(src/include/switch_rtp.h), so thememsetsets it to NULL rather than to an empty string, and
strcmp(NULL, from_host)faults.
The interleaving required is: the reader evaluates
cand_idx[proto]as non-zero,then the writer's
memsetlands, then the reader dereferencescands[i][proto].con_addr— now NULL.switch_rtp_reset(), called fromclear_ice(), also mutatesrtp_session->iceoutside the lock. Same path, smaller exposure.
Trigger path
verto.attach→switch_core_media_clear_ice()→clear_ice(), i.e. a clientre-attaching to an existing session while media is flowing. Any caller of
switch_core_media_clear_ice()on a live session with an active RTP thread canhit it.
Expected behavior
clear_ice()should hold the sameice_mutexthathandle_ice()holds beforemutating the candidate tables, so the reader never observes a partially cleared
ice_in.Package version or git hash
1.11.1-release. The code is unchanged on current
master, and identical inv1.10.10 and v1.10.12, so this is long-standing rather than a regression.
Impact
Observed twice in five days on production WebRTC media nodes carrying ~150
concurrent channels, each occurrence dropping every call on the affected node
(57 and 68 calls respectively). Because the faulting thread dies holding
ice_mutex, other threads touching that session block behind it and the processlimps for several minutes before exiting, rather than dying immediately.
Trace logs / backtrace from core file
I have a core dump but the distro libc is stripped in the region the top frame
falls in, so I cannot provide a usefully symbolised
thread apply all bt— thetop frame resolves only to
libc.so.6 + 0x167132, which sits in the IFUNC string-routine area. What I can state from the crash handler output:
Fault address is exactly 0, consistent with
strcmp(NULL, ...).Happy to attempt a better backtrace if there is a preferred way to get symbols
for the string routines.
Suggested fix
Same shape as #2915 ("[core] Fix - add missing ice_mutex to protect dtls"): export
lock helpers from
switch_rtp.c(the mutex isSWITCH_MUTEX_NESTED, sorecursion is safe) and take the lock across
clear_ice(), which also covers theswitch_rtp_reset()exposure.then in
clear_ice():I'm happy to open a PR with this if the approach looks right. One thing worth a
maintainer's eye: this introduces a new acquisition of
ice_mutexfromswitch_core_media.c. I found no lock-order inversion on theverto.attachpath, but I don't have full visibility of every caller of
switch_core_media_clear_ice().