From c762e8204aba7e9ba5e10807f21bef7f94d695ab Mon Sep 17 00:00:00 2001 From: Winlin Date: Mon, 11 Aug 2025 18:57:31 -0400 Subject: [PATCH] AI: HTTP-FLV: Fix heap-use-after-free crash during stream unmount. v6.0.171 v7.0.50 (#4432) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fixes a critical heap-use-after-free crash in HTTP-FLV streaming that occurs when a client requests a stream while it's being unmounted asynchronously. ## Problem - **Issue**: #4429 - Heap-use-after-free crash in `SrsLiveStream::serve_http()` - **Root Cause**: Race condition between coroutines in single-threaded SRS server: 1. **Coroutine A**: HTTP client requests FLV stream → `serve_http()` starts 2. **Coroutine B**: RTMP publisher disconnects → triggers async stream destruction 3. **Async Worker**: Destroys `SrsLiveStream` object while Coroutine A is yielded 4. **Coroutine A**: Resumes and accesses freed memory → **CRASH** ## Solution 1. **Early viewer registration**: Add HTTP connection to `viewers_` list immediately in `serve_http()` before any I/O operations that could yield 2. **Lifecycle protection**: Split `serve_http()` into wrapper and implementation to ensure proper viewer management 3. **Stream availability checks**: Add fast checks for stream disposal state before critical operations 4. **Improved error handling**: Convert warnings to fatal errors when trying to free alive streams ## Key Changes - **`SrsLiveStream::serve_http()`**: Now immediately registers viewer and delegates to `serve_http_impl()` - **`SrsLiveStream::serve_http_impl()`**: Contains the actual HTTP serving logic - **`SrsHttpStreamDestroy::call()`**: Enhanced error handling and longer wait timeout - **Stream state validation**: Added checks for `entry->enabled` before proceeding with stream operations Fixes #4429 --- trunk/doc/CHANGELOG.md | 2 ++ trunk/src/app/srs_app_http_stream.cpp | 42 +++++++++++++++++++++------ trunk/src/app/srs_app_http_stream.hpp | 3 ++ trunk/src/core/srs_core_version6.hpp | 2 +- trunk/src/core/srs_core_version7.hpp | 2 +- 5 files changed, 40 insertions(+), 11 deletions(-) diff --git a/trunk/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md index f31485677..96cd485bb 100644 --- a/trunk/doc/CHANGELOG.md +++ b/trunk/doc/CHANGELOG.md @@ -7,6 +7,7 @@ The changelog for SRS. ## SRS 7.0 Changelog +* v7.0, 2025-08-11, Merge [#4432](https://github.com/ossrs/srs/pull/4432): AI: HTTP-FLV: Fix heap-use-after-free crash during stream unmount. v7.0.50 (#4432) * v7.0, 2025-07-28, Merge [#4245](https://github.com/ossrs/srs/pull/4245): Allow Forward to be configured with Env Var. v7.0.49 (#4245) * v7.0, 2025-07-16, Merge [#4295](https://github.com/ossrs/srs/pull/4295): RTC: audio packet jitter buffer. v7.0.48 (#4295) * v7.0, 2025-07-11, Merge [#4333](https://github.com/ossrs/srs/pull/4333): NEW PROTOCOL: Support viewing stream over RTSP. v7.0.47 (#4333) @@ -60,6 +61,7 @@ The changelog for SRS. ## SRS 6.0 Changelog +* v6.0, 2025-08-11, Merge [#4432](https://github.com/ossrs/srs/pull/4432): AI: HTTP-FLV: Fix heap-use-after-free crash during stream unmount. v6.0.171 (#4432) * v6.0, 2025-07-28, Merge [#4245](https://github.com/ossrs/srs/pull/4245): Allow Forward to be configured with Env Var. v6.0.170 (#4245) * v6.0, 2025-07-10, Merge [#4414](https://github.com/ossrs/srs/pull/4414): Fix H.264 B-frame detection logic to comply with specification. v6.0.169 (#4414) * v6.0, 2025-06-04, Merge [#4325](https://github.com/ossrs/srs/pull/4325): fix bug: loop transcoding #3516. v6.0.168 (#4325) diff --git a/trunk/src/app/srs_app_http_stream.cpp b/trunk/src/app/srs_app_http_stream.cpp index cfe6d42de..6f48462bd 100755 --- a/trunk/src/app/srs_app_http_stream.cpp +++ b/trunk/src/app/srs_app_http_stream.cpp @@ -619,6 +619,26 @@ srs_error_t SrsLiveStream::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage { srs_error_t err = srs_success; + SrsHttpMessage* hr = dynamic_cast(r); + SrsHttpConn* hc = dynamic_cast(hr->connection()); + + // Add the viewer to the viewers list. + viewers_.push_back(hc); + + err = serve_http_impl(w, r); + + // Remove viewer from the viewers list. + vector::iterator it = std::find(viewers_.begin(), viewers_.end(), hc); + srs_assert (it != viewers_.end()); + viewers_.erase(it); + + return err; +} + +srs_error_t SrsLiveStream::serve_http_impl(ISrsHttpResponseWriter* w, ISrsHttpMessage* r) +{ + srs_error_t err = srs_success; + SrsHttpMessage* hr = dynamic_cast(r); SrsHttpConn* hc = dynamic_cast(hr->connection()); SrsHttpxConn* hxc = dynamic_cast(hc->handler()); @@ -651,6 +671,11 @@ srs_error_t SrsLiveStream::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage return srs_error_wrap(err, "http hook"); } + // Fast check whether stream is still available. + if (!entry->enabled) { + return srs_error_new(ERROR_RTMP_STREAM_NOT_FOUND, "stream not found"); + } + // Always try to create the source, because http handler won't create it. SrsSharedPtr live_source; if ((err = _srs_sources->fetch_or_create(req, server_, live_source)) != srs_success) { @@ -673,16 +698,13 @@ srs_error_t SrsLiveStream::serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage // object is not alive. SrsUniquePtr consumer(consumer_raw); - // Add the viewer to the viewers list. - viewers_.push_back(hc); + // Fast check whether stream is still available. + if (!entry->enabled) { + return srs_error_new(ERROR_RTMP_STREAM_NOT_FOUND, "stream not found"); + } // Serve the viewer connection. err = do_serve_http(live_source.get(), consumer.get(), w, r); - - // Remove viewer from the viewers list. - vector::iterator it = std::find(viewers_.begin(), viewers_.end(), hc); - srs_assert (it != viewers_.end()); - viewers_.erase(it); // Do hook after serving. http_hooks_on_stop(r); @@ -1321,7 +1343,7 @@ srs_error_t SrsHttpStreamDestroy::call() // Wait for cache and stream to stop. int i = 0; - for (; i < 1024; i++) { + for (; i < 3000; i++) { if (!cache->alive() && !stream->alive()) { break; } @@ -1329,7 +1351,9 @@ srs_error_t SrsHttpStreamDestroy::call() } if (cache->alive() || stream->alive()) { - srs_warn("http: try to free a alive stream, cache=%d, stream=%d", cache->alive(), stream->alive()); + srs_error("http: try to free an alive stream, sid=%s, cache=%d, stream=%d", + sid_.c_str(), cache->alive(), stream->alive()); + srs_assert(false); } // Remove the entry from handlers. diff --git a/trunk/src/app/srs_app_http_stream.hpp b/trunk/src/app/srs_app_http_stream.hpp index 90d897ff3..4085ea559 100755 --- a/trunk/src/app/srs_app_http_stream.hpp +++ b/trunk/src/app/srs_app_http_stream.hpp @@ -198,6 +198,9 @@ public: virtual srs_error_t update_auth(SrsRequest* r); public: virtual srs_error_t serve_http(ISrsHttpResponseWriter* w, ISrsHttpMessage* r); +private: + srs_error_t serve_http_impl(ISrsHttpResponseWriter* w, ISrsHttpMessage* r); +public: virtual bool alive(); // Interface ISrsExpire public: diff --git a/trunk/src/core/srs_core_version6.hpp b/trunk/src/core/srs_core_version6.hpp index 2de2cc4e7..7df5576ea 100644 --- a/trunk/src/core/srs_core_version6.hpp +++ b/trunk/src/core/srs_core_version6.hpp @@ -9,6 +9,6 @@ #define VERSION_MAJOR 6 #define VERSION_MINOR 0 -#define VERSION_REVISION 170 +#define VERSION_REVISION 171 #endif diff --git a/trunk/src/core/srs_core_version7.hpp b/trunk/src/core/srs_core_version7.hpp index bf26d5c86..1ee7bc906 100644 --- a/trunk/src/core/srs_core_version7.hpp +++ b/trunk/src/core/srs_core_version7.hpp @@ -9,6 +9,6 @@ #define VERSION_MAJOR 7 #define VERSION_MINOR 0 -#define VERSION_REVISION 49 +#define VERSION_REVISION 50 #endif \ No newline at end of file