diff --git a/CHANGELOG.md b/CHANGELOG.md index f578edf3e..647cdaff5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ SPDX-License-Identifier: GPL-3.0-or-later ### Changed +- **breaking:** core: fix buffer overflow in function utf8_next_char and return NULL for empty string - **breaking:** core: fix integer overflow and return "unsigned long" in function util_version_number - core: write configuration files on disk only if there are changes ([#2250](https://github.com/weechat/weechat/issues/2250)) diff --git a/src/core/core-string.c b/src/core/core-string.c index ab51ffffc..2cf083efe 100644 --- a/src/core/core-string.c +++ b/src/core/core-string.c @@ -254,6 +254,8 @@ string_reverse (const char *string) while (ptr_string && ptr_string[0]) { char_size = utf8_char_size (ptr_string); + if (char_size < 1) + break; ptr_result -= char_size; memcpy (ptr_result, ptr_string, char_size); @@ -313,6 +315,8 @@ string_reverse_screen (const char *string) if (ptr_string[0]) { char_size = utf8_char_size (ptr_string); + if (char_size < 1) + break; ptr_result -= char_size; memcpy (ptr_result, ptr_string, char_size); @@ -934,7 +938,7 @@ string_strcasestr (const char *string, const char *search) if (!string || !search || (length_search == 0)) return NULL; - while (string[0]) + while (string && string[0]) { if (string_strncasecmp (string, search, length_search) == 0) return (char *)string; @@ -4139,6 +4143,8 @@ string_input_for_buffer (const char *string) return string; next_char = utf8_next_char (string); + if (!next_char) + return NULL; /* next char is a space, then it's not a command */ if (next_char[0] == ' ') @@ -4226,8 +4232,12 @@ string_levenshtein (const char *string1, const char *string2, last_diag + ((char1 == char2) ? 0 : 1)); last_diag = old_diag; ptr_str1 = utf8_next_char (ptr_str1); + if (!ptr_str1) + break; } ptr_str2 = utf8_next_char (ptr_str2); + if (!ptr_str2) + break; } return column[length1]; diff --git a/src/core/core-utf8.c b/src/core/core-utf8.c index 5c9b5ac5f..7faa4ed89 100644 --- a/src/core/core-utf8.c +++ b/src/core/core-utf8.c @@ -242,7 +242,7 @@ utf8_prev_char (const char *string_start, const char *string) const char * utf8_next_char (const char *string) { - if (!string) + if (!string || !string[0]) return NULL; /* UTF-8, 2 bytes: 110vvvvv 10vvvvvv */ @@ -311,7 +311,7 @@ utf8_end_of_line (const char *string) if (!string) return NULL; - while (string[0] && (string[0] != '\n')) + while (string && string[0] && (string[0] != '\n')) { string = utf8_next_char (string); } @@ -451,10 +451,16 @@ utf8_int_string (unsigned int unicode_value, char *string) int utf8_char_size (const char *string) { - if (!string) + const char *ptr_next; + + if (!string || !string[0]) return 0; - return utf8_next_char (string) - string; + ptr_next = utf8_next_char (string); + if (!ptr_next) + return 0; + + return ptr_next - string; } /* diff --git a/src/core/hook/hook-command-run.c b/src/core/hook/hook-command-run.c index 0b79fe02f..9af298c7c 100644 --- a/src/core/hook/hook-command-run.c +++ b/src/core/hook/hook-command-run.c @@ -115,7 +115,7 @@ hook_command_run_exec (struct t_gui_buffer *buffer, const char *command) if (command[0] != '/') { ptr_string = utf8_next_char (command); - if (string_asprintf (&command2, "/%s", ptr_string) < 0) + if (string_asprintf (&command2, "/%s", (ptr_string) ? ptr_string : "") < 0) return WEECHAT_RC_ERROR; } else diff --git a/src/gui/gui-completion.c b/src/gui/gui-completion.c index 7f1b95da5..fbebb8e63 100644 --- a/src/gui/gui-completion.c +++ b/src/gui/gui-completion.c @@ -323,9 +323,11 @@ gui_completion_nick_has_ignored_chars (const char *string) int char_size; char utf_char[16]; - while (string[0]) + while (string && string[0]) { char_size = utf8_char_size (string); + if (char_size < 1) + break; memcpy (utf_char, string, char_size); utf_char[char_size] = '\0'; @@ -352,9 +354,11 @@ gui_completion_nick_strdup_ignore_chars (const char *string) result = malloc (strlen (string) + 1); pos = result; - while (string[0]) + while (string && string[0]) { char_size = utf8_char_size (string); + if (char_size < 1) + break; memcpy (utf_char, string, char_size); utf_char[char_size] = '\0'; @@ -915,13 +919,16 @@ gui_completion_find_context (struct t_gui_completion *completion, if (string_is_command_char (ptr_data)) { ptr_data = utf8_next_char (ptr_data); - if (ptr_data < data + pos) + if (ptr_data) { - if (string_is_command_char (ptr_data)) - ptr_data = utf8_next_char (ptr_data); + if (ptr_data < data + pos) + { + if (string_is_command_char (ptr_data)) + ptr_data = utf8_next_char (ptr_data); + } + if (!string_is_command_char (ptr_data)) + ptr_command = ptr_data; } - if (!string_is_command_char (ptr_data)) - ptr_command = ptr_data; } /* diff --git a/src/gui/gui-input.c b/src/gui/gui-input.c index 810dea7b7..c88b0eeac 100644 --- a/src/gui/gui-input.c +++ b/src/gui/gui-input.c @@ -871,10 +871,14 @@ gui_input_delete_next_char (struct t_gui_buffer *buffer) return; } - gui_buffer_undo_snap (buffer); pos = (char *)utf8_add_offset (buffer->input_buffer, buffer->input_buffer_pos); pos_next = (char *)utf8_next_char (pos); + if (!pos_next) + return; + + gui_buffer_undo_snap (buffer); + char_size = pos_next - pos; size_to_move = strlen (pos_next); memmove (pos, pos_next, size_to_move); @@ -899,9 +903,13 @@ gui_input_delete_range (struct t_gui_buffer *buffer, char *start, char *end) { + const char *ptr_next; int size_deleted, length_deleted; - size_deleted = utf8_next_char (end) - start; + ptr_next = utf8_next_char (end); + if (!ptr_next) + return; + size_deleted = ptr_next - start; length_deleted = utf8_strnlen (start, size_deleted); gui_input_clipboard_copy (start, size_deleted); @@ -956,7 +964,8 @@ gui_input_delete_previous_word (struct t_gui_buffer *buffer) else string = buffer->input_buffer; - gui_input_delete_range (buffer, string, start); + if (string) + gui_input_delete_range (buffer, string, start); } /* @@ -994,7 +1003,8 @@ gui_input_delete_previous_word_whitespace (struct t_gui_buffer *buffer) else string = buffer->input_buffer; - gui_input_delete_range (buffer, string, start); + if (string) + gui_input_delete_range (buffer, string, start); } /* @@ -1016,13 +1026,13 @@ gui_input_delete_next_word (struct t_gui_buffer *buffer) string = start; length_deleted = 0; /* move to the right until we reach a word char */ - while (string[0] && !string_is_word_char_input (string)) + while (string && string[0] && !string_is_word_char_input (string)) { string = (char *)utf8_next_char (string); length_deleted++; } /* move to the right to skip the whole word */ - while (string[0] && string_is_word_char_input (string)) + while (string && string[0] && string_is_word_char_input (string)) { string = (char *)utf8_next_char (string); length_deleted++; @@ -1060,12 +1070,14 @@ gui_input_delete_beginning_of_line (struct t_gui_buffer *buffer) if (!buffer->input || (buffer->input_buffer_pos <= 0)) return; - gui_buffer_undo_snap (buffer); start = (char *)utf8_add_offset (buffer->input_buffer, buffer->input_buffer_pos); beginning_of_line = (char *)utf8_beginning_of_line (buffer->input_buffer, start); + if (!beginning_of_line) + return; + if (beginning_of_line == start) { beginning_of_line = (char *)utf8_prev_char (buffer->input_buffer, @@ -1074,6 +1086,11 @@ gui_input_delete_beginning_of_line (struct t_gui_buffer *buffer) beginning_of_line); } + if (!beginning_of_line) + return; + + gui_buffer_undo_snap (buffer); + size_deleted = start - beginning_of_line; length_deleted = utf8_strnlen (beginning_of_line, size_deleted); gui_input_clipboard_copy (beginning_of_line, size_deleted); @@ -1109,7 +1126,6 @@ gui_input_delete_end_of_line (struct t_gui_buffer *buffer) if (!buffer->input) return; - gui_buffer_undo_snap (buffer); start = (char *)utf8_add_offset (buffer->input_buffer, buffer->input_buffer_pos); @@ -1117,7 +1133,14 @@ gui_input_delete_end_of_line (struct t_gui_buffer *buffer) end_of_line = (char *)utf8_next_char (start); else end_of_line = start; + if (!end_of_line) + return; + end_of_line = (char *)utf8_end_of_line (end_of_line); + if (!end_of_line) + return; + + gui_buffer_undo_snap (buffer); size_deleted = end_of_line - start; length_deleted = utf8_strnlen (start, size_deleted); @@ -1215,7 +1238,6 @@ gui_input_delete_line (struct t_gui_buffer *buffer) if (!buffer->input) return; - gui_buffer_undo_snap (buffer); start = (char *)utf8_add_offset (buffer->input_buffer, buffer->input_buffer_pos); @@ -1223,6 +1245,11 @@ gui_input_delete_line (struct t_gui_buffer *buffer) start); end_of_line = (char *)utf8_end_of_line (start); + if (!beginning_of_line || !end_of_line) + return; + + gui_buffer_undo_snap (buffer); + size_deleted = end_of_line - beginning_of_line; length_deleted = utf8_strnlen (beginning_of_line, size_deleted); @@ -1321,12 +1348,16 @@ gui_input_move_beginning_of_line (struct t_gui_buffer *buffer) buffer->input_buffer_pos); original_pos = pos; pos = (char *)utf8_beginning_of_line (buffer->input_buffer, pos); + if (!pos) + return; if (pos == original_pos) { pos = (char *)utf8_prev_char (buffer->input_buffer, pos); pos = (char *)utf8_beginning_of_line (buffer->input_buffer, pos); } + if (!pos) + return; buffer->input_buffer_pos = utf8_pos (buffer->input_buffer, pos - buffer->input_buffer); @@ -1354,11 +1385,9 @@ gui_input_move_end_of_line (struct t_gui_buffer *buffer) pos = (char *)utf8_add_offset (buffer->input_buffer, buffer->input_buffer_pos); if (pos[0] && pos[0] == '\n') - { pos = (char *)utf8_next_char (pos); - } pos = (char *)utf8_end_of_line (pos); - if (pos[0]) + if (pos && pos[0]) { buffer->input_buffer_pos = utf8_pos (buffer->input_buffer, pos - buffer->input_buffer); @@ -1462,6 +1491,8 @@ gui_input_move_previous_word (struct t_gui_buffer *buffer) pos = (char *)utf8_next_char (pos); else pos = buffer->input_buffer; + if (!pos) + return; buffer->input_buffer_pos = utf8_pos (buffer->input_buffer, pos - buffer->input_buffer); } @@ -1489,17 +1520,17 @@ gui_input_move_next_word (struct t_gui_buffer *buffer) pos = (char *)utf8_add_offset (buffer->input_buffer, buffer->input_buffer_pos); - while (pos[0] && !string_is_word_char_input (pos)) + while (pos && pos[0] && !string_is_word_char_input (pos)) { pos = (char *)utf8_next_char (pos); } - if (pos[0]) + if (pos && pos[0]) { while (pos[0] && string_is_word_char_input (pos)) { pos = (char *)utf8_next_char (pos); } - if (pos[0]) + if (pos && pos[0]) { buffer->input_buffer_pos = utf8_pos (buffer->input_buffer, @@ -1531,6 +1562,9 @@ gui_input_move_previous_line (struct t_gui_buffer *buffer) buffer->input_buffer_pos); pos = (char *)utf8_beginning_of_line (buffer->input_buffer, pos); + if (!pos) + return; + if (pos != buffer->input_buffer) { beginning_of_line_pos = utf8_pos (buffer->input_buffer, @@ -1540,13 +1574,19 @@ gui_input_move_previous_line (struct t_gui_buffer *buffer) pos = (char *)utf8_prev_char (buffer->input_buffer, pos); pos = (char *)utf8_beginning_of_line (buffer->input_buffer, pos); + if (!pos) + return; + for (i = 0; - pos[0] && (pos[0] != '\n') && (i < length_from_beginning); + pos && pos[0] && (pos[0] != '\n') && (i < length_from_beginning); i++) { pos = (char *)utf8_next_char (pos); } + if (!pos) + return; + buffer->input_buffer_pos = utf8_pos (buffer->input_buffer, pos - buffer->input_buffer); @@ -1574,22 +1614,27 @@ gui_input_move_next_line (struct t_gui_buffer *buffer) buffer->input_buffer_pos); pos = (char *)utf8_beginning_of_line (buffer->input_buffer, pos); + if (!pos) + return; + beginning_of_line_pos = utf8_pos (buffer->input_buffer, pos - buffer->input_buffer); length_from_beginning = buffer->input_buffer_pos - beginning_of_line_pos; pos = (char *)utf8_end_of_line (pos); - if (pos[0]) + if (pos && pos[0]) { pos = (char *)utf8_next_char (pos); - for (i = 0; - pos[0] && (pos[0] != '\n') && (i < length_from_beginning); + pos && pos[0] && (pos[0] != '\n') && (i < length_from_beginning); i++) { pos = (char *)utf8_next_char (pos); } + if (!pos) + return; + buffer->input_buffer_pos = utf8_pos (buffer->input_buffer, pos - buffer->input_buffer); diff --git a/src/gui/gui-key.c b/src/gui/gui-key.c index 3a68f09d2..a590958f9 100644 --- a/src/gui/gui-key.c +++ b/src/gui/gui-key.c @@ -1328,7 +1328,7 @@ gui_key_chunk_seems_valid (const char *chunk) if (!found) chunk = utf8_next_char (chunk); - if (chunk[0]) + if (chunk && chunk[0]) return 0; return 1; diff --git a/src/plugins/alias/alias.c b/src/plugins/alias/alias.c index 6ded0d121..abcf5fc65 100644 --- a/src/plugins/alias/alias.c +++ b/src/plugins/alias/alias.c @@ -420,6 +420,7 @@ void alias_hook_command (struct t_alias *alias) { char *str_priority_name, *str_completion; + const char *ptr_command; if (alias->hook) { @@ -442,11 +443,10 @@ alias_hook_command (struct t_alias *alias) str_completion = NULL; if (!alias->completion) { - weechat_asprintf ( - &str_completion, - "%%%%%s", - (weechat_string_is_command_char (alias->command)) ? - weechat_utf8_next_char (alias->command) : alias->command); + ptr_command = (weechat_string_is_command_char (alias->command)) ? + weechat_utf8_next_char (alias->command) : alias->command; + weechat_asprintf (&str_completion, "%%%%%s", + (ptr_command) ? ptr_command : ""); } alias->hook = weechat_hook_command ( @@ -681,11 +681,20 @@ alias_new (const char *name, const char *command, const char *completion) if (!command || !command[0]) return NULL; - while (weechat_string_is_command_char (name)) + while (name && weechat_string_is_command_char (name)) { name = weechat_utf8_next_char (name); } + if (!name || !name[0]) + { + weechat_printf (NULL, + _("%s%s: invalid alias name: \"%s\""), + weechat_prefix ("error"), ALIAS_PLUGIN_NAME, + ""); + return NULL; + } + ptr_alias = alias_search (name); alias_free (ptr_alias); diff --git a/src/plugins/irc/irc-message.c b/src/plugins/irc/irc-message.c index 9f80dc451..bb1b9eb00 100644 --- a/src/plugins/irc/irc-message.c +++ b/src/plugins/irc/irc-message.c @@ -1079,12 +1079,12 @@ irc_message_split_string (struct t_irc_message_split_context *context, pos = arguments; pos_max = pos + max_length; pos_last_delim = NULL; - while (pos[0]) + while (pos && pos[0]) { if (pos[0] == delimiter) pos_last_delim = pos; pos_next = weechat_utf8_next_char (pos); - if (pos_next > pos_max) + if (!pos_next || (pos_next > pos_max)) break; pos = pos_next; } diff --git a/src/plugins/spell/spell.c b/src/plugins/spell/spell.c index d0595322f..99494b0c3 100644 --- a/src/plugins/spell/spell.c +++ b/src/plugins/spell/spell.c @@ -834,7 +834,7 @@ spell_modifier_cb (const void *pointer, void *data, } current_pos = 0; - while (ptr_string[0]) + while (ptr_string && ptr_string[0]) { ptr_string_orig = NULL; @@ -885,7 +885,7 @@ spell_modifier_cb (const void *pointer, void *data, word_end_pos_valid = word_end_pos; } ptr_end = (char *)weechat_utf8_next_char (ptr_end); - if (!ptr_end[0]) + if (!ptr_end || !ptr_end[0]) break; code_point = weechat_utf8_char_int (ptr_end); } @@ -906,7 +906,7 @@ spell_modifier_cb (const void *pointer, void *data, while (!iswspace (code_point)) { ptr_end = (char *)weechat_utf8_next_char (ptr_end); - if (!ptr_end[0]) + if (!ptr_end || !ptr_end[0]) break; code_point = weechat_utf8_char_int (ptr_end); } diff --git a/src/plugins/trigger/trigger.c b/src/plugins/trigger/trigger.c index 49a10f312..afdfd84fd 100644 --- a/src/plugins/trigger/trigger.c +++ b/src/plugins/trigger/trigger.c @@ -700,11 +700,11 @@ trigger_regex_split (const char *str_regex, /* search the delimiter (which can be more than one char) */ pos = weechat_utf8_next_char (ptr_regex); - while (pos[0] && (weechat_string_charcmp (ptr_regex, pos) == 0)) + while (pos && pos[0] && (weechat_string_charcmp (ptr_regex, pos) == 0)) { pos = weechat_utf8_next_char (pos); } - if (!pos[0]) + if (!pos || !pos[0]) goto format_error; delimiter = weechat_strndup (ptr_regex, pos - ptr_regex); if (!delimiter) diff --git a/tests/unit/core/test-core-utf8.cpp b/tests/unit/core/test-core-utf8.cpp index 4080cf82a..418ffacca 100644 --- a/tests/unit/core/test-core-utf8.cpp +++ b/tests/unit/core/test-core-utf8.cpp @@ -351,7 +351,7 @@ TEST(CoreUtf8, Move) STRCMP_EQUAL(NULL, utf8_prev_char (NULL, NULL)); STRCMP_EQUAL(NULL, utf8_next_char (NULL)); STRCMP_EQUAL(NULL, utf8_prev_char (empty_string, empty_string)); - STRCMP_EQUAL(empty_string + 1, utf8_next_char (empty_string)); + STRCMP_EQUAL(NULL, utf8_next_char (empty_string)); STRCMP_EQUAL(NULL, utf8_prev_char (noel_valid + 1, noel_valid)); ptr = utf8_next_char (noel_valid); STRCMP_EQUAL("oël", ptr); @@ -512,7 +512,7 @@ TEST(CoreUtf8, Size) { /* char size (in bytes) */ LONGS_EQUAL(0, utf8_char_size (NULL)); - LONGS_EQUAL(1, utf8_char_size ("")); + LONGS_EQUAL(0, utf8_char_size ("")); LONGS_EQUAL(1, utf8_char_size ("A")); LONGS_EQUAL(2, utf8_char_size ("ë")); LONGS_EQUAL(3, utf8_char_size ("€"));