diff --git a/fuzzing/fuzz_key.c b/fuzzing/fuzz_key.c index 77cb684aef..7d00443310 100644 --- a/fuzzing/fuzz_key.c +++ b/fuzzing/fuzz_key.c @@ -26,11 +26,20 @@ test_parse (const gchar *data, GKeyFileFlags flags) { GKeyFile *key = NULL; + char *comment = NULL; + char **list = NULL; key = g_key_file_new (); g_key_file_load_from_data (key, (const gchar*) data, size, G_KEY_FILE_NONE, NULL); + /* Also try some additional parsing and see if it crashes */ + comment = g_key_file_get_comment (key, "group", "key", NULL); + g_free (comment); + + list = g_key_file_get_locale_string_list (key, "group", "key", "de", NULL, NULL); + g_strfreev (list); + g_key_file_free (key); } diff --git a/gio/gdbusauthmechanism.c b/gio/gdbusauthmechanism.c index be1b1dca06..0b52f17e41 100644 --- a/gio/gdbusauthmechanism.c +++ b/gio/gdbusauthmechanism.c @@ -324,6 +324,13 @@ _g_dbus_auth_mechanism_client_data_send (GDBusAuthMechanism *mechanism, return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_data_send (mechanism, out_data_len); } +gchar * +_g_dbus_auth_mechanism_client_get_reject_reason (GDBusAuthMechanism *mechanism) +{ + g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM (mechanism), NULL); + return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_get_reject_reason (mechanism); +} + void _g_dbus_auth_mechanism_client_shutdown (GDBusAuthMechanism *mechanism) { diff --git a/gio/gdbusauthmechanism.h b/gio/gdbusauthmechanism.h index f0edd19a3e..e906a47ac5 100644 --- a/gio/gdbusauthmechanism.h +++ b/gio/gdbusauthmechanism.h @@ -100,6 +100,7 @@ struct _GDBusAuthMechanismClass gsize data_len); gchar *(*client_data_send) (GDBusAuthMechanism *mechanism, gsize *out_data_len); + gchar *(*client_get_reject_reason) (GDBusAuthMechanism *mechanism); void (*client_shutdown) (GDBusAuthMechanism *mechanism); }; @@ -148,6 +149,7 @@ void _g_dbus_auth_mechanism_client_data_receive (GDBus gsize data_len); gchar *_g_dbus_auth_mechanism_client_data_send (GDBusAuthMechanism *mechanism, gsize *out_data_len); +gchar *_g_dbus_auth_mechanism_client_get_reject_reason (GDBusAuthMechanism *mechanism); void _g_dbus_auth_mechanism_client_shutdown (GDBusAuthMechanism *mechanism); diff --git a/gio/gdbusauthmechanismanon.c b/gio/gdbusauthmechanismanon.c index 5f59d4a61d..3d80ec15fe 100644 --- a/gio/gdbusauthmechanismanon.c +++ b/gio/gdbusauthmechanismanon.c @@ -56,7 +56,7 @@ static void mechanism_server_data_receive (GDBusAuthMe gsize data_len); static gchar *mechanism_server_data_send (GDBusAuthMechanism *mechanism, gsize *out_data_len); -static gchar *mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism); +static gchar *mechanism_server_or_client_get_reject_reason (GDBusAuthMechanism *mechanism); static void mechanism_server_shutdown (GDBusAuthMechanism *mechanism); static GDBusAuthMechanismState mechanism_client_get_state (GDBusAuthMechanism *mechanism); static gchar *mechanism_client_initiate (GDBusAuthMechanism *mechanism, @@ -103,12 +103,13 @@ _g_dbus_auth_mechanism_anon_class_init (GDBusAuthMechanismAnonClass *klass) mechanism_class->server_initiate = mechanism_server_initiate; mechanism_class->server_data_receive = mechanism_server_data_receive; mechanism_class->server_data_send = mechanism_server_data_send; - mechanism_class->server_get_reject_reason = mechanism_server_get_reject_reason; + mechanism_class->server_get_reject_reason = mechanism_server_or_client_get_reject_reason; mechanism_class->server_shutdown = mechanism_server_shutdown; mechanism_class->client_get_state = mechanism_client_get_state; mechanism_class->client_initiate = mechanism_client_initiate; mechanism_class->client_data_receive = mechanism_client_data_receive; mechanism_class->client_data_send = mechanism_client_data_send; + mechanism_class->client_get_reject_reason = mechanism_server_or_client_get_reject_reason; mechanism_class->client_shutdown = mechanism_client_shutdown; } @@ -222,12 +223,11 @@ mechanism_server_data_send (GDBusAuthMechanism *mechanism, } static gchar * -mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism) +mechanism_server_or_client_get_reject_reason (GDBusAuthMechanism *mechanism) { GDBusAuthMechanismAnon *m = G_DBUS_AUTH_MECHANISM_ANON (mechanism); g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_ANON (mechanism), NULL); - g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL); g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_REJECTED, NULL); /* can never end up here because we are never in the REJECTED state */ diff --git a/gio/gdbusauthmechanismexternal.c b/gio/gdbusauthmechanismexternal.c index f7cb1b1b99..fce32fa83f 100644 --- a/gio/gdbusauthmechanismexternal.c +++ b/gio/gdbusauthmechanismexternal.c @@ -64,7 +64,7 @@ static void mechanism_server_data_receive (GDBusAuthMe gsize data_len); static gchar *mechanism_server_data_send (GDBusAuthMechanism *mechanism, gsize *out_data_len); -static gchar *mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism); +static gchar *mechanism_server_or_client_get_reject_reason (GDBusAuthMechanism *mechanism); static void mechanism_server_shutdown (GDBusAuthMechanism *mechanism); static GDBusAuthMechanismState mechanism_client_get_state (GDBusAuthMechanism *mechanism); static gchar *mechanism_client_initiate (GDBusAuthMechanism *mechanism, @@ -111,12 +111,13 @@ _g_dbus_auth_mechanism_external_class_init (GDBusAuthMechanismExternalClass *kla mechanism_class->server_initiate = mechanism_server_initiate; mechanism_class->server_data_receive = mechanism_server_data_receive; mechanism_class->server_data_send = mechanism_server_data_send; - mechanism_class->server_get_reject_reason = mechanism_server_get_reject_reason; + mechanism_class->server_get_reject_reason = mechanism_server_or_client_get_reject_reason; mechanism_class->server_shutdown = mechanism_server_shutdown; mechanism_class->client_get_state = mechanism_client_get_state; mechanism_class->client_initiate = mechanism_client_initiate; mechanism_class->client_data_receive = mechanism_client_data_receive; mechanism_class->client_data_send = mechanism_client_data_send; + mechanism_class->client_get_reject_reason = mechanism_server_or_client_get_reject_reason; mechanism_class->client_shutdown = mechanism_client_shutdown; } @@ -321,12 +322,11 @@ mechanism_server_data_send (GDBusAuthMechanism *mechanism, } static gchar * -mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism) +mechanism_server_or_client_get_reject_reason (GDBusAuthMechanism *mechanism) { GDBusAuthMechanismExternal *m = G_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism); g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_EXTERNAL (mechanism), NULL); - g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL); g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_REJECTED, NULL); /* can never end up here because we are never in the REJECTED state */ diff --git a/gio/gdbusauthmechanismsha1.c b/gio/gdbusauthmechanismsha1.c index c8aa08977c..9993bf3245 100644 --- a/gio/gdbusauthmechanismsha1.c +++ b/gio/gdbusauthmechanismsha1.c @@ -119,7 +119,7 @@ static void mechanism_server_data_receive (GDBusAuthMe gsize data_len); static gchar *mechanism_server_data_send (GDBusAuthMechanism *mechanism, gsize *out_data_len); -static gchar *mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism); +static gchar *mechanism_server_or_client_get_reject_reason (GDBusAuthMechanism *mechanism); static void mechanism_server_shutdown (GDBusAuthMechanism *mechanism); static GDBusAuthMechanismState mechanism_client_get_state (GDBusAuthMechanism *mechanism); static gchar *mechanism_client_initiate (GDBusAuthMechanism *mechanism, @@ -172,12 +172,13 @@ _g_dbus_auth_mechanism_sha1_class_init (GDBusAuthMechanismSha1Class *klass) mechanism_class->server_initiate = mechanism_server_initiate; mechanism_class->server_data_receive = mechanism_server_data_receive; mechanism_class->server_data_send = mechanism_server_data_send; - mechanism_class->server_get_reject_reason = mechanism_server_get_reject_reason; + mechanism_class->server_get_reject_reason = mechanism_server_or_client_get_reject_reason; mechanism_class->server_shutdown = mechanism_server_shutdown; mechanism_class->client_get_state = mechanism_client_get_state; mechanism_class->client_initiate = mechanism_client_initiate; mechanism_class->client_data_receive = mechanism_client_data_receive; mechanism_class->client_data_send = mechanism_client_data_send; + mechanism_class->client_get_reject_reason = mechanism_server_or_client_get_reject_reason; mechanism_class->client_shutdown = mechanism_client_shutdown; } @@ -1128,12 +1129,11 @@ mechanism_server_data_send (GDBusAuthMechanism *mechanism, } static gchar * -mechanism_server_get_reject_reason (GDBusAuthMechanism *mechanism) +mechanism_server_or_client_get_reject_reason (GDBusAuthMechanism *mechanism) { GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism); g_return_val_if_fail (G_IS_DBUS_AUTH_MECHANISM_SHA1 (mechanism), NULL); - g_return_val_if_fail (m->priv->is_server && !m->priv->is_client, NULL); g_return_val_if_fail (m->priv->state == G_DBUS_AUTH_MECHANISM_STATE_REJECTED, NULL); return g_strdup (m->priv->reject_reason); @@ -1198,6 +1198,34 @@ mechanism_client_initiate (GDBusAuthMechanism *mechanism, return initial_response; } +/* Context names must be valid ASCII, nonzero length, and may not contain the + * characters slash ("/"), backslash ("\"), space (" "), newline ("\n"), + * carriage return ("\r"), tab ("\t"), or period ("."). + * + * See https://dbus.freedesktop.org/doc/dbus-specification.html#auth-mechanisms-sha */ +static gboolean +validate_cookie_context (const char *cookie_context) +{ + size_t i = 0; + + g_return_val_if_fail (cookie_context != NULL, FALSE); + + for (i = 0; cookie_context[i] != '\0'; i++) + { + if ((uint8_t) cookie_context[i] >= 128 || + cookie_context[i] == '/' || + cookie_context[i] == '\\' || + cookie_context[i] == ' ' || + cookie_context[i] == '\n' || + cookie_context[i] == '\r' || + cookie_context[i] == '\t' || + cookie_context[i] == '.') + return FALSE; + } + + return (i > 0); +} + static void mechanism_client_data_receive (GDBusAuthMechanism *mechanism, const gchar *data, @@ -1206,7 +1234,7 @@ mechanism_client_data_receive (GDBusAuthMechanism *mechanism, GDBusAuthMechanismSha1 *m = G_DBUS_AUTH_MECHANISM_SHA1 (mechanism); gchar **tokens; const gchar *cookie_context; - guint cookie_id; + int64_t cookie_id; const gchar *server_challenge; gchar *client_challenge; gchar *endp; @@ -1232,8 +1260,16 @@ mechanism_client_data_receive (GDBusAuthMechanism *mechanism, } cookie_context = tokens[0]; + if (!validate_cookie_context (tokens[0])) + { + g_free (m->priv->reject_reason); + m->priv->reject_reason = g_strdup_printf ("Malformed cookie_context '%s'", tokens[0]); + m->priv->state = G_DBUS_AUTH_MECHANISM_STATE_REJECTED; + goto out; + } + cookie_id = g_ascii_strtoll (tokens[1], &endp, 10); - if (*endp != '\0') + if (*endp != '\0' || endp == tokens[1] || cookie_id < 0 || cookie_id > UINT32_MAX) { g_free (m->priv->reject_reason); m->priv->reject_reason = g_strdup_printf ("Malformed cookie_id '%s'", tokens[1]); @@ -1243,7 +1279,7 @@ mechanism_client_data_receive (GDBusAuthMechanism *mechanism, server_challenge = tokens[2]; error = NULL; - cookie = keyring_lookup_entry (cookie_context, cookie_id, &error); + cookie = keyring_lookup_entry (cookie_context, (unsigned int) cookie_id, &error); if (cookie == NULL) { g_free (m->priv->reject_reason); diff --git a/gio/gdbusmessage.c b/gio/gdbusmessage.c index 9212035df6..bd778a32d4 100644 --- a/gio/gdbusmessage.c +++ b/gio/gdbusmessage.c @@ -2273,9 +2273,8 @@ g_dbus_message_bytes_needed (guchar *blob, gsize blob_len, GError **error) { - gssize ret; - - ret = -1; + uint32_t header_len, body_len; + size_t ret; g_return_val_if_fail (blob != NULL, -1); g_return_val_if_fail (error == NULL || *error == NULL, -1); @@ -2283,21 +2282,13 @@ g_dbus_message_bytes_needed (guchar *blob, if (blob[0] == 'l') { - /* core header (12 bytes) + ARRAY of STRUCT of (BYTE,VARIANT) */ - ret = 12 + 4 + GUINT32_FROM_LE (((guint32 *) blob)[3]); - /* round up so it's a multiple of 8 */ - ret = 8 * ((ret + 7)/8); - /* finally add the body size */ - ret += GUINT32_FROM_LE (((guint32 *) blob)[1]); + header_len = GUINT32_FROM_LE (((guint32 *) blob)[3]); + body_len = GUINT32_FROM_LE (((guint32 *) blob)[1]); } else if (blob[0] == 'B') { - /* core header (12 bytes) + ARRAY of STRUCT of (BYTE,VARIANT) */ - ret = 12 + 4 + GUINT32_FROM_BE (((guint32 *) blob)[3]); - /* round up so it's a multiple of 8 */ - ret = 8 * ((ret + 7)/8); - /* finally add the body size */ - ret += GUINT32_FROM_BE (((guint32 *) blob)[1]); + header_len = GUINT32_FROM_BE (((guint32 *) blob)[3]); + body_len = GUINT32_FROM_BE (((guint32 *) blob)[1]); } else { @@ -2305,18 +2296,29 @@ g_dbus_message_bytes_needed (guchar *blob, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Unable to determine message blob length - given blob is malformed"); + return -1; } - if (ret > (1<<27)) + /* core header (12 bytes) + array length (4 bytes) + length of array. + * Array elements are tuples: (byte, variant) */ + ret = 0; + if (!g_size_checked_add (&ret, 12 + 4, header_len) || + /* round up so it's a multiple of 8: ret = 8 * ((ret + 7)/8) */ + !g_size_checked_add (&ret, ret, 7) || + !g_size_checked_mul (&ret, 8, ret/8) || + /* finally add the body size */ + !g_size_checked_add (&ret, ret, body_len) || + ret > (1 << 27)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Blob indicates that message exceeds maximum message length (128MiB)"); - ret = -1; + return -1; } - return ret; + g_assert (ret <= G_MAXSSIZE); + return (gssize) ret; } /* ---------------------------------------------------------------------------------------------------- */ diff --git a/gio/tests/gdbus-auth-mechanism-sha1.c b/gio/tests/gdbus-auth-mechanism-sha1.c new file mode 100644 index 0000000000..abcdb4e3e7 --- /dev/null +++ b/gio/tests/gdbus-auth-mechanism-sha1.c @@ -0,0 +1,177 @@ +/* GLib testing framework examples and tests + * + * Copyright (C) 2026 Philip Withnall + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see . + * + * Author: Philip Withnall + */ + +#include +#include + +#include +#include + +#include "gdbus-tests.h" + +#ifdef G_OS_UNIX +#include +#include +#include +#include +#endif + +#define GIO_COMPILATION 1 +#include "gdbusauthmechanism.h" +#include "gdbusauthmechanismsha1.h" + +/* Vfunc wrappers copied from gdbusauthmechanism.c as they are not public. */ +static gboolean +dbus_auth_mechanism_is_supported (GDBusAuthMechanism *mechanism) +{ + return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->is_supported (mechanism); +} + +static GDBusAuthMechanismState +dbus_auth_mechanism_client_get_state (GDBusAuthMechanism *mechanism) +{ + return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_get_state (mechanism); +} + +static gchar * +dbus_auth_mechanism_client_initiate (GDBusAuthMechanism *mechanism, + GDBusConnectionFlags conn_flags, + size_t *out_initial_response_len) +{ + return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_initiate (mechanism, + conn_flags, + out_initial_response_len); +} + +static void +dbus_auth_mechanism_client_data_receive (GDBusAuthMechanism *mechanism, + const char *data, + size_t data_len) +{ + G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_data_receive (mechanism, data, data_len); +} + +static char * +dbus_auth_mechanism_client_get_reject_reason (GDBusAuthMechanism *mechanism) +{ + return G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_get_reject_reason (mechanism); +} + +static void +dbus_auth_mechanism_client_shutdown (GDBusAuthMechanism *mechanism) +{ + G_DBUS_AUTH_MECHANISM_GET_CLASS (mechanism)->client_shutdown (mechanism); +} + +static void +test_server_challenge_validation (void) +{ + const struct + { + const char *server_challenge; + const char *expected_reject_reason_prefix; + } + vectors[] = { + { "valid_context 123 456", "Problems looking up entry in keyring" }, + { "invalid/context 123 456", "Malformed cookie_context" }, + { "invalid.context 123 456", "Malformed cookie_context" }, + { " 123 456", "Malformed cookie_context" }, + { "😀 123 456", "Malformed cookie_context" }, + { "invalid\ncontext 123 456", "Malformed cookie_context" }, + { "invalid\rcontext 123 456", "Malformed cookie_context" }, + { "invalid\tcontext 123 456", "Malformed cookie_context" }, + { "invalid\\context 123 456", "Malformed cookie_context" }, + { "valid_context 456", "Malformed cookie_id" }, + { "valid_context 123notanumber 456", "Malformed cookie_id" }, + { "valid_context -1 456", "Malformed cookie_id" }, + { "valid_context 4294967296 456", "Malformed cookie_id" }, + { "valid_context 123 ", "Malformed data" }, + { "valid_context ", "Malformed data" }, + }; + GType mechanism_type; + GDBusConnection *connection = NULL; + + g_test_summary ("Test that GDBusAuthMechanismSha1 rejects various malformed server data lines"); + + /* Briefly connect to the actual bus to ensure the GDBusAuth mechanisms are + * all registered. */ + session_bus_up (); + + connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); + g_assert_nonnull (connection); + g_clear_object (&connection); + + session_bus_down (); + + /* Check that we now have the type ID for GDBusAuthMechanismSha1 */ + mechanism_type = g_type_from_name ("GDBusAuthMechanismSha1"); + g_assert_cmpint (mechanism_type, !=, 0); + + for (size_t i = 0; i < G_N_ELEMENTS (vectors); i++) + { + GDBusAuthMechanism *mechanism = NULL; + char *data = NULL; + size_t data_len = 0; + char *reject_reason = NULL; + + mechanism = g_object_new (mechanism_type, NULL); + + if (!dbus_auth_mechanism_is_supported (mechanism)) + { + g_test_skip ("Mechanism not supported"); + g_clear_object (&mechanism); + return; + } + + data = dbus_auth_mechanism_client_initiate (mechanism, + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, + &data_len); + g_free (data); + + dbus_auth_mechanism_client_data_receive (mechanism, vectors[i].server_challenge, strlen (vectors[i].server_challenge)); + + g_assert_cmpint (dbus_auth_mechanism_client_get_state (mechanism), ==, G_DBUS_AUTH_MECHANISM_STATE_REJECTED); + + reject_reason = dbus_auth_mechanism_client_get_reject_reason (mechanism); + g_assert_true (g_str_has_prefix (reject_reason, vectors[i].expected_reject_reason_prefix)); + g_free (reject_reason); + + dbus_auth_mechanism_client_shutdown (mechanism); + + g_clear_object (&mechanism); + } +} + +int +main (int argc, + char *argv[]) +{ + setlocale (LC_ALL, "C"); + + g_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL); + + g_test_dbus_unset (); + + g_test_add_func ("/gdbus/auth-mechanism-sha1/server-challenge-validation", test_server_challenge_validation); + + return g_test_run (); +} diff --git a/gio/tests/gdbus-message.c b/gio/tests/gdbus-message.c index 647cd7a24b..84487b76ba 100644 --- a/gio/tests/gdbus-message.c +++ b/gio/tests/gdbus-message.c @@ -185,6 +185,22 @@ message_bytes_needed (void) 0, 0, 0, 0x08, /* body length (128MiB) */ 1, 0, 0, 0, /* message serial */ 7, 0, 0, 0 /* header length */}, -1 }, + { { 'B', 0, 0, 1, /* endianness, message type, flags, protocol version */ + 0, 0, 0, 0, /* body length (empty) */ + 1, 0, 0, 0, /* message serial */ + 0xff, 0xff, 0xff, 0xf0 /* header length (overflow) */}, -1 }, + { { 'l', 0, 0, 1, /* endianness, message type, flags, protocol version */ + 0, 0, 0, 0, /* body length (empty) */ + 1, 0, 0, 0, /* message serial */ + 0xf0, 0xff, 0xff, 0xff /* header length (overflow) */}, -1 }, + { { 'B', 0, 0, 1, /* endianness, message type, flags, protocol version */ + 0, 0, 0, 1, /* body length (short) */ + 1, 0, 0, 0, /* message serial */ + 0xff, 0xff, 0xff, 0xef /* header length (overflow once body is added) */}, -1 }, + { { 'l', 0, 0, 1, /* endianness, message type, flags, protocol version */ + 1, 0, 0, 0, /* body length (short) */ + 1, 0, 0, 0, /* message serial */ + 0xef, 0xff, 0xff, 0xff /* header length (overflow once body is added) */}, -1 }, }; gsize i; diff --git a/gio/tests/meson.build b/gio/tests/meson.build index 9bd0d30d07..b1e9df265c 100644 --- a/gio/tests/meson.build +++ b/gio/tests/meson.build @@ -474,6 +474,7 @@ if host_machine.system() != 'windows' }, 'fdo-notification-backend': {}, 'gdbus-auth' : {'extra_sources' : extra_sources}, + 'gdbus-auth-mechanism-sha1': {'extra_sources' : extra_sources}, 'gdbus-bz627724' : {'extra_sources' : extra_sources}, 'gdbus-close-pending' : {'extra_sources' : extra_sources}, 'gdbus-connection' : { diff --git a/glib/giochannel.c b/glib/giochannel.c index e54aea2568..640f698cbe 100644 --- a/glib/giochannel.c +++ b/glib/giochannel.c @@ -1828,7 +1828,8 @@ read_again: { if (channel->line_term) { - if (memcmp (channel->line_term, nextchar, line_term_len) == 0) + if ((size_t) (lastchar - nextchar) >= line_term_len && + memcmp (channel->line_term, nextchar, line_term_len) == 0) { line_length = nextchar - use_buf->str; got_term_len = line_term_len; diff --git a/glib/gkeyfile.c b/glib/gkeyfile.c index 84ddddc387..5ee2296f21 100644 --- a/glib/gkeyfile.c +++ b/glib/gkeyfile.c @@ -2462,7 +2462,7 @@ g_key_file_get_locale_string_list (GKeyFile *key_file, } len = strlen (value); - if (value[len - 1] == key_file->list_separator) + if (len > 0 && value[len - 1] == key_file->list_separator) value[len - 1] = '\0'; list_separator[0] = key_file->list_separator; diff --git a/glib/tests/io-channel.c b/glib/tests/io-channel.c index cab4869328..3ff5d38e3a 100644 --- a/glib/tests/io-channel.c +++ b/glib/tests/io-channel.c @@ -228,6 +228,65 @@ test_read_line_embedded_nuls (void) g_free (filename); } +static void +test_read_line_long_terminator (void) +{ + uint8_t *test_data = NULL; + size_t test_data_len = 0; + int fd; + char *filename = NULL; + GIOChannel *channel = NULL; + GError *local_error = NULL; + char *line = NULL; + size_t line_length, terminator_pos; + const char *line_term; + int line_term_length; + GIOStatus status; + + g_test_summary ("Test that reading a line when using a long terminator doesn’t over-read the buffer."); + g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/work_items/3925"); + + /* Write out a temporary file containing 2047 bytes. This is enough to make it + * near the length of the GString buffer when read back in. */ + fd = g_file_open_tmp ("glib-test-io-channel-XXXXXX", &filename, &local_error); + g_assert_no_error (local_error); + g_close (g_steal_fd (&fd), NULL); + + test_data_len = 2047; + test_data = g_malloc (test_data_len); + memset (test_data, 'M', test_data_len); + g_file_set_contents (filename, (const gchar *) test_data, test_data_len, &local_error); + g_assert_no_error (local_error); + + /* Create the channel. */ + channel = g_io_channel_new_file (filename, "r", &local_error); + g_assert_no_error (local_error); + + /* Use a long line terminator so it could potentially over-read the end of the buffer. */ + g_io_channel_set_line_term (channel, "DEADBEEF", 8); + + line_term = g_io_channel_get_line_term (channel, &line_term_length); + g_assert_cmpstr (line_term, ==, "DEADBEEF"); + g_assert_cmpint (line_term_length, ==, 8); + + g_io_channel_set_encoding (channel, "UTF-8", &local_error); + g_assert_no_error (local_error); + + status = g_io_channel_read_line (channel, &line, &line_length, + &terminator_pos, &local_error); + g_assert_no_error (local_error); + g_assert_cmpint (status, ==, G_IO_STATUS_NORMAL); + g_assert_cmpuint (line_length, ==, 2047); + g_assert_cmpuint (terminator_pos, ==, 2047); + g_assert_cmpmem (line, line_length, test_data, test_data_len); + + g_free (line); + g_io_channel_unref (channel); + g_free (test_data); + g_unlink (filename); + g_free (filename); +} + int main (int argc, char *argv[]) @@ -236,6 +295,7 @@ main (int argc, g_test_add_func ("/io-channel/read-write", test_read_write); g_test_add_func ("/io-channel/read-line/embedded-nuls", test_read_line_embedded_nuls); + g_test_add_func ("/io-channel/read-line/long-terminator", test_read_line_long_terminator); return g_test_run (); } diff --git a/glib/tests/keyfile.c b/glib/tests/keyfile.c index 64a9ec317b..da5cad8e0e 100644 --- a/glib/tests/keyfile.c +++ b/glib/tests/keyfile.c @@ -889,6 +889,28 @@ test_locale_string_multiple_loads (void) g_free (old_locale); } +static void +test_locale_string_empty (void) +{ + GKeyFile *keyfile = NULL; + GError *local_error = NULL; + const char *data = + "[valid]\n" + "key1=\n"; + + g_test_summary ("Check that loading an empty translatable string works"); + g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/3930"); + + keyfile = g_key_file_new (); + + g_key_file_load_from_data (keyfile, data, -1, G_KEY_FILE_NONE, &local_error); + g_assert_no_error (local_error); + + check_locale_string_list_value (keyfile, "valid", "key1", NULL, NULL); + + g_key_file_free (keyfile); +} + static void test_lists (void) { @@ -2011,6 +2033,7 @@ main (int argc, char *argv[]) g_test_add_func ("/keyfile/number", test_number); g_test_add_func ("/keyfile/locale-string", test_locale_string); g_test_add_func ("/keyfile/locale-string/multiple-loads", test_locale_string_multiple_loads); + g_test_add_func ("/keyfile/locale-string/empty", test_locale_string_empty); g_test_add_func ("/keyfile/lists", test_lists); g_test_add_func ("/keyfile/lists-set-get", test_lists_set_get); g_test_add_func ("/keyfile/group-remove", test_group_remove);