From 7c6b11df2d622f4ef83a16ea875e18334c38302e Mon Sep 17 00:00:00 2001 From: Michael Catanzaro Date: Tue, 10 Sep 2024 16:50:38 -0500 Subject: [PATCH] giochannel: ensure line terminator remains nul-terminated if needed If the user passes -1 length to g_io_channel_set_line_term() along with a nul-terminated string, then calls g_io_channel_get_line_term() and decides to treat the result as nul-terminated rather than checking the length parameter, then the application will have a problem, because it's not nul-terminated. That's weird, since the input string was. Let's ensure the result is consistent: if you pass a nul-terminated string, the result is nul-terminated. If not, it's not. Also add a warning to g_io_channel_get_line_term(), since it's very strange for a gchar * return value to be anything other than a nul-terminated UTF-8 string. This is an API design bug, but we cannot fix it. --- glib/giochannel.c | 28 +++++++++++++++++----------- glib/tests/io-channel.c | 7 +++++++ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/glib/giochannel.c b/glib/giochannel.c index b44fff35b..e54aea256 100644 --- a/glib/giochannel.c +++ b/glib/giochannel.c @@ -877,26 +877,31 @@ g_io_channel_set_line_term (GIOChannel *channel, const gchar *line_term, gint length) { - guint length_unsigned; - g_return_if_fail (channel != NULL); g_return_if_fail (line_term == NULL || length != 0); /* Disallow "" */ + g_free (channel->line_term); + if (line_term == NULL) - length_unsigned = 0; + { + channel->line_term = NULL; + channel->line_term_len = 0; + } else if (length >= 0) - length_unsigned = (guint) length; + { + /* We store the value nul-terminated even if the input is not */ + channel->line_term = g_malloc0 (length + 1); + memcpy (channel->line_term, line_term, length); + channel->line_term_len = (guint) length; + } else { - /* FIXME: We’re constrained by line_term_len being a guint here */ + /* We’re constrained by line_term_len being a guint here */ gsize length_size = strlen (line_term); g_return_if_fail (length_size <= G_MAXUINT); - length_unsigned = (guint) length_size; + channel->line_term = g_strdup (line_term); + channel->line_term_len = (guint) length_size; } - - g_free (channel->line_term); - channel->line_term = line_term ? g_memdup2 (line_term, length_unsigned) : NULL; - channel->line_term_len = length_unsigned; } /** @@ -906,7 +911,8 @@ g_io_channel_set_line_term (GIOChannel *channel, * * This returns the string that #GIOChannel uses to determine * where in the file a line break occurs. A value of %NULL - * indicates autodetection. + * indicates autodetection. Since 2.84, the return value is always + * nul-terminated. * * Returns: The line termination string. This value * is owned by GLib and must not be freed. diff --git a/glib/tests/io-channel.c b/glib/tests/io-channel.c index c5dd01d04..99879be1b 100644 --- a/glib/tests/io-channel.c +++ b/glib/tests/io-channel.c @@ -178,6 +178,8 @@ test_read_line_embedded_nuls (void) GError *local_error = NULL; gchar *line = NULL; gsize line_length, terminator_pos; + const gchar *line_term; + gint line_term_length; GIOStatus status; g_test_summary ("Test that reading a line containing embedded nuls works " @@ -200,6 +202,11 @@ test_read_line_embedded_nuls (void) * Use length -1 here to exercise glib#2323; the case where length > 0 * is covered in glib/tests/protocol.c. */ g_io_channel_set_line_term (channel, "\n", -1); + + line_term = g_io_channel_get_line_term (channel, &line_term_length); + g_assert_cmpstr (line_term, ==, "\n"); + g_assert_cmpint (line_term_length, ==, 1); + g_io_channel_set_encoding (channel, NULL, &local_error); g_assert_no_error (local_error);