CVE-2020-9760: Buffer Copy without Checking Size of Input ('Classic Buffer Overflow') (CWE-120)
value: An issue was discovered in WeeChat before 2.7.1 (0.3.4 to 2.7 are affected). When a new IRC message 005 is received with longer nick prefixes, a buffer overflow and possibly a crash can happen when a new mode is set for a nick.
CWE description: The product copies an input buffer to an output buffer without verifying that the size of the input buffer is less than the size of the output buffer, leading to a buffer overflow.
CVSS v3 base score: 9.8
Published: 2020-03-23T16:15Z
Repository: https://github.com/weechat/weechat
Language: C
Files changed: ChangeLog.adoc, src/plugins/irc/irc-nick.c, src/plugins/irc/irc-nick.h, src/plugins/irc/irc-server.c
Commit date: 2020-02-09 20:04:45 +0100
Commit message: irc: fix crash when a new message 005 is received with longer nick prefixes

Thanks to Stuart Nevans Locke for reporting the issue.

Diff: diff --git a/ChangeLog.adoc b/ChangeLog.adoc
index ef1aa87faf..862c4d429b 100644
--- a/ChangeLog.adoc
+++ b/ChangeLog.adoc
@@ -29,10 +29,11 @@ New features::
 Bug fixes::
 
   * core: fix memory leak in completion
   * core: flush stdout/stderr before forking in hook_process function (issue #1441)
   * core: fix evaluation of condition with nested "if" (issue #1434)
+  * irc: fix crash when a new message 005 is received with longer nick prefixes
   * irc: fix crash when receiving a malformed message 324 (channel mode)
   * irc: add nick changes in the hotlist (except self nick change)
   * irc: case-insensitive comparison on incoming CTCP command, force upper case on CTCP replies (issue #1439)
   * irc: fix memory leak when the channel topic is changed
   * logger: fix crash when logging is disabled on a buffer and the log file was deleted in the meanwhile, when option logger.file.info_lines is on (issue #1444)
diff --git a/src/plugins/irc/irc-nick.c b/src/plugins/irc/irc-nick.c
index 5dbe3a256a..06b9d79b7f 100644
--- a/src/plugins/irc/irc-nick.c
+++ b/src/plugins/irc/irc-nick.c
@@ -641,10 +641,57 @@ irc_nick_set_mode (struct t_irc_server *server, struct t_irc_channel *channel,
         weechat_bar_item_update ("irc_nick");
         weechat_bar_item_update ("irc_nick_host");
     }
 }
 
+/*
+ * Reallocates the "prefixes" string in all nicks of all channels on the server
+ * (after 005 has been received).
+ */
+
+void
+irc_nick_realloc_prefixes (struct t_irc_server *server,
+                           int old_length, int new_length)
+{
+    struct t_irc_channel *ptr_channel;
+    struct t_irc_nick *ptr_nick;
+    char *new_prefixes;
+
+    for (ptr_channel = server->channels; ptr_channel;
+         ptr_channel = ptr_channel->next_channel)
+    {
+        for (ptr_nick = ptr_channel->nicks; ptr_nick;
+             ptr_nick = ptr_nick->next_nick)
+        {
+            if (ptr_nick->prefixes)
+            {
+                new_prefixes = realloc (ptr_nick->prefixes, new_length + 1);
+                if (new_prefixes)
+                {
+                    ptr_nick->prefixes = new_prefixes;
+                    if (new_length > old_length)
+                    {
+                        memset (ptr_nick->prefixes + old_length,
+                                ' ',
+                                new_length - old_length);
+                    }
+                    ptr_nick->prefixes[new_length] = '\0';
+                }
+            }
+            else
+            {
+                ptr_nick->prefixes = malloc (new_length + 1);
+                if (ptr_nick->prefixes)
+                {
+                    memset (ptr_nick->prefixes, ' ', new_length);
+                    ptr_nick->prefixes[new_length] = '\0';
+                }
+            }
+        }
+    }
+}
+
 /*
  * Removes a nick from a channel.
  */
 
 void
diff --git a/src/plugins/irc/irc-nick.h b/src/plugins/irc/irc-nick.h
index caccb29321..a5f57ca416 100644
--- a/src/plugins/irc/irc-nick.h
+++ b/src/plugins/irc/irc-nick.h
@@ -72,10 +72,12 @@ extern void irc_nick_change (struct t_irc_server *server,
                              struct t_irc_channel *channel,
                              struct t_irc_nick *nick, const char *new_nick);
 extern void irc_nick_set_mode (struct t_irc_server *server,
                                struct t_irc_channel *channel,
                                struct t_irc_nick *nick, int set, char mode);
+extern void irc_nick_realloc_prefixes (struct t_irc_server *server,
+                                       int old_length, int new_length);
 extern void irc_nick_free (struct t_irc_server *server,
                            struct t_irc_channel *channel,
                            struct t_irc_nick *nick);
 extern void irc_nick_free_all (struct t_irc_server *server,
                                struct t_irc_channel *channel);
diff --git a/src/plugins/irc/irc-server.c b/src/plugins/irc/irc-server.c
index eeacc06b7a..d66b50657b 100644
--- a/src/plugins/irc/irc-server.c
+++ b/src/plugins/irc/irc-server.c
@@ -986,15 +986,18 @@ irc_server_get_isupport_value (struct t_irc_server *server, const char *feature)
 void
 irc_server_set_prefix_modes_chars (struct t_irc_server *server,
                                    const char *prefix)
 {
     char *pos;
-    int i, length_modes, length_chars;
+    int i, old_length_chars, length_modes, length_chars;
 
     if (!server || !prefix)
         return;
 
+    old_length_chars = (server->prefix_chars) ?
+        strlen (server->prefix_chars) : 0;
+
     /* free previous values */
     if (server->prefix_modes)
     {
         free (server->prefix_modes);
         server->prefix_modes = NULL;
@@ -1030,10 +1033,14 @@ irc_server_set_prefix_modes_chars (struct t_irc_server *server,
                 free (server->prefix_modes);
                 server->prefix_modes = NULL;
             }
         }
     }
+
+    length_chars = (server->prefix_chars) ? strlen (server->prefix_chars) : 0;
+    if (server->prefix_chars && (length_chars != old_length_chars))
+        irc_nick_realloc_prefixes (server, old_length_chars, length_chars);
 }
 
 /*
  * Sets lag in server buffer (local variable), update bar item "lag"
  * and send signal "irc_server_lag_changed" for the server.

Vulnerable code:     int i, length_modes, length_chars;
Fixed code:   * irc: fix crash when a new message 005 is received with longer nick prefixes
/*
 * Reallocates the "prefixes" string in all nicks of all channels on the server
 * (after 005 has been received).
 */

void
irc_nick_realloc_prefixes (struct t_irc_server *server,
                           int old_length, int new_length)
{
    struct t_irc_channel *ptr_channel;
    struct t_irc_nick *ptr_nick;
    char *new_prefixes;

    for (ptr_channel = server->channels; ptr_channel;
         ptr_channel = ptr_channel->next_channel)
    {
        for (ptr_nick = ptr_channel->nicks; ptr_nick;
             ptr_nick = ptr_nick->next_nick)
        {
            if (ptr_nick->prefixes)
            {
                new_prefixes = realloc (ptr_nick->prefixes, new_length + 1);
                if (new_prefixes)
                {
                    ptr_nick->prefixes = new_prefixes;
                    if (new_length > old_length)
                    {
                        memset (ptr_nick->prefixes + old_length,
                                ' ',
                                new_length - old_length);
                    }
                    ptr_nick->prefixes[new_length] = '\0';
                }
            }
            else
            {
                ptr_nick->prefixes = malloc (new_length + 1);
                if (ptr_nick->prefixes)
                {
                    memset (ptr_nick->prefixes, ' ', new_length);
                    ptr_nick->prefixes[new_length] = '\0';
                }
            }
        }
    }
}

extern void irc_nick_realloc_prefixes (struct t_irc_server *server,
                                       int old_length, int new_length);
    int i, old_length_chars, length_modes, length_chars;
    old_length_chars = (server->prefix_chars) ?
        strlen (server->prefix_chars) : 0;


    length_chars = (server->prefix_chars) ? strlen (server->prefix_chars) : 0;
    if (server->prefix_chars && (length_chars != old_length_chars))
        irc_nick_realloc_prefixes (server, old_length_chars, length_chars);