From 89f27b020f0e61a66c9308a0e67846e155c110ba Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Tue, 28 Apr 2026 16:45:14 +0100 Subject: [PATCH 1/2] giochannel: Fix memcmp() off the end of the buffer with long terminators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the line terminator is longer than a single byte, and the current line extends to the end of the buffer, and the buffer (which is a `GString`) is near a power of two in length (as that’s how `GString`s are allocated) it’s possible for the `memcmp()` which checks the terminator to read off the end of the string buffer. Fix that by checking the terminator length against the last character before calling `memcmp()`. Add a unit test. Spotted by linhlhq as #YWH-PGM9867-199. The fix is theirs (validated by me), and the unit test is adapted from their proof of concept. Signed-off-by: Philip Withnall Fixes: #3925 --- glib/giochannel.c | 3 ++- glib/tests/io-channel.c | 60 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/glib/giochannel.c b/glib/giochannel.c index e93c4b458..66b68e645 100644 --- a/glib/giochannel.c +++ b/glib/giochannel.c @@ -1830,7 +1830,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/tests/io-channel.c b/glib/tests/io-channel.c index 4a1b10876..c619fb00f 100644 --- a/glib/tests/io-channel.c +++ b/glib/tests/io-channel.c @@ -69,6 +69,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[]) @@ -76,6 +135,7 @@ main (int argc, g_test_init (&argc, &argv, NULL); 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 (); } From 94b3e1b6d1380c8f78e1e869b5cb46b2fbfe6689 Mon Sep 17 00:00:00 2001 From: RHEL Packaging Agent Date: Wed, 19 Aug 2026 15:07:16 +0000 Subject: [PATCH 2/2] Adapt test for GLib 2.68: replace uint8_t with guint8 and g_steal_fd with inline equivalent --- glib/tests/io-channel.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/glib/tests/io-channel.c b/glib/tests/io-channel.c index c619fb00f..afcea93f0 100644 --- a/glib/tests/io-channel.c +++ b/glib/tests/io-channel.c @@ -72,16 +72,16 @@ test_read_line_embedded_nuls (void) static void test_read_line_long_terminator (void) { - uint8_t *test_data = NULL; - size_t test_data_len = 0; - int fd; - char *filename = NULL; + guint8 *test_data = NULL; + gsize test_data_len = 0; + gint fd; + gchar *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; + 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 when using a long terminator doesn’t over-read the buffer."); @@ -91,7 +91,8 @@ test_read_line_long_terminator (void) * 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); + g_close (fd, NULL); + fd = -1; test_data_len = 2047; test_data = g_malloc (test_data_len);