From: Peter Marko <[email protected]> Pick patch which has closed gitlab issue for the CVE [1]. The issue is also referenced in [2]
[1] https://gitlab.gnome.org/GNOME/libsoup/-/work_items/496 [2] https://security-tracker.debian.org/tracker/CVE-2026-4271 Signed-off-by: Peter Marko <[email protected]> --- .../libsoup/libsoup/CVE-2026-4271.patch | 362 ++++++++++++++++++ meta/recipes-support/libsoup/libsoup_3.6.6.bb | 1 + 2 files changed, 363 insertions(+) create mode 100644 meta/recipes-support/libsoup/libsoup/CVE-2026-4271.patch diff --git a/meta/recipes-support/libsoup/libsoup/CVE-2026-4271.patch b/meta/recipes-support/libsoup/libsoup/CVE-2026-4271.patch new file mode 100644 index 0000000000..ca377e0355 --- /dev/null +++ b/meta/recipes-support/libsoup/libsoup/CVE-2026-4271.patch @@ -0,0 +1,362 @@ +From 489affa74c8a229b8a4dd541710d4a5debedb7b4 Mon Sep 17 00:00:00 2001 +From: Carlos Garcia Campos <[email protected]> +Date: Mon, 16 Feb 2026 12:09:08 +0100 +Subject: [PATCH] server: protect message io while reading and writing + +Ensure the nghttp2 session is not destroyed while being used. + +Closes #496 + +CVE: CVE-2026-4271 +Upstream-Status: Backport [https://gitlab.gnome.org/GNOME/libsoup/-/commit/489affa74c8a229b8a4dd541710d4a5debedb7b4] +Signed-off-by: Peter Marko <[email protected]> +--- + .../http2/soup-server-message-io-http2.c | 117 +++++++++++++----- + tests/http2-test.c | 54 ++++++++ + 2 files changed, 141 insertions(+), 30 deletions(-) + +diff --git a/libsoup/server/http2/soup-server-message-io-http2.c b/libsoup/server/http2/soup-server-message-io-http2.c +index 913afb46..6f8d1bb6 100644 +--- a/libsoup/server/http2/soup-server-message-io-http2.c ++++ b/libsoup/server/http2/soup-server-message-io-http2.c +@@ -69,6 +69,8 @@ typedef struct { + GHashTable *messages; + + guint in_callback; ++ guint protected; ++ gboolean destroyed; + } SoupServerMessageIOHTTP2; + + static void soup_server_message_io_http2_send_response (SoupServerMessageIOHTTP2 *io, +@@ -146,6 +148,8 @@ soup_server_message_io_http2_destroy (SoupServerMessageIO *iface) + { + SoupServerMessageIOHTTP2 *io = (SoupServerMessageIOHTTP2 *)iface; + ++ io->destroyed = TRUE; ++ + if (io->read_source) { + g_source_destroy (io->read_source); + g_source_unref (io->read_source); +@@ -160,10 +164,14 @@ soup_server_message_io_http2_destroy (SoupServerMessageIO *iface) + } + + g_clear_object (&io->iostream); +- g_clear_pointer (&io->session, nghttp2_session_del); +- g_clear_pointer (&io->messages, g_hash_table_unref); ++ io->istream = NULL; ++ io->ostream = NULL; + +- g_free (io); ++ if (io->protected == 0) { ++ g_clear_pointer (&io->session, nghttp2_session_del); ++ g_clear_pointer (&io->messages, g_hash_table_unref); ++ g_free (io); ++ } + } + + static void +@@ -321,7 +329,33 @@ static const SoupServerMessageIOFuncs io_funcs = { + soup_server_message_io_http2_is_paused + }; + ++static void ++soup_server_message_io_http2_protect (SoupServerMessageIOHTTP2 *io) ++{ ++ io->protected++; ++ g_object_ref (io->conn); ++} ++ + static gboolean ++soup_server_message_io_http2_unprotect (SoupServerMessageIOHTTP2 *io) ++{ ++ g_object_unref (io->conn); ++ ++ if (--io->protected > 0) ++ return FALSE; ++ ++ if (io->destroyed) { ++ g_clear_pointer (&io->session, nghttp2_session_del); ++ g_clear_pointer (&io->messages, g_hash_table_unref); ++ g_free (io); ++ ++ return TRUE; ++ } ++ ++ return FALSE; ++} ++ ++static void + io_write (SoupServerMessageIOHTTP2 *io, + GError **error) + { +@@ -336,51 +370,57 @@ io_write (SoupServerMessageIOHTTP2 *io, + if (io->write_buffer_size == 0) { + /* Done */ + io->write_buffer = NULL; +- return TRUE; ++ return; + } + } + ++ if (!io->ostream) ++ return; ++ + gssize ret = g_pollable_stream_write (io->ostream, + io->write_buffer + io->written_bytes, + io->write_buffer_size - io->written_bytes, + FALSE, NULL, error); +- if (ret < 0) +- return FALSE; +- +- io->written_bytes += ret; +- return TRUE; ++ if (ret > 0) ++ io->written_bytes += ret; + } + + static gboolean + io_write_ready (GObject *stream, + SoupServerMessageIOHTTP2 *io) + { +- SoupServerConnection *conn = io->conn; + GError *error = NULL; + +- g_object_ref (conn); ++ soup_server_message_io_http2_protect (io); ++ ++ while (!error) { ++ if (io->destroyed) ++ break; ++ ++ if (!nghttp2_session_want_write (io->session)) ++ break; + +- while (!error && soup_server_connection_get_io_data (conn) == (SoupServerMessageIO *)io && nghttp2_session_want_write (io->session)) + io_write (io, &error); ++ } + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { + g_error_free (error); +- g_object_unref (conn); ++ soup_server_message_io_http2_unprotect (io); + return G_SOURCE_CONTINUE; + } + +- if (soup_server_connection_get_io_data (conn) == (SoupServerMessageIO *)io) { ++ if (!io->destroyed) { + if (error) + h2_debug (io, NULL, "[SESSION] IO error: %s", error->message); + + g_clear_pointer (&io->write_source, g_source_unref); + + if (error || (!nghttp2_session_want_read (io->session) && !nghttp2_session_want_write (io->session))) +- soup_server_connection_disconnect (conn); ++ soup_server_connection_disconnect (io->conn); + } + + g_clear_error (&error); +- g_object_unref (conn); ++ soup_server_message_io_http2_unprotect (io); + + return G_SOURCE_REMOVE; + } +@@ -390,13 +430,12 @@ static gboolean io_write_idle_cb (SoupServerMessageIOHTTP2* io); + static void + io_try_write (SoupServerMessageIOHTTP2 *io) + { +- SoupServerConnection *conn = io->conn; + GError *error = NULL; + + if (io->write_source) + return; + +- if (io->in_callback && soup_server_connection_get_io_data (conn) == (SoupServerMessageIO *)io) { ++ if (io->in_callback && !io->destroyed) { + if (!nghttp2_session_want_write (io->session)) + return; + +@@ -416,12 +455,19 @@ io_try_write (SoupServerMessageIOHTTP2 *io) + g_clear_pointer (&io->write_idle_source, g_source_unref); + } + +- g_object_ref (conn); ++ soup_server_message_io_http2_protect (io); ++ ++ while (!error) { ++ if (io->destroyed) ++ break; ++ ++ if (!nghttp2_session_want_write (io->session)) ++ break; + +- while (!error && soup_server_connection_get_io_data (conn) == (SoupServerMessageIO *)io && !io->in_callback && nghttp2_session_want_write (io->session)) + io_write (io, &error); ++ } + +- if (soup_server_connection_get_io_data (conn) == (SoupServerMessageIO *)io) { ++ if (!io->destroyed) { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { + g_clear_error (&error); + io->write_source = g_pollable_output_stream_create_source (G_POLLABLE_OUTPUT_STREAM (io->ostream), NULL); +@@ -434,11 +480,11 @@ io_try_write (SoupServerMessageIOHTTP2 *io) + h2_debug (io, NULL, "[SESSION] IO error: %s", error->message); + + if (error || (!nghttp2_session_want_read (io->session) && !nghttp2_session_want_write (io->session))) +- soup_server_connection_disconnect (conn); ++ soup_server_connection_disconnect (io->conn); + } + + g_clear_error (&error); +- g_object_unref (conn); ++ soup_server_message_io_http2_unprotect (io); + } + + static gboolean +@@ -481,31 +527,37 @@ static gboolean + io_read_ready (GObject *stream, + SoupServerMessageIOHTTP2 *io) + { +- SoupServerConnection *conn = io->conn; + gboolean progress = TRUE; + GError *error = NULL; + +- g_object_ref (conn); ++ soup_server_message_io_http2_protect (io); ++ ++ while (progress) { ++ if (io->destroyed) ++ break; ++ ++ if (!nghttp2_session_want_read (io->session)) ++ break; + +- while (progress && soup_server_connection_get_io_data (conn) == (SoupServerMessageIO *)io && nghttp2_session_want_read (io->session)) + progress = io_read (io, &error); ++ } + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { + g_error_free (error); +- g_object_unref (conn); ++ soup_server_message_io_http2_unprotect (io); + return G_SOURCE_CONTINUE; + } + +- if (soup_server_connection_get_io_data (conn) == (SoupServerMessageIO *)io) { ++ if (!io->destroyed) { + if (error) + h2_debug (io, NULL, "[SESSION] IO error: %s", error->message); + + if (error || (!nghttp2_session_want_read (io->session) && !nghttp2_session_want_write (io->session))) +- soup_server_connection_disconnect (conn); ++ soup_server_connection_disconnect (io->conn); + } + + g_clear_error (&error); +- g_object_unref (conn); ++ soup_server_message_io_http2_unprotect (io); + + return G_SOURCE_REMOVE; + } +@@ -931,5 +983,10 @@ soup_server_message_io_http2_new (SoupServerConnection *conn, + nghttp2_submit_settings (io->session, NGHTTP2_FLAG_NONE, settings, G_N_ELEMENTS (settings)); + io_try_write (io); + ++#ifdef __clang_analyzer__ ++ // Suppress false positive about io being destroyed here, since at this point we have only ++ // send the initial settings and not callback is called. ++ [[clang::suppress]] ++#endif + return (SoupServerMessageIO *)io; + } +diff --git a/tests/http2-test.c b/tests/http2-test.c +index 0846a0a6..d12b4bf7 100644 +--- a/tests/http2-test.c ++++ b/tests/http2-test.c +@@ -1268,6 +1268,40 @@ do_broken_pseudo_header_test (Test *test, gconstpointer data) + g_uri_unref (uri); + } + ++static void ++disconnect_on_got_headers (SoupServerMessage *msg, gpointer user_data) ++{ ++ GUri *uri; ++ SoupServerConnection *conn; ++ ++ uri = soup_server_message_get_uri (msg); ++ if (!g_str_equal (g_uri_get_path (uri), "/close-on-got-headers")) ++ return; ++ ++ conn = soup_server_message_get_connection (msg); ++ soup_server_connection_disconnect (conn); ++} ++ ++static void ++do_server_disconnect_on_got_headers_test (Test *test, gconstpointer data) ++{ ++ SoupMessage *msg; ++ GUri *uri; ++ GBytes *response; ++ GError *error = NULL; ++ ++ uri = g_uri_parse_relative (base_uri, "/close-on-got-headers", SOUP_HTTP_URI_FLAGS, NULL); ++ msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri); ++ ++ response = soup_test_session_async_send (test->session, msg, NULL, &error); ++ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT); ++ ++ g_clear_error (&error); ++ g_bytes_unref (response); ++ g_object_unref (msg); ++ g_uri_unref (uri); ++} ++ + static gboolean + unpause_message (SoupServerMessage *msg) + { +@@ -1396,12 +1430,26 @@ server_handler (SoupServer *server, + shutdown (fd, SHUT_WR); + #endif + ++ soup_server_message_set_response (msg, "text/plain", ++ SOUP_MEMORY_STATIC, ++ "Success!", 8); ++ } else if (strcmp (path, "/close-on-got-headers") == 0) { + soup_server_message_set_response (msg, "text/plain", + SOUP_MEMORY_STATIC, + "Success!", 8); + } + } + ++static void ++server_request_started (SoupServer *server, ++ SoupServerMessage *msg, ++ SoupServerConnection *conn, ++ gpointer user_data) ++{ ++ g_signal_connect (msg, "got-headers", ++ G_CALLBACK (disconnect_on_got_headers), NULL); ++} ++ + static gboolean + server_basic_auth_callback (SoupAuthDomain *auth_domain, + SoupServerMessage *msg, +@@ -1428,6 +1476,8 @@ main (int argc, char **argv) + return 0; + + server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD | SOUP_TEST_SERVER_HTTP2); ++ g_signal_connect (server, "request-started", ++ G_CALLBACK (server_request_started), NULL); + auth = soup_auth_domain_basic_new ("realm", "http2-test", + "auth-callback", server_basic_auth_callback, + NULL); +@@ -1584,6 +1634,10 @@ main (int argc, char **argv) + setup_session, + do_broken_pseudo_header_test, + teardown_session); ++ g_test_add ("/http2/server-disconnect-on-got-headers", Test, NULL, ++ setup_session, ++ do_server_disconnect_on_got_headers_test, ++ teardown_session); + + ret = g_test_run (); + diff --git a/meta/recipes-support/libsoup/libsoup_3.6.6.bb b/meta/recipes-support/libsoup/libsoup_3.6.6.bb index d18a16379b..e5c2d18ded 100644 --- a/meta/recipes-support/libsoup/libsoup_3.6.6.bb +++ b/meta/recipes-support/libsoup/libsoup_3.6.6.bb @@ -20,6 +20,7 @@ SRC_URI += "file://CVE-2025-32049-1.patch \ file://CVE-2026-1539.patch \ file://CVE-2026-5119.patch \ file://CVE-2026-2708.patch \ + file://CVE-2026-4271.patch \ " PROVIDES = "libsoup-3.0"
-=-=-=-=-=-=-=-=-=-=-=- Links: You receive all messages sent to this group. View/Reply Online (#241097): https://lists.openembedded.org/g/openembedded-core/message/241097 Mute This Topic: https://lists.openembedded.org/mt/120301667/21656 Group Owner: [email protected] Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [[email protected]] -=-=-=-=-=-=-=-=-=-=-=-
