summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaxim Cournoyer <maxim@guixotic.coop>2025-07-31 08:02:12 +0900
committerMaxim Cournoyer <maxim@guixotic.coop>2025-07-31 09:10:38 +0900
commit55cd48b0beed28462c303f258d8b85e1d866218f (patch)
treeb1ef9e48c25c92547e0d4b2fa464bb07c47ed24e
parent7e535a9194cd7c6a4c1494431e4dca03423fae40 (diff)
gnu: libsoup: Apply patches fixing deadlocks and CVE-2025-4476.
The patches are a subset taken from Debian (see: <https://sources.debian.org/patches/libsoup3/3.6.5-3/>). * gnu/packages/patches/libsoup-auth-digest-fix-crash.patch * gnu/packages/patches/libsoup-deadlock-in-add_listener_in_thread.patch * gnu/packages/patches/libsoup-fix-merge-of-ranges.patch * gnu/packages/patches/libsoup-memory-leak-in-soup_form_decode.patch * gnu/packages/patches/libsoup-multipart-bounds-check.patch * gnu/packages/patches/libsoup-use-libdl-instead-of-gmodule.patch: New files. * gnu/local.mk (dist_patch_DATA): Register them. * gnu/packages/gnome.scm (libsoup-minimal): Apply them. Change-Id: I7e4968c1d87e28860fc68616f6107d018e0d93dd
-rw-r--r--gnu/local.mk8
-rw-r--r--gnu/packages/gnome.scm18
-rw-r--r--gnu/packages/patches/libsoup-auth-digest-fix-crash.patch31
-rw-r--r--gnu/packages/patches/libsoup-deadlock-in-add_listener_in_thread.patch42
-rw-r--r--gnu/packages/patches/libsoup-fix-merge-of-ranges.patch192
-rw-r--r--gnu/packages/patches/libsoup-memory-leak-in-soup_form_decode.patch101
-rw-r--r--gnu/packages/patches/libsoup-multipart-bounds-check.patch106
-rw-r--r--gnu/packages/patches/libsoup-use-libdl-instead-of-gmodule.patch70
8 files changed, 562 insertions, 6 deletions
diff --git a/gnu/local.mk b/gnu/local.mk
index 4d5793b206..a78c63dd7e 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -2301,7 +2301,13 @@ dist_patch_DATA = \
%D%/packages/patches/seq24-rename-mutex.patch \
%D%/packages/patches/libsequoia-fix-ffi-Makefile.patch \
%D%/packages/patches/libsequoia-remove-store.patch \
- %D%/packages/patches/shakespeare-spl-fix-grammar.patch \
+ %D%/packages/patches/libsoup-auth-digest-fix-crash.patch \
+ %D%/packages/patches/libsoup-deadlock-in-add_listener_in_thread.patch \
+ %D%/packages/patches/libsoup-fix-merge-of-ranges.patch \
+ %D%/packages/patches/libsoup-memory-leak-in-soup_form_decode.patch \
+ %D%/packages/patches/libsoup-multipart-bounds-check.patch \
+ %D%/packages/patches/libsoup-use-libdl-instead-of-gmodule.patch \
+ %D%/packages/patches/shakespeare-spl-fix-grammar.patch \
%D%/packages/patches/shared-mime-info-xdgmime-path.patch \
%D%/packages/patches/sharutils-CVE-2018-1000097.patch \
%D%/packages/patches/sipwitch-fix-build-with-exosip5.patch \
diff --git a/gnu/packages/gnome.scm b/gnu/packages/gnome.scm
index e6e1718731..2f30f6925e 100644
--- a/gnu/packages/gnome.scm
+++ b/gnu/packages/gnome.scm
@@ -5288,7 +5288,15 @@ as OpenStreetMap, OpenCycleMap, OpenAerialMap and Maps.")
"libsoup-" version ".tar.xz"))
(sha256
(base32
- "0d52mnvvsvwpc3scjva5fbvns8f8ijyswgjwjhbr151ymid7d4b8"))))
+ "0d52mnvvsvwpc3scjva5fbvns8f8ijyswgjwjhbr151ymid7d4b8"))
+ (patches
+ (search-patches
+ "libsoup-auth-digest-fix-crash.patch"
+ "libsoup-deadlock-in-add_listener_in_thread.patch"
+ "libsoup-fix-merge-of-ranges.patch"
+ "libsoup-memory-leak-in-soup_form_decode.patch"
+ "libsoup-multipart-bounds-check.patch"
+ "libsoup-use-libdl-instead-of-gmodule.patch"))))
(build-system meson-build-system)
(arguments
(list
@@ -5312,21 +5320,21 @@ as OpenStreetMap, OpenCycleMap, OpenAerialMap and Maps.")
(substitute* "tests/hsts-db-test.c"
((".*/hsts-db/subdomains.*") "")))))))
(native-inputs
- (list `(,glib "bin") ;for glib-mkenums
+ (list `(,glib "bin") ;for glib-mkenums
gobject-introspection
pkg-config
python-wrapper
vala
curl
- gnutls ;for 'certtool'
+ gnutls ;for 'certtool'
httpd/pinned))
(propagated-inputs
;; libsoup-3.0.pc refers to all of these (except where otherwise noted)
(list brotli
glib
- glib-networking ; for GIO runtime modules
+ glib-networking ; for GIO runtime modules
libpsl
- nghttp2 ;for pkg-config
+ nghttp2 ;for pkg-config
`(,nghttp2 "lib")
libxml2
mit-krb5
diff --git a/gnu/packages/patches/libsoup-auth-digest-fix-crash.patch b/gnu/packages/patches/libsoup-auth-digest-fix-crash.patch
new file mode 100644
index 0000000000..7b147338a2
--- /dev/null
+++ b/gnu/packages/patches/libsoup-auth-digest-fix-crash.patch
@@ -0,0 +1,31 @@
+From: Michael Catanzaro <mcatanzaro@redhat.com>
+Date: Thu, 8 May 2025 09:27:01 -0500
+Subject: auth-digest: fix crash in soup_auth_digest_get_protection_space()
+
+We need to validate the Domain parameter in the WWW-Authenticate header.
+
+Unfortunately this crash only occurs when listening on default ports 80
+and 443, so there's no good way to test for this. The test would require
+running as root.
+
+Origin: upstream, 3.7.0, commit:e64c221f9c7d09b48b610c5626b3b8c400f0907c
+Bug: https://gitlab.gnome.org/GNOME/libsoup/-/issues/440
+Bug-CVE: https://security-tracker.debian.org/tracker/CVE-2025-4476
+Bug-Debian: https://bugs.debian.org/1105887
+---
+ libsoup/auth/soup-auth-digest.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/libsoup/auth/soup-auth-digest.c b/libsoup/auth/soup-auth-digest.c
+index d8bb291..292f204 100644
+--- a/libsoup/auth/soup-auth-digest.c
++++ b/libsoup/auth/soup-auth-digest.c
+@@ -220,7 +220,7 @@ soup_auth_digest_get_protection_space (SoupAuth *auth, GUri *source_uri)
+ if (uri &&
+ g_strcmp0 (g_uri_get_scheme (uri), g_uri_get_scheme (source_uri)) == 0 &&
+ g_uri_get_port (uri) == g_uri_get_port (source_uri) &&
+- !strcmp (g_uri_get_host (uri), g_uri_get_host (source_uri)))
++ !g_strcmp0 (g_uri_get_host (uri), g_uri_get_host (source_uri)))
+ dir = g_strdup (g_uri_get_path (uri));
+ else
+ dir = NULL;
diff --git a/gnu/packages/patches/libsoup-deadlock-in-add_listener_in_thread.patch b/gnu/packages/patches/libsoup-deadlock-in-add_listener_in_thread.patch
new file mode 100644
index 0000000000..1e3fa161bc
--- /dev/null
+++ b/gnu/packages/patches/libsoup-deadlock-in-add_listener_in_thread.patch
@@ -0,0 +1,42 @@
+From: Michael Catanzaro <mcatanzaro@redhat.com>
+Date: Wed, 30 Apr 2025 14:13:41 -0500
+Subject: test-utils: fix deadlock in add_listener_in_thread()
+
+The mutex is locked in the wrong place here.
+
+Hopefully fixes #379
+
+Origin: upstream, 3.7.0, commit:3c0cee2cfddb9ba31b30421f2b3cdd3c5a255e99
+Bug: https://gitlab.gnome.org/GNOME/libsoup/-/issues/379
+---
+ tests/test-utils.c | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+diff --git a/tests/test-utils.c b/tests/test-utils.c
+index 62f0b83..6fb09b6 100644
+--- a/tests/test-utils.c
++++ b/tests/test-utils.c
+@@ -627,9 +627,11 @@ static gboolean
+ add_listener_in_thread (gpointer user_data)
+ {
+ AddListenerData *data = user_data;
++ GUri *uri;
+
+- data->uri = add_listener (data->server, data->scheme, data->host);
++ uri = add_listener (data->server, data->scheme, data->host);
+ g_mutex_lock (&data->mutex);
++ data->uri = uri;
+ g_cond_signal (&data->cond);
+ g_mutex_unlock (&data->mutex);
+
+@@ -661,9 +663,9 @@ soup_test_server_get_uri (SoupServer *server,
+ data.host = host;
+ data.uri = NULL;
+
+- g_mutex_lock (&data.mutex);
+ soup_add_completion (context, add_listener_in_thread, &data);
+
++ g_mutex_lock (&data.mutex);
+ while (!data.uri)
+ g_cond_wait (&data.cond, &data.mutex);
+
diff --git a/gnu/packages/patches/libsoup-fix-merge-of-ranges.patch b/gnu/packages/patches/libsoup-fix-merge-of-ranges.patch
new file mode 100644
index 0000000000..5cc7325a5d
--- /dev/null
+++ b/gnu/packages/patches/libsoup-fix-merge-of-ranges.patch
@@ -0,0 +1,192 @@
+From: Milan Crha <mcrha@redhat.com>
+Date: Tue, 15 Apr 2025 12:17:39 +0200
+Subject: soup-message-headers: Correct merge of ranges
+
+It had been skipping every second range, which generated an array
+of a lot of insane ranges, causing large memory usage by the server.
+
+Origin: upstream, 3.7.0, commit:9bb92f7a685e31e10e9e8221d0342280432ce836
+Bug: https://gitlab.gnome.org/GNOME/libsoup/-/issues/428
+Bug-CVE: https://security-tracker.debian.org/tracker/CVE-2025-32907
+Bug-Debian: https://bugs.debian.org/1103264
+---
+ libsoup/soup-message-headers.c | 1 +
+ tests/meson.build | 1 +
+ tests/server-mem-limit-test.c | 144 +++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 146 insertions(+)
+ create mode 100644 tests/server-mem-limit-test.c
+
+diff --git a/libsoup/soup-message-headers.c b/libsoup/soup-message-headers.c
+index ee7a3cb..f101d4b 100644
+--- a/libsoup/soup-message-headers.c
++++ b/libsoup/soup-message-headers.c
+@@ -1244,6 +1244,7 @@ soup_message_headers_get_ranges_internal (SoupMessageHeaders *hdrs,
+ if (cur->start <= prev->end) {
+ prev->end = MAX (prev->end, cur->end);
+ g_array_remove_index (array, i);
++ i--;
+ }
+ }
+ }
+diff --git a/tests/meson.build b/tests/meson.build
+index cf4ddbd..68e6d01 100644
+--- a/tests/meson.build
++++ b/tests/meson.build
+@@ -102,6 +102,7 @@ tests = [
+ {'name': 'samesite'},
+ {'name': 'session'},
+ {'name': 'server-auth'},
++ {'name': 'server-mem-limit'},
+ {'name': 'server'},
+ {'name': 'sniffing',
+ 'depends': [test_resources],
+diff --git a/tests/server-mem-limit-test.c b/tests/server-mem-limit-test.c
+new file mode 100644
+index 0000000..98f1c40
+--- /dev/null
++++ b/tests/server-mem-limit-test.c
+@@ -0,0 +1,144 @@
++/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
++/*
++ * Copyright (C) 2025 Red Hat <www.redhat.com>
++ */
++
++#include "test-utils.h"
++
++#include <sys/resource.h>
++
++/*
++ This test limits memory usage to trigger too large buffer allocation crash.
++ As restoring the limits back to what it was does not always work, it's split
++ out of the server-test.c test with copied minimal server code.
++ */
++
++typedef struct {
++ SoupServer *server;
++ GUri *base_uri, *ssl_base_uri;
++ GSList *handlers;
++} ServerData;
++
++static void
++server_setup_nohandler (ServerData *sd, gconstpointer test_data)
++{
++ sd->server = soup_test_server_new (SOUP_TEST_SERVER_IN_THREAD);
++ sd->base_uri = soup_test_server_get_uri (sd->server, "http", NULL);
++ if (tls_available)
++ sd->ssl_base_uri = soup_test_server_get_uri (sd->server, "https", NULL);
++}
++
++static void
++server_add_handler (ServerData *sd,
++ const char *path,
++ SoupServerCallback callback,
++ gpointer user_data,
++ GDestroyNotify destroy)
++{
++ soup_server_add_handler (sd->server, path, callback, user_data, destroy);
++ sd->handlers = g_slist_prepend (sd->handlers, g_strdup (path));
++}
++
++static void
++server_setup (ServerData *sd, gconstpointer test_data)
++{
++ server_setup_nohandler (sd, test_data);
++}
++
++static void
++server_teardown (ServerData *sd, gconstpointer test_data)
++{
++ GSList *iter;
++
++ for (iter = sd->handlers; iter; iter = iter->next)
++ soup_server_remove_handler (sd->server, iter->data);
++ g_slist_free_full (sd->handlers, g_free);
++
++ g_clear_pointer (&sd->server, soup_test_server_quit_unref);
++ g_clear_pointer (&sd->base_uri, g_uri_unref);
++ g_clear_pointer (&sd->ssl_base_uri, g_uri_unref);
++}
++
++static void
++server_file_callback (SoupServer *server,
++ SoupServerMessage *msg,
++ const char *path,
++ GHashTable *query,
++ gpointer data)
++{
++ void *mem;
++
++ g_assert_cmpstr (path, ==, "/file");
++ g_assert_cmpstr (soup_server_message_get_method (msg), ==, SOUP_METHOD_GET);
++
++ mem = g_malloc0 (sizeof (char) * 1024 * 1024);
++ /* fedora-scan CI claims a warning about possibly leaked `mem` variable, thus use
++ the copy and free it explicitly, to workaround the false positive; the g_steal_pointer()
++ did not help for the malloc-ed memory */
++ soup_server_message_set_response (msg, "application/octet-stream", SOUP_MEMORY_COPY, mem, sizeof (char) * 1024 *1024);
++ soup_server_message_set_status (msg, SOUP_STATUS_OK, NULL);
++ g_free (mem);
++}
++
++static void
++do_ranges_overlaps_test (ServerData *sd, gconstpointer test_data)
++{
++ SoupSession *session;
++ SoupMessage *msg;
++ GString *range;
++ GUri *uri;
++ const char *chunk = ",0,0,0,0,0,0,0,0,0,0,0";
++
++ g_test_bug ("428");
++
++ #ifdef G_OS_WIN32
++ g_test_skip ("Cannot run under windows");
++ return;
++ #endif
++
++ range = g_string_sized_new (99 * 1024);
++ g_string_append (range, "bytes=1024");
++ while (range->len < 99 * 1024)
++ g_string_append (range, chunk);
++
++ session = soup_test_session_new (NULL);
++ server_add_handler (sd, "/file", server_file_callback, NULL, NULL);
++
++ uri = g_uri_parse_relative (sd->base_uri, "/file", SOUP_HTTP_URI_FLAGS, NULL);
++
++ msg = soup_message_new_from_uri ("GET", uri);
++ soup_message_headers_append (soup_message_get_request_headers (msg), "Range", range->str);
++
++ soup_test_session_send_message (session, msg);
++
++ soup_test_assert_message_status (msg, SOUP_STATUS_PARTIAL_CONTENT);
++
++ g_object_unref (msg);
++
++ g_string_free (range, TRUE);
++ g_uri_unref (uri);
++
++ soup_test_session_abort_unref (session);
++}
++
++int
++main (int argc, char **argv)
++{
++ int ret;
++
++ test_init (argc, argv, NULL);
++
++ #ifndef G_OS_WIN32
++ struct rlimit new_rlimit = { 1024 * 1024 * 64, 1024 * 1024 * 64 };
++ /* limit memory usage, to trigger too large memory allocation abort */
++ g_assert_cmpint (setrlimit (RLIMIT_DATA, &new_rlimit), ==, 0);
++ #endif
++
++ g_test_add ("/server-mem/range-overlaps", ServerData, NULL,
++ server_setup, do_ranges_overlaps_test, server_teardown);
++
++ ret = g_test_run ();
++
++ test_cleanup ();
++ return ret;
++}
diff --git a/gnu/packages/patches/libsoup-memory-leak-in-soup_form_decode.patch b/gnu/packages/patches/libsoup-memory-leak-in-soup_form_decode.patch
new file mode 100644
index 0000000000..ccba980ab7
--- /dev/null
+++ b/gnu/packages/patches/libsoup-memory-leak-in-soup_form_decode.patch
@@ -0,0 +1,101 @@
+From: Milan Crha <mcrha@redhat.com>
+Date: Tue, 13 May 2025 10:38:49 +0200
+Subject: soup-form: Fix a possible memory leak in
+ soup_form_decode_multipart()
+
+The output variables can be set multiple times, when there are multiparts
+with the same name, thus first clear any previously value and only then
+assign a new value.
+
+Origin: upstream, 3.7.0, commit:66b5c5be947062df9caf7025b56ee1de32aee3ac
+Bug: https://gitlab.gnome.org/GNOME/libsoup/-/issues/446
+---
+ libsoup/soup-form.c | 12 +++++++++---
+ tests/forms-test.c | 41 +++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 50 insertions(+), 3 deletions(-)
+
+diff --git a/libsoup/soup-form.c b/libsoup/soup-form.c
+index 2eb5d57..98130c8 100644
+--- a/libsoup/soup-form.c
++++ b/libsoup/soup-form.c
+@@ -168,12 +168,18 @@ soup_form_decode_multipart (SoupMultipart *multipart,
+ }
+
+ if (file_control_name && !strcmp (name, file_control_name)) {
+- if (filename)
++ if (filename) {
++ g_free (*filename);
+ *filename = g_strdup (g_hash_table_lookup (params, "filename"));
+- if (content_type)
++ }
++ if (content_type) {
++ g_free (*content_type);
+ *content_type = g_strdup (soup_message_headers_get_content_type (part_headers, NULL));
+- if (file)
++ }
++ if (file) {
++ g_clear_pointer (file, g_bytes_unref);
+ *file = g_bytes_ref (part_body);
++ }
+ } else {
+ g_hash_table_insert (form_data_set,
+ g_strdup (name),
+diff --git a/tests/forms-test.c b/tests/forms-test.c
+index 1002374..183900f 100644
+--- a/tests/forms-test.c
++++ b/tests/forms-test.c
+@@ -485,6 +485,46 @@ md5_callback (SoupServer *server,
+ soup_server_message_set_status (msg, SOUP_STATUS_METHOD_NOT_ALLOWED, NULL);
+ }
+
++static void
++do_form_decode_multipart_test (void)
++{
++ SoupMultipart *multipart = soup_multipart_new ("multipart/form-data");
++ const char *file_control_name = "uploaded_file";
++ char *content_type = NULL;
++ char *filename = NULL;
++ GBytes *file = NULL;
++ GHashTable *result;
++ int part;
++
++ for (part = 0; part < 2; part++) {
++ SoupMessageHeaders *headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
++ GHashTable *params = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
++ GBytes *body = g_bytes_new (NULL, 0);
++
++ g_hash_table_insert (params, g_strdup ("name"), g_strdup (file_control_name));
++ g_hash_table_insert (params, g_strdup ("filename"), g_strdup (file_control_name));
++ soup_message_headers_set_content_disposition (headers, "form-data", params);
++ soup_message_headers_set_content_type (headers, "text/x-form", NULL);
++ soup_multipart_append_part (multipart, headers, body);
++
++ soup_message_headers_unref (headers);
++ g_hash_table_destroy (params);
++ g_bytes_unref (body);
++ }
++
++ /* this would leak memory of the output variables, due to two parts having the same 'file_control_name' */
++ result = soup_form_decode_multipart (multipart, file_control_name, &filename, &content_type, &file);
++ g_assert_nonnull (result);
++ g_assert_cmpstr (content_type, ==, "text/x-form");
++ g_assert_cmpstr (filename, ==, file_control_name);
++ g_assert_nonnull (file);
++
++ g_hash_table_destroy (result);
++ g_free (content_type);
++ g_free (filename);
++ g_bytes_unref (file);
++}
++
+ static gboolean run_tests = TRUE;
+
+ static GOptionEntry no_test_entry[] = {
+@@ -525,6 +565,7 @@ main (int argc, char **argv)
+ g_uri_unref (uri);
+
+ g_test_add_func ("/forms/decode", do_form_decode_test);
++ g_test_add_func ("/forms/decodemultipart", do_form_decode_multipart_test);
+
+ ret = g_test_run ();
+ } else {
diff --git a/gnu/packages/patches/libsoup-multipart-bounds-check.patch b/gnu/packages/patches/libsoup-multipart-bounds-check.patch
new file mode 100644
index 0000000000..0bafe09206
--- /dev/null
+++ b/gnu/packages/patches/libsoup-multipart-bounds-check.patch
@@ -0,0 +1,106 @@
+From: Milan Crha <mcrha@redhat.com>
+Date: Tue, 15 Apr 2025 09:03:00 +0200
+Subject: multipart: Fix read out of buffer bounds under
+ soup_multipart_new_from_message()
+
+This is CVE-2025-32914, special crafted input can cause read out of buffer bounds
+of the body argument.
+
+Origin: upstream, 3.7.0, commit:5bfcf8157597f2d327050114fb37ff600004dbcf
+Bug: https://gitlab.gnome.org/GNOME/libsoup/-/issues/436
+Bug-CVE: https://security-tracker.debian.org/tracker/CVE-2025-32914
+Bug-Debian: https://bugs.debian.org/1103267
+---
+ libsoup/soup-multipart.c | 2 +-
+ tests/multipart-test.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 59 insertions(+), 1 deletion(-)
+
+diff --git a/libsoup/soup-multipart.c b/libsoup/soup-multipart.c
+index 2421c91..102ce37 100644
+--- a/libsoup/soup-multipart.c
++++ b/libsoup/soup-multipart.c
+@@ -173,7 +173,7 @@ soup_multipart_new_from_message (SoupMessageHeaders *headers,
+ return NULL;
+ }
+
+- split = strstr (start, "\r\n\r\n");
++ split = g_strstr_len (start, body_end - start, "\r\n\r\n");
+ if (!split || split > end) {
+ soup_multipart_free (multipart);
+ return NULL;
+diff --git a/tests/multipart-test.c b/tests/multipart-test.c
+index 2c0e7e9..f5b9868 100644
+--- a/tests/multipart-test.c
++++ b/tests/multipart-test.c
+@@ -471,6 +471,62 @@ test_multipart (gconstpointer data)
+ loop = NULL;
+ }
+
++static void
++test_multipart_bounds_good (void)
++{
++ #define TEXT "line1\r\nline2"
++ SoupMultipart *multipart;
++ SoupMessageHeaders *headers, *set_headers = NULL;
++ GBytes *bytes, *set_bytes = NULL;
++ const char *raw_data = "--123\r\nContent-Type: text/plain;\r\n\r\n" TEXT "\r\n--123--\r\n";
++ gboolean success;
++
++ headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
++ soup_message_headers_append (headers, "Content-Type", "multipart/mixed; boundary=\"123\"");
++
++ bytes = g_bytes_new (raw_data, strlen (raw_data));
++
++ multipart = soup_multipart_new_from_message (headers, bytes);
++
++ g_assert_nonnull (multipart);
++ g_assert_cmpint (soup_multipart_get_length (multipart), ==, 1);
++ success = soup_multipart_get_part (multipart, 0, &set_headers, &set_bytes);
++ g_assert_true (success);
++ g_assert_nonnull (set_headers);
++ g_assert_nonnull (set_bytes);
++ g_assert_cmpint (strlen (TEXT), ==, g_bytes_get_size (set_bytes));
++ g_assert_cmpstr ("text/plain", ==, soup_message_headers_get_content_type (set_headers, NULL));
++ g_assert_cmpmem (TEXT, strlen (TEXT), g_bytes_get_data (set_bytes, NULL), g_bytes_get_size (set_bytes));
++
++ soup_message_headers_unref (headers);
++ g_bytes_unref (bytes);
++
++ soup_multipart_free (multipart);
++
++ #undef TEXT
++}
++
++static void
++test_multipart_bounds_bad (void)
++{
++ SoupMultipart *multipart;
++ SoupMessageHeaders *headers;
++ GBytes *bytes;
++ const char *raw_data = "--123\r\nContent-Type: text/plain;\r\nline1\r\nline2\r\n--123--\r\n";
++
++ headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_MULTIPART);
++ soup_message_headers_append (headers, "Content-Type", "multipart/mixed; boundary=\"123\"");
++
++ bytes = g_bytes_new (raw_data, strlen (raw_data));
++
++ /* it did read out of raw_data/bytes bounds */
++ multipart = soup_multipart_new_from_message (headers, bytes);
++ g_assert_null (multipart);
++
++ soup_message_headers_unref (headers);
++ g_bytes_unref (bytes);
++}
++
+ int
+ main (int argc, char **argv)
+ {
+@@ -498,6 +554,8 @@ main (int argc, char **argv)
+ g_test_add_data_func ("/multipart/sync", GINT_TO_POINTER (SYNC_MULTIPART), test_multipart);
+ g_test_add_data_func ("/multipart/async", GINT_TO_POINTER (ASYNC_MULTIPART), test_multipart);
+ g_test_add_data_func ("/multipart/async-small-reads", GINT_TO_POINTER (ASYNC_MULTIPART_SMALL_READS), test_multipart);
++ g_test_add_func ("/multipart/bounds-good", test_multipart_bounds_good);
++ g_test_add_func ("/multipart/bounds-bad", test_multipart_bounds_bad);
+
+ ret = g_test_run ();
+
diff --git a/gnu/packages/patches/libsoup-use-libdl-instead-of-gmodule.patch b/gnu/packages/patches/libsoup-use-libdl-instead-of-gmodule.patch
new file mode 100644
index 0000000000..3b6608cb04
--- /dev/null
+++ b/gnu/packages/patches/libsoup-use-libdl-instead-of-gmodule.patch
@@ -0,0 +1,70 @@
+From: Fabio Manganiello <fabio@manganiello.tech>
+Date: Tue, 15 Jul 2025 15:41:47 +0200
+Subject: soup-init: Use libdl instead of gmodule in `soup2_is_loaded` check
+
+Calling `g_module_open` in the library constructor can cause deadlocks
+when libsoup is used with other libraries that also contend for GLib
+mutexes. `dlopen` should be used instead.
+
+Co-authored-by: Nirbheek Chauhan <nirbheek@centricular.com>
+Bug: https://gitlab.gnome.org/GNOME/libsoup/-/issues/463
+Bug: https://gitlab.gnome.org/GNOME/glib/-/issues/1443
+Bug-Debian: https://bugs.debian.org/1109685
+Origin: https://gitlab.gnome.org/GNOME/libsoup/-/merge_requests/475
+Applied-upstream: 3.7.0, commit:1296cbf983f036f20262c453926dff77e1d6a852
+Applied-upstream: 3.6.6, commit:2316e56a5502ac4c41ef4ff56a3266e680aca129
+---
+ libsoup/soup-init.c | 28 +++++++++++++++++-----------
+ 1 file changed, 17 insertions(+), 11 deletions(-)
+
+diff --git a/libsoup/soup-init.c b/libsoup/soup-init.c
+index 8a33c77..3392e8e 100644
+--- a/libsoup/soup-init.c
++++ b/libsoup/soup-init.c
+@@ -10,7 +10,6 @@
+ #endif
+
+ #include <glib/gi18n-lib.h>
+-#include <gmodule.h>
+ #include "gconstructor.h"
+
+ #ifdef G_OS_WIN32
+@@ -18,21 +17,28 @@
+ #include <windows.h>
+
+ HMODULE soup_dll;
++#else
++#include <dlfcn.h>
+ #endif
+
+ static gboolean
+ soup2_is_loaded (void)
+ {
+- GModule *module = g_module_open (NULL, 0);
+- gpointer func;
+- gboolean result = FALSE;
+-
+- if (g_module_symbol (module, "soup_uri_new", &func))
+- result = TRUE;
+-
+- g_module_close (module);
+-
+- return result;
++ gboolean result = FALSE;
++
++ /* Skip on PE/COFF, as it doesn't have a flat symbol namespace */
++#ifndef G_OS_WIN32
++ gpointer handle;
++ gpointer func;
++
++ handle = dlopen (NULL, RTLD_LAZY | RTLD_GLOBAL);
++ if (handle != NULL) {
++ func = dlsym (handle, "soup_uri_new");
++ result = (func != NULL);
++ dlclose (handle);
++ }
++#endif
++ return result;
+ }
+
+ static void