diff --git a/SOURCES/0001-service-Validate-host-header.patch b/SOURCES/0001-service-Validate-host-header.patch new file mode 100644 index 0000000..a173e52 --- /dev/null +++ b/SOURCES/0001-service-Validate-host-header.patch @@ -0,0 +1,122 @@ +From 1d399272672308b05c964aedd61c11a2d4e2a3ac Mon Sep 17 00:00:00 2001 +From: Jens Georg +Date: Mon, 10 May 2021 10:34:36 +0200 +Subject: [PATCH 1/3] service: Validate host header + +Make sure that the host header matches the ip:port of the context. + +This is in line with UDA (Host header is required and must match the +location url) and DLNA 7.2.24.1 (All communication has to use ip +addresses and not names) + +Prevents DNS rebinding attacs against agains UPnP services + + Conflicts: + libgupnp/gupnp-context-private.h + libgupnp/gupnp-context.c +--- + libgupnp/gupnp-context-private.h | 3 ++ + libgupnp/gupnp-context.c | 51 ++++++++++++++++++++++++++++++++ + libgupnp/gupnp-service.c | 13 ++++++++ + 3 files changed, 67 insertions(+) + +diff --git a/libgupnp/gupnp-context-private.h b/libgupnp/gupnp-context-private.h +index c088563..f0fe52e 100644 +--- a/libgupnp/gupnp-context-private.h ++++ b/libgupnp/gupnp-context-private.h +@@ -36,6 +36,9 @@ _gupnp_context_add_server_handler_with_data (GUPnPContext *context, + const char *path, + AclServerHandler *data); + ++G_GNUC_INTERNAL gboolean ++gupnp_context_validate_host_header (GUPnPContext *context, const char *host); ++ + G_END_DECLS + + #endif /* __GUPNP_CONTEXT_PRIVATE_H__ */ +diff --git a/libgupnp/gupnp-context.c b/libgupnp/gupnp-context.c +index 641c59a..7f13f47 100644 +--- a/libgupnp/gupnp-context.c ++++ b/libgupnp/gupnp-context.c +@@ -1558,3 +1558,54 @@ gupnp_context_remove_server_handler (GUPnPContext *context, const char *path) + + soup_server_remove_handler (context->priv->server, path); + } ++ ++gboolean ++gupnp_context_validate_host_header (GUPnPContext *context, ++ const char *host_header) ++{ ++ gboolean retval = FALSE; ++ // Be lazy and let GUri do the heavy lifting here, such as stripping the ++ // [] from v6 addresses, splitting of the port etc. ++ char *uri_from_host = g_strconcat ("http://", host_header, NULL); ++ ++ char *host = NULL; ++ int port = 0; ++ GError *error = NULL; ++ ++ g_uri_split_network (uri_from_host, ++ G_URI_FLAGS_NONE, ++ NULL, ++ &host, ++ &port, ++ &error); ++ ++ if (error != NULL) { ++ g_debug ("Failed to parse HOST header from request: %s", ++ error->message); ++ goto out; ++ } ++ ++ const char *host_ip = gssdp_client_get_host_ip (GSSDP_CLIENT (context)); ++ gint context_port = gupnp_context_get_port (context); ++ ++ if (!g_str_equal (host, host_ip)) { ++ g_debug ("Mismatch between host header and host IP (%s, " ++ "expected: %s)", ++ host, ++ host_ip); ++ } ++ ++ if (port != context_port) { ++ g_debug ("Mismatch between host header and host port (%d, " ++ "expected %d)", ++ port, ++ context_port); ++ } ++ ++ retval = g_str_equal (host, host_ip) && port == context_port; ++ ++out: ++ g_clear_error (&error); ++ g_free (uri_from_host); ++ return retval; ++} +diff --git a/libgupnp/gupnp-service.c b/libgupnp/gupnp-service.c +index 0417852..1c7896b 100644 +--- a/libgupnp/gupnp-service.c ++++ b/libgupnp/gupnp-service.c +@@ -949,6 +949,19 @@ control_server_handler (SoupServer *server, + + context = gupnp_service_info_get_context (GUPNP_SERVICE_INFO (service)); + ++ const char *host_header = ++ soup_message_headers_get_one (msg->request_headers, "Host"); ++ ++ if (!gupnp_context_validate_host_header (context, host_header)) { ++ g_warning ("Host header mismatch, expected %s:%d, got %s", ++ gssdp_client_get_host_ip (GSSDP_CLIENT (context)), ++ gupnp_context_get_port (context), ++ host_header); ++ ++ soup_message_set_status (msg, SOUP_STATUS_PRECONDITION_FAILED); ++ return; ++ } ++ + /* Get action name */ + soap_action = soup_message_headers_get_one (msg->request_headers, + "SOAPAction"); +-- +2.31.1 + diff --git a/SOURCES/0002-Tests-Add-test-for-host-header-validation.patch b/SOURCES/0002-Tests-Add-test-for-host-header-validation.patch new file mode 100644 index 0000000..a3b6100 --- /dev/null +++ b/SOURCES/0002-Tests-Add-test-for-host-header-validation.patch @@ -0,0 +1,182 @@ +From 3b33ac7859325a451f7d6a9a7f358418860d0f48 Mon Sep 17 00:00:00 2001 +From: Jens Georg +Date: Mon, 10 May 2021 11:45:57 +0200 +Subject: [PATCH 2/3] Tests: Add test for host header validation + +Conflicts: + libgupnp/gupnp-context.c +--- + libgupnp/gupnp-context-private.h | 5 ++ + libgupnp/gupnp-context.c | 25 ++++++++-- + tests/gtest/test-bugs.c | 78 ++++++++++++++++++++++++++++++-- + 3 files changed, 99 insertions(+), 9 deletions(-) + +diff --git a/libgupnp/gupnp-context-private.h b/libgupnp/gupnp-context-private.h +index f0fe52e..a141305 100644 +--- a/libgupnp/gupnp-context-private.h ++++ b/libgupnp/gupnp-context-private.h +@@ -39,6 +39,11 @@ _gupnp_context_add_server_handler_with_data (GUPnPContext *context, + G_GNUC_INTERNAL gboolean + gupnp_context_validate_host_header (GUPnPContext *context, const char *host); + ++gboolean ++validate_host_header (const char *host_header, ++ const char *host_ip, ++ guint context_port); ++ + G_END_DECLS + + #endif /* __GUPNP_CONTEXT_PRIVATE_H__ */ +diff --git a/libgupnp/gupnp-context.c b/libgupnp/gupnp-context.c +index 7f13f47..878acf3 100644 +--- a/libgupnp/gupnp-context.c ++++ b/libgupnp/gupnp-context.c +@@ -1560,9 +1560,11 @@ gupnp_context_remove_server_handler (GUPnPContext *context, const char *path) + } + + gboolean +-gupnp_context_validate_host_header (GUPnPContext *context, +- const char *host_header) ++validate_host_header (const char *host_header, ++ const char *host_ip, ++ guint context_port) + { ++ + gboolean retval = FALSE; + // Be lazy and let GUri do the heavy lifting here, such as stripping the + // [] from v6 addresses, splitting of the port etc. +@@ -1585,8 +1587,11 @@ gupnp_context_validate_host_header (GUPnPContext *context, + goto out; + } + +- const char *host_ip = gssdp_client_get_host_ip (GSSDP_CLIENT (context)); +- gint context_port = gupnp_context_get_port (context); ++ // -1 means there was no :port; according to UDA this is allowed and ++ // defaults to 80, the HTTP port then ++ if (port == -1) { ++ port = 80; ++ } + + if (!g_str_equal (host, host_ip)) { + g_debug ("Mismatch between host header and host IP (%s, " +@@ -1606,6 +1611,18 @@ gupnp_context_validate_host_header (GUPnPContext *context, + + out: + g_clear_error (&error); ++ g_free (host); + g_free (uri_from_host); ++ + return retval; + } ++ ++gboolean ++gupnp_context_validate_host_header (GUPnPContext *context, ++ const char *host_header) ++{ ++ return validate_host_header ( ++ host_header, ++ gssdp_client_get_host_ip (GSSDP_CLIENT (context)), ++ gupnp_context_get_port (context)); ++} +diff --git a/tests/gtest/test-bugs.c b/tests/gtest/test-bugs.c +index 0ffac76..24ec4ba 100644 +--- a/tests/gtest/test-bugs.c ++++ b/tests/gtest/test-bugs.c +@@ -24,6 +24,7 @@ + #endif + + #include ++#include + + + struct _GUPnPServiceAction { +@@ -468,14 +469,81 @@ test_bgo_743233 (void) + g_object_unref (context); + } + ++static void ++test_ggo_24 (void) ++{ ++ // IPv4 ++ g_assert ( ++ validate_host_header ("127.0.0.1:4711", "127.0.0.1", 4711)); ++ ++ g_assert ( ++ validate_host_header ("127.0.0.1", "127.0.0.1", 80)); ++ ++ g_assert_false ( ++ validate_host_header ("example.com", "127.0.0.1", 4711)); ++ ++ g_assert_false ( ++ validate_host_header ("example.com:80", "127.0.0.1", 4711)); ++ ++ g_assert_false ( ++ validate_host_header ("example.com:4711", "127.0.0.1", 4711)); ++ ++ g_assert_false ( ++ validate_host_header ("192.168.1.2:4711", "127.0.0.1", 4711)); ++ ++ g_assert_false ( ++ validate_host_header ("[fe80::01]", "127.0.0.1", 4711)); ++ ++ // Link ids should not be parsed ++ g_assert_false ( ++ validate_host_header ("[fe80::01%1]", "127.0.0.1", 4711)); ++ ++ g_assert_false ( ++ validate_host_header ("[fe80::01%eth0]", "127.0.0.1", 4711)); ++ ++ // IPv6 ++ g_assert ( ++ validate_host_header ("[::1]:4711", "::1", 4711)); ++ ++ g_assert ( ++ validate_host_header ("[::1]", "::1", 80)); ++ ++ // Host header needs to be enclosed in [] even without port ++ g_assert_false ( ++ validate_host_header ("::1", "::1", 80)); ++ ++ g_assert_false ( ++ validate_host_header ("example.com", "::1", 4711)); ++ ++ g_assert_false ( ++ validate_host_header ("example.com:80", "::1", 4711)); ++ ++ g_assert_false ( ++ validate_host_header ("example.com:4711", "::1", 4711)); ++ ++ g_assert_false ( ++ validate_host_header ("192.168.1.2:4711", "::1", 4711)); ++ ++ g_assert_false ( ++ validate_host_header ("[fe80::01]", "::1", 4711)); ++ ++ // Link ids should not be parsed ++ g_assert_false ( ++ validate_host_header ("[fe80::01%1]", "fe80::acab", 4711)); ++ ++ g_assert_false ( ++ validate_host_header ("[fe80::01%eth0]", "fe80::acab", 4711)); ++} ++ + int + main (int argc, char *argv[]) { + g_test_init (&argc, &argv, NULL); +- g_test_add_func ("/bugs/696762", test_bgo_696762); +- g_test_add_func ("/bugs/678701", test_bgo_678701); +- g_test_add_func ("/bugs/690400", test_bgo_690400); +- g_test_add_func ("/bugs/722696", test_bgo_722696); +- g_test_add_func ("/bugs/743233", test_bgo_743233); ++ g_test_add_func ("/bugs/bgo/696762", test_bgo_696762); ++ g_test_add_func ("/bugs/bgo/678701", test_bgo_678701); ++ g_test_add_func ("/bugs/bgo/690400", test_bgo_690400); ++ g_test_add_func ("/bugs/bgo/722696", test_bgo_722696); ++ g_test_add_func ("/bugs/bgo/743233", test_bgo_743233); ++ g_test_add_func ("/bugs/ggo/24", test_ggo_24); + + return g_test_run (); + } +-- +2.31.1 + diff --git a/SOURCES/0003-context-Use-SoupURI-instead-of-GUri.patch b/SOURCES/0003-context-Use-SoupURI-instead-of-GUri.patch new file mode 100644 index 0000000..468b4c1 --- /dev/null +++ b/SOURCES/0003-context-Use-SoupURI-instead-of-GUri.patch @@ -0,0 +1,63 @@ +From c58c1b0ffa7d26ad0bc016a874744ffdce9d9d5b Mon Sep 17 00:00:00 2001 +From: Jens Georg +Date: Wed, 2 Jun 2021 12:43:45 +0200 +Subject: [PATCH 3/3] context: Use SoupURI instead of GUri + +Do not bump the implicit requirement to GLib 2.66 for this version +--- + libgupnp/gupnp-context.c | 25 ++++++++++--------------- + 1 file changed, 10 insertions(+), 15 deletions(-) + +diff --git a/libgupnp/gupnp-context.c b/libgupnp/gupnp-context.c +index 878acf3..af8207f 100644 +--- a/libgupnp/gupnp-context.c ++++ b/libgupnp/gupnp-context.c +@@ -1570,26 +1570,22 @@ validate_host_header (const char *host_header, + // [] from v6 addresses, splitting of the port etc. + char *uri_from_host = g_strconcat ("http://", host_header, NULL); + +- char *host = NULL; ++ const char *host = NULL; + int port = 0; +- GError *error = NULL; +- +- g_uri_split_network (uri_from_host, +- G_URI_FLAGS_NONE, +- NULL, +- &host, +- &port, +- &error); + +- if (error != NULL) { +- g_debug ("Failed to parse HOST header from request: %s", +- error->message); ++ SoupURI *uri = soup_uri_new (uri_from_host); ++ if (uri == NULL) { ++ g_debug ("Failed to parse HOST header %s from request", ++ host_header); + goto out; + } ++ host = soup_uri_get_host (uri); ++ port = soup_uri_get_port (uri); ++ + + // -1 means there was no :port; according to UDA this is allowed and + // defaults to 80, the HTTP port then +- if (port == -1) { ++ if (soup_uri_uses_default_port (uri)) { + port = 80; + } + +@@ -1610,8 +1606,7 @@ validate_host_header (const char *host_header, + retval = g_str_equal (host, host_ip) && port == context_port; + + out: +- g_clear_error (&error); +- g_free (host); ++ g_clear_pointer (&uri, soup_uri_free); + g_free (uri_from_host); + + return retval; +-- +2.31.1 + diff --git a/SPECS/gupnp.spec b/SPECS/gupnp.spec index 076dcba..0276417 100644 --- a/SPECS/gupnp.spec +++ b/SPECS/gupnp.spec @@ -1,6 +1,6 @@ Name: gupnp Version: 1.0.2 -Release: 5%{?dist} +Release: 6%{?dist} Summary: A framework for creating UPnP devices & control points License: LGPLv2+ @@ -17,6 +17,11 @@ BuildRequires: vala Requires: dbus +# https://gitlab.gnome.org/GNOME/gupnp/-/issues/24 +Patch0: 0001-service-Validate-host-header.patch +Patch1: 0002-Tests-Add-test-for-host-header-validation.patch +Patch2: 0003-context-Use-SoupURI-instead-of-GUri.patch + %description GUPnP is an object-oriented open source framework for creating UPnP devices and control points, written in C using GObject and libsoup. @@ -39,6 +44,9 @@ This package contains developer documentation for %{name}. %prep %setup -q +%patch0 -p1 +%patch1 -p1 +%patch2 -p1 %build %configure --disable-static --with-context-manager=network-manager @@ -75,6 +83,11 @@ make check %{?_smp_mflags} V=1 %doc %{_datadir}/gtk-doc/html/%{name} %changelog +* Tue Jun 08 2021 Bastien Nocera - 1.0.2-6 ++ gupnp-1.0.3-3 +- Fix DNS rebind issue +- Resolves: #1964706 + * Mon Jun 04 2018 Richard Hughes - 1.0.2-5 + Update to latest upstream version - Resolves: #1569980