diff --git a/SOURCES/0001-Tests-Add-test-for-host-header-validation.patch b/SOURCES/0001-Tests-Add-test-for-host-header-validation.patch new file mode 100644 index 0000000..08ff1ae --- /dev/null +++ b/SOURCES/0001-Tests-Add-test-for-host-header-validation.patch @@ -0,0 +1,180 @@ +From 125e957092083ea37cf8ac712fa62587e3817242 Mon Sep 17 00:00:00 2001 +From: Jens Georg +Date: Mon, 10 May 2021 11:45:57 +0200 +Subject: [PATCH] Tests: Add test for host header validation + +--- + 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 5848d02..a8b4a75 100644 +--- a/libgupnp/gupnp-context-private.h ++++ b/libgupnp/gupnp-context-private.h +@@ -42,6 +42,11 @@ gupnp_context_ip_is_ours (GUPnPContext *context, const char *address); + 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 0381474..ec88b93 100644 +--- a/libgupnp/gupnp-context.c ++++ b/libgupnp/gupnp-context.c +@@ -1585,9 +1585,11 @@ out: + } + + 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. +@@ -1610,8 +1612,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, " +@@ -1631,6 +1636,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/0001-context-Use-SoupURI-instead-of-GUri.patch b/SOURCES/0001-context-Use-SoupURI-instead-of-GUri.patch new file mode 100644 index 0000000..d5eaa0a --- /dev/null +++ b/SOURCES/0001-context-Use-SoupURI-instead-of-GUri.patch @@ -0,0 +1,63 @@ +From 8ed9a525a97091c2a416c82f05e8311837cc7600 Mon Sep 17 00:00:00 2001 +From: Jens Georg +Date: Wed, 2 Jun 2021 12:43:45 +0200 +Subject: [PATCH] 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 ec88b93..dda565e 100644 +--- a/libgupnp/gupnp-context.c ++++ b/libgupnp/gupnp-context.c +@@ -1595,26 +1595,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; + } + +@@ -1635,8 +1631,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/SOURCES/0001-service-Validate-host-header.patch b/SOURCES/0001-service-Validate-host-header.patch new file mode 100644 index 0000000..1c06280 --- /dev/null +++ b/SOURCES/0001-service-Validate-host-header.patch @@ -0,0 +1,118 @@ +From 05e964d48322ff23a65c6026d656e4494ace6ff9 Mon Sep 17 00:00:00 2001 +From: Jens Georg +Date: Mon, 10 May 2021 10:34:36 +0200 +Subject: [PATCH] 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 +--- + 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 801d679..5848d02 100644 +--- a/libgupnp/gupnp-context-private.h ++++ b/libgupnp/gupnp-context-private.h +@@ -39,6 +39,9 @@ _gupnp_context_add_server_handler_with_data (GUPnPContext *context, + G_GNUC_INTERNAL gboolean + gupnp_context_ip_is_ours (GUPnPContext *context, const char *address); + ++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 1732bf4..0381474 100644 +--- a/libgupnp/gupnp-context.c ++++ b/libgupnp/gupnp-context.c +@@ -1583,3 +1583,54 @@ out: + + return retval; + } ++ ++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 4235cab..50765f0 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/SPECS/gupnp.spec b/SPECS/gupnp.spec index bf6133c..44fe08b 100644 --- a/SPECS/gupnp.spec +++ b/SPECS/gupnp.spec @@ -1,6 +1,6 @@ Name: gupnp Version: 1.0.6 -Release: 1%{?dist} +Release: 2%{?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: 0001-Tests-Add-test-for-host-header-validation.patch +Patch2: 0001-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 # Use Python 3 sed -i '1s|^#! /usr/bin/env python$|#!/usr/bin/python3|' tools/gupnp-binding-tool @@ -79,6 +87,11 @@ find %{buildroot} -name '*.la' -delete %doc %{_datadir}/gtk-doc/html/%{name} %changelog +* Wed Jun 02 2021 Bastien Nocera - 1.0.6-2 ++ gupnp-1.0.6-2 +- Fix DNS rebind issue +- Resolves: #1964710 + * Wed Sep 30 2020 Bastien Nocera - 1.0.6-1 + gupnp-1.0.6-1 - Update to 1.0.6