Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package weechat for openSUSE:Factory checked in at 2026-06-01 18:08:28 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/weechat (Old) and /work/SRC/openSUSE:Factory/.weechat.new.1937 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "weechat" Mon Jun 1 18:08:28 2026 rev:93 rq:1356360 version:4.9.1 Changes: -------- --- /work/SRC/openSUSE:Factory/weechat/weechat.changes 2026-03-30 18:37:10.821810607 +0200 +++ /work/SRC/openSUSE:Factory/.weechat.new.1937/weechat.changes 2026-06-01 18:09:14.935351525 +0200 @@ -1,0 +2,12 @@ +Mon Jun 1 13:01:54 UTC 2026 - Hunter Wardlaw <[email protected]> + +- Update to 4.9.1: + * core: fix option weechat.look.color_real_white not applied when + color is "white" on 16+ colors terminals (#1742) + * irc: fix tag in message with list of names when joining a channel + * relay: limit size of decompressed websocket frame with + permessage-deflate to prevent memory exhaustion (GHSA-v2v4-45wm-5cr3) + * relay: fix timing attack on password authentication (GHSA-vhv8-g2r9-cwcc) + * api, relay: fix timing attack on TOTP validation (GHSA-vhv8-g2r9-cwcc) + +------------------------------------------------------------------- Old: ---- weechat-4.9.0.tar.xz weechat-4.9.0.tar.xz.asc New: ---- weechat-4.9.1.tar.xz weechat-4.9.1.tar.xz.asc ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ weechat.spec ++++++ --- /var/tmp/diff_new_pack.qIo2tk/_old 2026-06-01 18:09:15.855389680 +0200 +++ /var/tmp/diff_new_pack.qIo2tk/_new 2026-06-01 18:09:15.855389680 +0200 @@ -17,7 +17,7 @@ Name: weechat -Version: 4.9.0 +Version: 4.9.1 Release: 0 Summary: Multi-protocol extensible Chat Client License: GPL-3.0-or-later ++++++ weechat-4.9.0.tar.xz -> weechat-4.9.1.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weechat-4.9.0/.poexam/poexam.toml new/weechat-4.9.1/.poexam/poexam.toml --- old/weechat-4.9.0/.poexam/poexam.toml 2026-03-29 10:20:23.000000000 +0200 +++ new/weechat-4.9.1/.poexam/poexam.toml 2026-05-31 13:46:04.000000000 +0200 @@ -9,7 +9,14 @@ ignore = [ "brackets", "double-quotes", + "double-words", + "header", + "html-tags", + "paths", + "punc-space-str", "unchanged", + "unicode-ctrl", + "urls", ] path_words = "." langs = [ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weechat-4.9.0/CHANGELOG.md new/weechat-4.9.1/CHANGELOG.md --- old/weechat-4.9.0/CHANGELOG.md 2026-03-29 10:20:23.000000000 +0200 +++ new/weechat-4.9.1/CHANGELOG.md 2026-05-31 13:46:04.000000000 +0200 @@ -6,6 +6,16 @@ # WeeChat ChangeLog +## Version 4.9.1 (2026-05-31) + +### Fixed + +- core: fix option weechat.look.color_real_white not applied when color is "white" on 16+ colors terminals ([#1742](https://github.com/weechat/weechat/issues/1742)) +- irc: fix tag in message with list of names when joining a channel +- relay: limit size of decompressed websocket frame with permessage-deflate to prevent memory exhaustion ([GHSA-v2v4-45wm-5cr3](https://github.com/weechat/weechat/security/advisories/GHSA-v2v4-45wm-5cr3)) +- relay: fix timing attack on password authentication ([GHSA-vhv8-g2r9-cwcc](https://github.com/weechat/weechat/security/advisories/GHSA-vhv8-g2r9-cwcc)) +- api, relay: fix timing attack on TOTP validation ([GHSA-vhv8-g2r9-cwcc](https://github.com/weechat/weechat/security/advisories/GHSA-vhv8-g2r9-cwcc)) + ## Version 4.9.0 (2026-03-29) ### Changed diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weechat-4.9.0/CMakeLists.txt new/weechat-4.9.1/CMakeLists.txt --- old/weechat-4.9.0/CMakeLists.txt 2026-03-29 10:20:23.000000000 +0200 +++ new/weechat-4.9.1/CMakeLists.txt 2026-05-31 13:46:04.000000000 +0200 @@ -28,6 +28,7 @@ # CMake options set(CMAKE_VERBOSE_MAKEFILE OFF) set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake" "${CMAKE_MODULE_PATH}") +set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_SKIP_RPATH ON) # compiler options diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weechat-4.9.0/src/core/core-crypto.c new/weechat-4.9.1/src/core/core-crypto.c --- old/weechat-4.9.0/src/core/core-crypto.c 2026-03-29 10:20:23.000000000 +0200 +++ new/weechat-4.9.1/src/core/core-crypto.c 2026-05-31 13:46:04.000000000 +0200 @@ -46,6 +46,9 @@ #include <netinet/in.h> #include <inttypes.h> #define BE_INT64 htonll +#elif defined(__HAIKU__) +#include <ByteOrder.h> +#define BE_INT64 B_HOST_TO_BENDIAN_INT64 #else #define BE_INT64 htobe64 #endif @@ -657,15 +660,18 @@ otp_ok = 0; + /* + * Compare in constant time and never break early: a non-constant + * compare and an early exit on match would let an observer measure + * how many digits of the expected OTP they got right and which + * time-window offset matched. + */ for (i = moving_factor - window; i <= moving_factor + window; i++) { rc = weecrypto_totp_generate_internal (secret, length_secret, i, digits, str_otp); - if (rc && (strcmp (str_otp, otp) == 0)) - { + if (rc && (string_memcmp_constant_time (str_otp, otp, digits) == 0)) otp_ok = 1; - break; - } } free (secret); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weechat-4.9.0/src/core/core-string.c new/weechat-4.9.1/src/core/core-string.c --- old/weechat-4.9.0/src/core/core-string.c 2026-03-29 10:20:23.000000000 +0200 +++ new/weechat-4.9.1/src/core/core-string.c 2026-05-31 13:46:04.000000000 +0200 @@ -923,6 +923,43 @@ } /* + * Compare two memory areas of the same size in constant time. + * + * Use to compare secrets (e.g. password hashes, MACs) without leaking + * information through the comparison's running time. The loop always + * walks the full "size" bytes and uses only bitwise operations on the + * data, so the execution time depends on "size" alone, not on the + * position of the first differing byte. + * + * If either pointer is NULL, the areas are considered different (the + * NULL check itself is not constant time but does not look at any + * secret content). + * + * Return: + * 0: areas are equal + * 1: areas differ + */ + +int +string_memcmp_constant_time (const void *area1, const void *area2, size_t size) +{ + const unsigned char *p1, *p2; + unsigned char diff; + size_t i; + + if (!area1 || !area2) + return (area1 == area2) ? 0 : 1; + + p1 = (const unsigned char *)area1; + p2 = (const unsigned char *)area2; + diff = 0; + for (i = 0; i < size; i++) + diff |= p1[i] ^ p2[i]; + + return (diff == 0) ? 0 : 1; +} + +/* * Search for a string in another string (locale and case independent). * * Return pointer to string found, or NULL if not found. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weechat-4.9.0/src/core/core-string.h new/weechat-4.9.1/src/core/core-string.h --- old/weechat-4.9.0/src/core/core-string.h 2026-03-29 10:20:23.000000000 +0200 +++ new/weechat-4.9.1/src/core/core-string.h 2026-05-31 13:46:04.000000000 +0200 @@ -69,6 +69,8 @@ const char *string2, const char *chars_ignored, int case_sensitive); +extern int string_memcmp_constant_time (const void *area1, const void *area2, + size_t size); extern const char *string_strcasestr (const char *string, const char *search); extern int string_match (const char *string, const char *mask, int case_sensitive); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weechat-4.9.0/src/core/core-url.c new/weechat-4.9.1/src/core/core-url.c --- old/weechat-4.9.0/src/core/core-url.c 2026-03-29 10:20:23.000000000 +0200 +++ new/weechat-4.9.1/src/core/core-url.c 2026-05-31 13:46:04.000000000 +0200 @@ -125,7 +125,9 @@ URL_DEF_CONST(AUTH, NTLM), URL_DEF_CONST(AUTH, ANY), URL_DEF_CONST(AUTH, ANYSAFE), +#if LIBCURL_VERSION_NUM < 0x081500 /* < 8.21.0 */ URL_DEF_CONST(AUTH, DIGEST_IE), +#endif URL_DEF_CONST(AUTH, ONLY), #if LIBCURL_VERSION_NUM < 0x080800 /* < 8.8.0 */ URL_DEF_CONST(AUTH, NTLM_WB), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weechat-4.9.0/src/gui/curses/gui-curses-window.c new/weechat-4.9.1/src/gui/curses/gui-curses-window.c --- old/weechat-4.9.0/src/gui/curses/gui-curses-window.c 2026-03-29 10:20:23.000000000 +0200 +++ new/weechat-4.9.1/src/gui/curses/gui-curses-window.c 2026-05-31 13:46:04.000000000 +0200 @@ -372,7 +372,8 @@ * if not real white, we use default terminal foreground instead of * white if bold attribute is set */ - if ((fg == COLOR_WHITE) && (gui_color[num_color]->attributes & A_BOLD) + if (((fg == COLOR_WHITE + 8) + || ((fg == COLOR_WHITE) && (gui_color[num_color]->attributes & A_BOLD))) && !CONFIG_BOOLEAN(config_look_color_real_white)) { fg = -1; @@ -442,7 +443,8 @@ * if not real white, we use default terminal foreground instead of * white if bold attribute is set */ - if ((fg == COLOR_WHITE) && (attributes & A_BOLD) + if (((fg == COLOR_WHITE + 8) + || ((fg == COLOR_WHITE) && (attributes & A_BOLD))) && !CONFIG_BOOLEAN(config_look_color_real_white)) { fg = -1; @@ -535,7 +537,8 @@ * if not real white, we use default terminal foreground instead of * white if bold attribute is set */ - if ((fg == COLOR_WHITE) && (attributes & A_BOLD) + if (((fg == COLOR_WHITE + 8) + || ((fg == COLOR_WHITE) && (attributes & A_BOLD))) && !CONFIG_BOOLEAN(config_look_color_real_white)) { fg = -1; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weechat-4.9.0/src/plugins/irc/irc-protocol.c new/weechat-4.9.1/src/plugins/irc/irc-protocol.c --- old/weechat-4.9.0/src/plugins/irc/irc-protocol.c 2026-03-29 10:20:23.000000000 +0200 +++ new/weechat-4.9.1/src/plugins/irc/irc-protocol.c 2026-05-31 13:46:04.000000000 +0200 @@ -208,7 +208,8 @@ */ const char * -irc_protocol_tags (struct t_irc_protocol_ctxt *ctxt, const char *extra_tags) +irc_protocol_tags_cmd (struct t_irc_protocol_ctxt *ctxt, const char *command, + const char *extra_tags) { static char string[4096]; const char *ptr_tag_batch, *ptr_nick, *ptr_address; @@ -222,7 +223,7 @@ ptr_nick = NULL; ptr_address = NULL; - is_numeric = irc_protocol_is_numeric_command (ctxt->command); + is_numeric = irc_protocol_is_numeric_command (command); has_irc_tags = (ctxt->tags && weechat_hashtable_get_integer (ctxt->tags, "items_count") > 0); @@ -287,9 +288,9 @@ } } - if (ctxt->command && ctxt->command[0]) + if (command && command[0]) { - log_level = irc_protocol_log_level_for_command (ctxt->command); + log_level = irc_protocol_log_level_for_command (command); if (log_level > 0) { snprintf (str_log_level, sizeof (str_log_level), @@ -299,8 +300,8 @@ snprintf (string, sizeof (string), "%s%s%s%s%s%s%s%s%s%s%s%s%s%s", - (ctxt->command && ctxt->command[0]) ? "irc_" : "", - (ctxt->command && ctxt->command[0]) ? ctxt->command : "", + (command && command[0]) ? "irc_" : "", + (command && command[0]) ? command : "", (is_numeric) ? "," : "", (is_numeric) ? "irc_numeric" : "", (str_irc_tags && (*str_irc_tags)[0]) ? "," : "", @@ -323,6 +324,16 @@ } /* + * Build tags list with IRC command and optional tags and nick. + */ + +const char * +irc_protocol_tags (struct t_irc_protocol_ctxt *ctxt, const char *extra_tags) +{ + return irc_protocol_tags_cmd (ctxt, ctxt->command, extra_tags); +} + +/* * Build a string with nick and optional address. * * If server_message is 1, color nick according to option @@ -6659,7 +6670,7 @@ ctxt->server, NULL, ctxt->command, "names", ptr_channel->buffer), ctxt->date, ctxt->date_usec, - irc_protocol_tags (ctxt, NULL), + irc_protocol_tags_cmd (ctxt, "353", NULL), _("%sNicks %s%s%s%s: %s[%s%s]"), weechat_prefix ("network"), IRC_COLOR_CHAT_CHANNEL, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weechat-4.9.0/src/plugins/plugin.c new/weechat-4.9.1/src/plugins/plugin.c --- old/weechat-4.9.0/src/plugins/plugin.c 2026-03-29 10:20:23.000000000 +0200 +++ new/weechat-4.9.1/src/plugins/plugin.c 2026-05-31 13:46:04.000000000 +0200 @@ -624,6 +624,7 @@ new_plugin->strncasecmp = &string_strncasecmp; new_plugin->strncasecmp_range = &string_strncasecmp_range; new_plugin->strcmp_ignore_chars = &string_strcmp_ignore_chars; + new_plugin->string_memcmp_constant_time = &string_memcmp_constant_time; new_plugin->strcasestr = &string_strcasestr; new_plugin->strlen_screen = &gui_chat_strlen_screen; new_plugin->string_match = &string_match; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weechat-4.9.0/src/plugins/relay/irc/relay-irc.c new/weechat-4.9.1/src/plugins/relay/irc/relay-irc.c --- old/weechat-4.9.0/src/plugins/relay/irc/relay-irc.c 2026-03-29 10:20:23.000000000 +0200 +++ new/weechat-4.9.1/src/plugins/relay/irc/relay-irc.c 2026-05-31 13:46:04.000000000 +0200 @@ -33,6 +33,7 @@ #include "../../weechat-plugin.h" #include "../relay.h" #include "relay-irc.h" +#include "../relay-auth.h" #include "../relay-buffer.h" #include "../relay-client.h" #include "../relay-config.h" @@ -1701,7 +1702,7 @@ NULL, NULL, NULL); if (password) { - if (strcmp (password, pos_password) == 0) + if (relay_auth_password_equals (password, pos_password)) { RELAY_IRC_DATA(client, password_ok) = 1; weechat_hook_signal_send ("relay_client_auth_ok", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weechat-4.9.0/src/plugins/relay/relay-auth.c new/weechat-4.9.1/src/plugins/relay/relay-auth.c --- old/weechat-4.9.0/src/plugins/relay/relay-auth.c 2026-03-29 10:20:23.000000000 +0200 +++ new/weechat-4.9.1/src/plugins/relay/relay-auth.c 2026-05-31 13:46:04.000000000 +0200 @@ -103,6 +103,66 @@ } /* + * Compare two passwords for equality in constant time. + * + * HMAC both sides with a fresh random key, then compare the fixed-size + * MACs. This hides both the per-byte comparison and the password length + * from a timing-side-channel observer. + * + * Both messages are prefixed with a zero byte so empty passwords still + * produce a valid HMAC (the underlying crypto API rejects zero-length + * messages); the prefix is identical on both sides so equal inputs + * still yield equal MACs. + * + * Return: + * 1: passwords are equal + * 0: passwords are not equal (or an error occurred) + */ + +int +relay_auth_password_equals (const char *password1, const char *password2) +{ + unsigned char key[32]; + char hmac1[64], hmac2[64]; + char *buf1, *buf2; + int buf1_size, buf2_size, hmac1_size, hmac2_size, rc; + + if (!password1 || !password2) + return 0; + + rc = 0; + buf1_size = strlen (password1) + 1; + buf2_size = strlen (password2) + 1; + buf1 = malloc (buf1_size); + buf2 = malloc (buf2_size); + if (buf1 && buf2) + { + buf1[0] = 0; + memcpy (buf1 + 1, password1, buf1_size - 1); + buf2[0] = 0; + memcpy (buf2 + 1, password2, buf2_size - 1); + gcry_create_nonce (key, sizeof (key)); + if (weechat_crypto_hmac (key, sizeof (key), + buf1, buf1_size, + "sha256", + hmac1, &hmac1_size) + && weechat_crypto_hmac (key, sizeof (key), + buf2, buf2_size, + "sha256", + hmac2, &hmac2_size) + && (hmac1_size == hmac2_size) + && (weechat_string_memcmp_constant_time ( + hmac1, hmac2, hmac1_size) == 0)) + { + rc = 1; + } + } + free (buf1); + free (buf2); + return rc; +} + +/* * Check if password received as plain text is valid. * * Return: @@ -127,7 +187,7 @@ return -1; } - return (strcmp (password, relay_password) == 0) ? 0 : -2; + return relay_auth_password_equals (password, relay_password) ? 0 : -2; } /* @@ -347,8 +407,9 @@ const char *hash_sha, const char *relay_password) { - char *salt_password, hash[512 / 8], hash_hexa[((512 / 8) * 2) + 1]; - int rc, length, hash_size; + char *salt_password, *hash_sha_upper, hash[512 / 8]; + char hash_hexa[((512 / 8) * 2) + 1]; + int rc, length, hash_size, hash_hexa_len; rc = 0; @@ -366,10 +427,23 @@ hash_algo, hash, &hash_size)) { - weechat_string_base_encode ("16", hash, hash_size, - hash_hexa); - if (weechat_strcasecmp (hash_hexa, hash_sha) == 0) + hash_hexa_len = weechat_string_base_encode ("16", hash, hash_size, + hash_hexa); + /* + * Compare in constant time to defeat timing attacks: the + * client-supplied hash is normalized to uppercase to match + * the output of base16 encoding, then compared byte-for-byte + * with no early exit. + */ + hash_sha_upper = weechat_string_toupper (hash_sha); + if (hash_sha_upper + && ((int)strlen (hash_sha_upper) == hash_hexa_len) + && (weechat_string_memcmp_constant_time ( + hash_hexa, hash_sha_upper, hash_hexa_len) == 0)) + { rc = 1; + } + free (hash_sha_upper); } free (salt_password); } @@ -393,8 +467,8 @@ const char *hash_pbkdf2, const char *relay_password) { - char hash[512 / 8], hash_hexa[((512 / 8) * 2) + 1]; - int rc, hash_size; + char *hash_pbkdf2_upper, hash[512 / 8], hash_hexa[((512 / 8) * 2) + 1]; + int rc, hash_size, hash_hexa_len; rc = 0; @@ -407,9 +481,18 @@ iterations, hash, &hash_size)) { - weechat_string_base_encode ("16", hash, hash_size, hash_hexa); - if (weechat_strcasecmp (hash_hexa, hash_pbkdf2) == 0) + hash_hexa_len = weechat_string_base_encode ("16", hash, hash_size, + hash_hexa); + /* see relay_auth_check_hash_sha for rationale */ + hash_pbkdf2_upper = weechat_string_toupper (hash_pbkdf2); + if (hash_pbkdf2_upper + && ((int)strlen (hash_pbkdf2_upper) == hash_hexa_len) + && (weechat_string_memcmp_constant_time ( + hash_hexa, hash_pbkdf2_upper, hash_hexa_len) == 0)) + { rc = 1; + } + free (hash_pbkdf2_upper); } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weechat-4.9.0/src/plugins/relay/relay-auth.h new/weechat-4.9.1/src/plugins/relay/relay-auth.h --- old/weechat-4.9.0/src/plugins/relay/relay-auth.h 2026-03-29 10:20:23.000000000 +0200 +++ new/weechat-4.9.1/src/plugins/relay/relay-auth.h 2026-05-31 13:46:04.000000000 +0200 @@ -39,6 +39,8 @@ extern int relay_auth_password_hash_algo_search (const char *name); extern char *relay_auth_generate_nonce (int size); +extern int relay_auth_password_equals (const char *password1, + const char *password2); extern int relay_auth_check_password_plain (struct t_relay_client *client, const char *password, const char *relay_password); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weechat-4.9.0/src/plugins/relay/relay-websocket.c new/weechat-4.9.1/src/plugins/relay/relay-websocket.c --- old/weechat-4.9.0/src/plugins/relay/relay-websocket.c 2026-03-29 10:20:23.000000000 +0200 +++ new/weechat-4.9.1/src/plugins/relay/relay-websocket.c 2026-05-31 13:46:04.000000000 +0200 @@ -532,7 +532,7 @@ int rc; unsigned char append_bytes[4] = { 0x00, 0x00, 0xFF, 0xFF }; Bytef *data2, *dest, *dest2; - uLongf size2, dest_size; + uLongf size2, dest_size, new_size; if (!data || (size == 0) || !strm || !size_decompressed) return NULL; @@ -549,8 +549,13 @@ memcpy (data2, data, size); memcpy (data2 + size, append_bytes, sizeof (append_bytes)); - /* estimate the decompressed size, by default 10 * size */ - dest_size = 10 * size2; + /* + * estimate the decompressed size, by default 10 * size, capped to the + * maximum allowed size (also prevents an integer overflow on the + * multiplication) + */ + dest_size = (size2 > WEBSOCKET_INFLATE_MAX_SIZE / 10) ? + WEBSOCKET_INFLATE_MAX_SIZE : 10 * size2; dest = malloc (dest_size); if (!dest) goto error; @@ -579,8 +584,19 @@ && (strm->avail_in > 0))) { /* output buffer is not large enough */ - strm->avail_out += dest_size; - dest_size *= 2; + if (dest_size >= WEBSOCKET_INFLATE_MAX_SIZE) + { + /* + * decompressed data is too large: reject the frame + * (protection against a "deflate bomb") + */ + goto error; + } + /* double the buffer, capped to the maximum allowed size */ + new_size = (dest_size > WEBSOCKET_INFLATE_MAX_SIZE / 2) ? + WEBSOCKET_INFLATE_MAX_SIZE : dest_size * 2; + strm->avail_out += (uInt)(new_size - dest_size); + dest_size = new_size; dest2 = realloc (dest, dest_size); if (!dest2) goto error; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weechat-4.9.0/src/plugins/relay/relay-websocket.h new/weechat-4.9.1/src/plugins/relay/relay-websocket.h --- old/weechat-4.9.0/src/plugins/relay/relay-websocket.h 2026-03-29 10:20:23.000000000 +0200 +++ new/weechat-4.9.1/src/plugins/relay/relay-websocket.h 2026-05-31 13:46:04.000000000 +0200 @@ -41,6 +41,13 @@ #define WEBSOCKET_SUB_PROTOCOL_API_WEECHAT "api.weechat" +/* + * maximum size of a decompressed websocket frame (with "permessage-deflate"): + * used as an upper bound when inflating, to prevent a small compressed frame + * from decompressing to an unbounded amount of data ("deflate bomb") + */ +#define WEBSOCKET_INFLATE_MAX_SIZE (8 * 1024 * 1024) + struct t_relay_client; struct t_relay_http_request; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weechat-4.9.0/src/plugins/weechat-plugin.h new/weechat-4.9.1/src/plugins/weechat-plugin.h --- old/weechat-4.9.0/src/plugins/weechat-plugin.h 2026-03-29 10:20:23.000000000 +0200 +++ new/weechat-4.9.1/src/plugins/weechat-plugin.h 2026-05-31 13:46:04.000000000 +0200 @@ -76,7 +76,7 @@ * please change the date with current one; for a second change at same * date, increment the 01, otherwise please keep 01. */ -#define WEECHAT_PLUGIN_API_VERSION "20251112-01" +#define WEECHAT_PLUGIN_API_VERSION "20260530-01" /* macros for defining plugin infos */ #define WEECHAT_PLUGIN_NAME(__name) \ @@ -348,6 +348,8 @@ int max, int range); int (*strcmp_ignore_chars) (const char *string1, const char *string2, const char *chars_ignored, int case_sensitive); + int (*string_memcmp_constant_time) (const void *area1, const void *area2, + size_t size); const char *(*strcasestr) (const char *string, const char *search); int (*strlen_screen) (const char *string); int (*string_match) (const char *string, const char *mask, @@ -1348,6 +1350,9 @@ (weechat_plugin->strcmp_ignore_chars)(__string1, __string2, \ __chars_ignored, \ __case_sensitive) +#define weechat_string_memcmp_constant_time(__area1, __area2, __size) \ + (weechat_plugin->string_memcmp_constant_time)(__area1, __area2, \ + __size) #define weechat_strcasestr(__string, __search) \ (weechat_plugin->strcasestr)(__string, __search) #define weechat_strlen_screen(__string) \ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weechat-4.9.0/tests/unit/core/test-core-string.cpp new/weechat-4.9.1/tests/unit/core/test-core-string.cpp --- old/weechat-4.9.0/tests/unit/core/test-core-string.cpp 2026-03-29 10:20:23.000000000 +0200 +++ new/weechat-4.9.1/tests/unit/core/test-core-string.cpp 2026-05-31 13:46:04.000000000 +0200 @@ -802,6 +802,38 @@ /* * Test functions: + * string_memcmp_constant_time + */ + +TEST(CoreString, MemcmpConstantTime) +{ + /* NULL handling */ + LONGS_EQUAL(0, string_memcmp_constant_time (NULL, NULL, 0)); + LONGS_EQUAL(0, string_memcmp_constant_time (NULL, NULL, 4)); + LONGS_EQUAL(1, string_memcmp_constant_time (NULL, "abcd", 4)); + LONGS_EQUAL(1, string_memcmp_constant_time ("abcd", NULL, 4)); + + /* zero-size compare always equal */ + LONGS_EQUAL(0, string_memcmp_constant_time ("", "", 0)); + LONGS_EQUAL(0, string_memcmp_constant_time ("abc", "xyz", 0)); + + /* equal areas */ + LONGS_EQUAL(0, string_memcmp_constant_time ("abcd", "abcd", 4)); + LONGS_EQUAL(0, string_memcmp_constant_time ("\x00\x01\x02\xff", + "\x00\x01\x02\xff", 4)); + + /* differing areas (first / middle / last byte) */ + LONGS_EQUAL(1, string_memcmp_constant_time ("Xbcd", "abcd", 4)); + LONGS_EQUAL(1, string_memcmp_constant_time ("aXcd", "abcd", 4)); + LONGS_EQUAL(1, string_memcmp_constant_time ("abcX", "abcd", 4)); + LONGS_EQUAL(1, string_memcmp_constant_time ("abcd", "abce", 4)); + + /* only compares "size" bytes, ignores trailing content */ + LONGS_EQUAL(0, string_memcmp_constant_time ("abcd", "abcz", 3)); +} + +/* + * Test functions: * string_strcasestr */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weechat-4.9.0/tests/unit/plugins/relay/test-relay-auth.cpp new/weechat-4.9.1/tests/unit/plugins/relay/test-relay-auth.cpp --- old/weechat-4.9.0/tests/unit/plugins/relay/test-relay-auth.cpp 2026-03-29 10:20:23.000000000 +0200 +++ new/weechat-4.9.1/tests/unit/plugins/relay/test-relay-auth.cpp 2026-05-31 13:46:04.000000000 +0200 @@ -111,6 +111,31 @@ /* * Test functions: + * relay_auth_password_equals + */ + +TEST(RelayAuth, PasswordEquals) +{ + /* invalid arguments */ + LONGS_EQUAL(0, relay_auth_password_equals (NULL, NULL)); + LONGS_EQUAL(0, relay_auth_password_equals ("abcd", NULL)); + LONGS_EQUAL(0, relay_auth_password_equals (NULL, "abcd")); + + /* different passwords */ + LONGS_EQUAL(0, relay_auth_password_equals ("test", "password")); + LONGS_EQUAL(0, relay_auth_password_equals ("Password", "password")); + LONGS_EQUAL(0, relay_auth_password_equals ("", "password")); + LONGS_EQUAL(0, relay_auth_password_equals ("password", "")); + + /* equal passwords */ + LONGS_EQUAL(1, relay_auth_password_equals ("", "")); + LONGS_EQUAL(1, relay_auth_password_equals ("password", "password")); + LONGS_EQUAL(1, relay_auth_password_equals ("a really long password with spaces", + "a really long password with spaces")); +} + +/* + * Test functions: * relay_auth_check_password_plain */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weechat-4.9.0/tests/unit/plugins/relay/test-relay-websocket.cpp new/weechat-4.9.1/tests/unit/plugins/relay/test-relay-websocket.cpp --- old/weechat-4.9.0/tests/unit/plugins/relay/test-relay-websocket.cpp 2026-03-29 10:20:23.000000000 +0200 +++ new/weechat-4.9.1/tests/unit/plugins/relay/test-relay-websocket.cpp 2026-05-31 13:46:04.000000000 +0200 @@ -466,6 +466,41 @@ free (payload_comp); relay_websocket_deflate_free (ws_deflate); + + /* + * protection against "deflate bomb": a small compressed frame that + * decompresses to more than WEBSOCKET_INFLATE_MAX_SIZE must be rejected + * (relay_websocket_inflate returns NULL) + */ + ws_deflate = relay_websocket_deflate_alloc (); + CHECK(ws_deflate); + ws_deflate->window_bits_deflate = 15; + ws_deflate->window_bits_inflate = 15; + ws_deflate->strm_deflate = (z_stream *)calloc (1, sizeof (*ws_deflate->strm_deflate)); + CHECK(ws_deflate->strm_deflate); + LONGS_EQUAL(1, relay_websocket_deflate_init_stream_deflate (ws_deflate)); + ws_deflate->strm_inflate = (z_stream *)calloc (1, sizeof (*ws_deflate->strm_inflate)); + CHECK(ws_deflate->strm_inflate); + LONGS_EQUAL(1, relay_websocket_deflate_init_stream_inflate (ws_deflate)); + + /* highly compressible payload that decompresses past the maximum size */ + size_t bomb_size = WEBSOCKET_INFLATE_MAX_SIZE + (1024 * 1024); + char *bomb = (char *)calloc (1, bomb_size); + CHECK(bomb); + + payload_comp = (char *)relay_websocket_deflate (bomb, bomb_size, + ws_deflate->strm_deflate, &size_comp); + CHECK(payload_comp); + CHECK(size_comp < bomb_size); + + payload_decomp = (char *)relay_websocket_inflate (payload_comp, size_comp, + ws_deflate->strm_inflate, &size_decomp); + POINTERS_EQUAL(NULL, payload_decomp); + + free (payload_comp); + free (bomb); + + relay_websocket_deflate_free (ws_deflate); } /* diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/weechat-4.9.0/version.sh new/weechat-4.9.1/version.sh --- old/weechat-4.9.0/version.sh 2026-03-29 10:20:23.000000000 +0200 +++ new/weechat-4.9.1/version.sh 2026-05-31 13:46:04.000000000 +0200 @@ -41,8 +41,8 @@ # devel-number the devel version as hex number ("0x04010000" for "4.1.0-dev") # -weechat_stable="4.9.0" -weechat_devel="4.9.0" +weechat_stable="4.9.1" +weechat_devel="4.9.1" stable_major=$(echo "${weechat_stable}" | cut -d"." -f1) stable_minor=$(echo "${weechat_stable}" | cut -d"." -f2)
