One dead camera can crash the whole gst-rtsp-server
Notes. Scope: Debian bookworm’s
gst-rtsp-server1.22 C library, driving a live RTSP relay for camera feeds. The crash and the fix are measured; the sequence that leaves a stream “complete but frameless” is a plausible mechanism, not fully isolated. Corrections welcome.
At 10,000 feet
Setting: an RTSP relay built on GStreamer’s gst-rtsp-server library (the
standard C library behind most self-hosted RTSP servers on Linux), which serves
a placeholder stream when a source is offline.
Problem: whenever a source stayed offline, the whole server process
SIGABRTed about every 30 seconds — one process serves several feeds, so every
abort took all of them down.
What this note establishes: the abort is a gratuitous g_assert(FALSE) in
gst_rtsp_media_get_rates() (rtsp-media.c:2766), triggered by a stream that
reached “complete sender” state without ever carrying data; a 3-line patch onto
the graceful result = FALSE path directly beside it fixes it.
Takeaway: if any stream can reach SETUP without ever carrying data (dead upstream, placeholder that hits EOS, teardown/PLAY race), you are exposed to a process-wide abort. Patch the library or keep such streams from existing.
The crash: an assert that should have been a return
In rtsp-media.c (the library, not application code):
if (gst_rtsp_stream_is_complete (stream) && gst_rtsp_stream_is_sender (stream)) {
if (gst_rtsp_stream_get_rates (stream, rate, applied_rate)) {
... if (save_rate != *rate ...) { g_assert (FALSE); /* line 2759 */ }
} else {
/* complete stream without rate and applied_rate, weird */
g_assert (FALSE); /* line 2766 — the crash */
result = FALSE;
break;
}
}
A stream whose SETUP finished (is_complete and is_sender true) but that
never carried data produces no segment event, so no rate. On the next client
PLAY, get_rates returns FALSE and g_assert(FALSE) fires — with glib
assertions live, an abort() of the entire process. The graceful path
(result = FALSE, caller logs “failed to obtain consistent rate” and tears
down one session) sits directly below the assert: a debug-time invariant check
left load-bearing in production.
Measured: ~130 aborts/hour while a source stayed offline, 949 aborts in retained logs, ~30 s cadence matching the placeholder-then-reconnect cycle.
How a stream ends up “complete but frameless” (plausible mechanism)
A placeholder test pattern runs for a fixed number of buffers, emits EOS, and the cached media’s streams stay registered as complete senders with no data ever coming; the next PLAY walks into the assert. It could equally be a race between source teardown and a client attaching. The fix is correct either way — the state (complete sender, no rate) is what the library mishandles.
The fix: de-assert the library (3-line patch)
Rebuild Debian’s libgstrtspserver-1.0-0 with the three assert sites in
get_rates (2746 guarded, 2759 and 2766 rerouted) sent onto the existing
result = FALSE path: a process-wide SIGABRT becomes a failed PLAY for one
client (500, session torn down, client retries). Install via quilt patch +
apt-get source rebuild, then apt-mark hold so an upgrade cannot restore the
aborting binary. Optionally add an application-side liveness gate that refuses
to build pipelines for dead sources — that shrinks the window but is not the
fix.
Verified: the repro (dead source + scripted client re-attaching) crashed the unpatched library with the exact signature; patched, it ran 5000+ attaches over ~8 minutes with zero aborts, plus a 20+ minute live soak with zero aborts and healthy feeds unaffected.
Honesty and prior art
- Measured: the assert-to-abort mechanism and the patch (before/after repro).
- Prior art: CVE-2024-44331 / GStreamer SA-2024-0004 documents an
assertion-abort DoS in the same
rtsp-media.c, so the class is known. New here is the trigger: an empty complete-sender stream. A GitHub-wide search forgst_rtsp_media_get_ratesreturned zero hits — absence of public record, not proof no one has hit it privately. - Plausible mechanism, not isolated: the application-side sequence that creates the empty stream. The fix does not depend on it.
Licensed under the site footer’s CC BY 4.0. If this saved you a debugging session, the optional thanks link in the footer is appreciated, no obligation.
Validation (2026-08-31)
Re-checked in a throwaway debian:bookworm Docker container:
- Version:
apt-get source gst-rtsp-server1.0pulled 1.22.0-2 (upstream 1.22.0), matching the installedlibgstrtspserver-1.0-0binary. - Source:
gst_rtsp_media_get_rates()(line 2725) contains the three asserts at 2746, 2759, 2766 (claimed crash site: exact), eachg_assert(FALSE)followed immediately byresult = FALSE; break;and a tail that logs and returns. - Shipped binary:
debian/rulessets no-Db_ndebug/G_DISABLE_ASSERT; disassembly of the shipped.soshows exactly 3g_assertion_message_exprcalls in the function. Assertions enabled: YES. - Runtime: a minimal glib
g_assert(FALSE)program dies with SIGABRT, so line 2766 aborts the whole process withERROR:../gst/rtsp-server/rtsp-media.c:2766:...: assertion failed: (FALSE). - Not reproduced: the end-to-end live trigger (real media reaching complete-sender-no-rate, then PLAY) was not built in the time box.
Verdict: source-confirmed and binary-confirmed on 1.22.0-2; every mechanistic link measured; end-to-end live trigger not reproduced.