1
0
mirror of https://github.com/weechat/weechat.git synced 2026-06-12 14:14:48 +02:00

Compare commits

...

29 Commits

Author SHA1 Message Date
Sébastien Helleu bd6455d07f Version 4.9.2 2026-06-07 09:25:09 +02:00
Sébastien Helleu 8d3180fa78 tests: increase buffer size for injection of fake IRC message 2026-06-07 08:48:47 +02:00
aizu-m 519ab4e7bb relay: fix out-of-bounds read in relay_http_print_log_request (#2324) 2026-06-06 11:20:05 +02:00
Sébastien Helleu 3c36fd7412 relay: limit size of partial message received while reading an HTTP request to prevent memory exhaustion
A relay client could send data with no end-of-line (an unterminated method
or header line) and dribble its payload, making WeeChat accumulate it in the
partial message buffer that grew without limit, until all memory was
exhausted. This path is reachable before authentication during websocket
initialization with the "weechat" and "irc" protocols.

The accumulated partial message is now bounded by
RELAY_HTTP_PARTIAL_MESSAGE_MAX_LENGTH: once the limit is reached, the extra
data is ignored.
2026-06-06 09:38:55 +02:00
Sébastien Helleu b62c97dbe3 core: add links to issues in ChangeLog (#2321, #2322) 2026-06-06 07:25:19 +02:00
aizu-m f91f92b48f xfer: fix out-of-bounds read in xfer_chat_recv_cb on empty line (#2323) 2026-06-06 07:21:55 +02:00
aizu-m 30529057c8 irc: fix out-of-bounds read in DCC command with quoted filename 2026-06-04 23:20:59 +02:00
Sébastien Helleu a69f356182 tests: add tests on function xfer_file_find_filename 2026-06-04 23:20:26 +02:00
aizu-m 1438255a87 xfer: replace directory separator in remote nick by underscore in download filename 2026-06-04 23:20:08 +02:00
Sébastien Helleu d15ce789a0 api: fix infinite loop in function string_replace when the search string is empty 2026-06-03 21:17:32 +02:00
Sébastien Helleu 377b6da43d relay: limit size of received websocket frame and HTTP body to prevent memory exhaustion
A relay client could announce a huge websocket frame (or HTTP body via
"Content-Length") and dribble its payload, making WeeChat accumulate it
in a buffer that grew without limit, until all memory was exhausted. The
websocket frame path is reachable before authentication with the
"weechat" and "irc" protocols.

The announced websocket frame length and HTTP "Content-Length" are now
bounded by WEBSOCKET_FRAME_MAX_LENGTH and RELAY_HTTP_BODY_MAX_LENGTH: an
oversized websocket frame closes the connection, and an oversized body is
rejected.
2026-06-01 22:09:27 +02:00
Sébastien Helleu 8b1b06a407 irc: limit size of data received from the server to prevent memory exhaustion
A malicious or compromised IRC server could send data with no end-of-line
(or a flood of "005" messages), making WeeChat accumulate it in a buffer
that grew without limit, until all memory was exhausted.

The unterminated received message and the accumulated "005" (ISUPPORT)
data are now bounded by IRC_SERVER_RECV_MSG_MAX_LENGTH and
IRC_SERVER_ISUPPORT_MAX_LENGTH: extra data is ignored once the limit is
reached.
2026-06-01 22:08:39 +02:00
Sébastien Helleu c09c1bf2fc ci: enable ruby 3.3 module on Rocky Linux 9 2026-05-31 15:19:37 +02:00
Sébastien Helleu c83afb9a06 ci: install dnf-plugins-core on Rocky Linux 9 for dnf config-manager 2026-05-31 15:18:42 +02:00
Sébastien Helleu ed9535a43f Version 4.9.2-dev 2026-05-31 13:50:17 +02:00
Sébastien Helleu 2148829ebe Version 4.9.1 2026-05-31 13:46:04 +02:00
Sébastien Helleu 1ca2a00255 core: fix timing attack on TOTP validation (GHSA-vhv8-g2r9-cwcc)
weecrypto_totp_validate compared the generated and client-supplied OTPs
with strcmp and broke out of the time-window loop on the first match.
Both choices leaked information via response timing: strcmp leaked the
expected OTP digit-by-digit (shrinking the brute-force search from
~10^digits to a handful of guesses within the 30-second window), and
the early break leaked which window offset matched.

Compare in constant time with string_memcmp_constant_time and always
iterate the full window, OR-ing the result into otp_ok without an
early exit.

This affects both relay protocols (which call totp_validate via the
public info hook) and any other caller of the info hook.
2026-05-31 09:14:24 +02:00
Sébastien Helleu c737373d17 relay/irc: fix timing attack on PASS command (GHSA-vhv8-g2r9-cwcc)
The IRC relay protocol's PASS handler compared the server password with
the client-supplied value using strcmp, leaking the password byte-by-byte
via response timing. This is the same class of bug fixed for the api and
weechat protocols, on a separate code path that did not go through
relay_auth_check_password_plain.

Extract the HMAC-then-constant-time-compare logic from
relay_auth_check_password_plain into relay_auth_password_equals, then
use it in both the plain-auth wrapper and the IRC PASS handler.
2026-05-31 09:12:09 +02:00
Sébastien Helleu 30230498b2 relay: fix timing attack on password authentication (GHSA-vhv8-g2r9-cwcc)
The relay authentication used non-constant-time comparisons (strcasecmp,
strcmp) to verify password hashes and plaintext passwords, allowing an
attacker to derive the expected hash byte-by-byte from response timing
and then authenticate without knowing the password.

- SHA/PBKDF2 hex hash comparisons: normalize the client-supplied hash to
  uppercase and compare in constant time over the fixed expected length.
- Plaintext password comparison: HMAC-SHA256 both passwords with a fresh
  per-call random key and compare the fixed-size MACs in constant time,
  hiding both per-byte timing and the password length.

Add string_memcmp_constant_time helper in core, exposed via the plugin
API. Bump WEECHAT_PLUGIN_API_VERSION accordingly.
2026-05-31 09:11:53 +02:00
Sébastien Helleu 35699ea802 relay: limit size of decompressed websocket frame to prevent memory exhaustion (GHSA-v2v4-45wm-5cr3)
An authenticated relay client using the permessage-deflate websocket
extension could send a small compressed frame that decompresses to an
unbounded amount of data, exhausting all memory and crashing WeeChat.

The output buffer in relay_websocket_inflate is now capped to
WEBSOCKET_INFLATE_MAX_SIZE: frames decompressing beyond this limit are
rejected and the connection is closed.
2026-05-31 09:07:23 +02:00
Sébastien Helleu 5e4c165dad ci: bump poexam to version 0.0.10 2026-05-31 08:32:59 +02:00
Sébastien Helleu 23fb6bfe88 core: fix option weechat.look.color_real_white not applied when color is "white" on 16+ colors terminals (issue #1742) 2026-05-23 13:41:40 +02:00
Sébastien Helleu ec03437f9e irc: fix tag in message with list of names when joining a channel
The message with list of nicks on the channel has now tag irc_353 instead of
irc_366.
2026-05-23 13:22:33 +02:00
Sébastien Helleu 564ad2d5cd core: set max curl version to 8.21.0 for symbol CURLAUTH_DIGEST_IE 2026-05-23 13:20:58 +02:00
Sébastien Helleu f935aa3f9f ci: bump Lua from 5.3 to 5.4 2026-05-23 13:20:44 +02:00
Sébastien Helleu 76a7d5d3bd debian: bump Lua from 5.3 to 5.4 2026-05-23 13:20:43 +02:00
Luc Schrijvers f0f77e1bd9 Build fix for Haiku 2026-05-23 13:19:08 +02:00
LuK1337 8c0a3b4d81 cmake: enable position independent code (PIE)
Fixes the following build error when compiling Fedora 45 RPM:

/usr/bin/ld.bfd: tests/unit/CMakeFiles/tests.dir/tests.cpp.o: relocation R_X86_64_32 against `.rodata' can not be used when making a PIE object; recompile with -fPIE
/usr/bin/ld.bfd: failed to set dynamic section sizes: bad value
collect2: error: ld returned 1 exit status

See: https://cmake.org/cmake/help/latest/prop_tgt/POSITION_INDEPENDENT_CODE.html
     https://cmake.org/cmake/help/latest/policy/CMP0083.html
2026-05-23 12:40:20 +02:00
Sébastien Helleu a4a06f255a Version 4.9.1-dev 2026-05-23 12:40:02 +02:00
34 changed files with 726 additions and 58 deletions
+5 -3
View File
@@ -36,7 +36,7 @@ env:
libcurl4-gnutls-dev
libgcrypt20-dev
libgnutls28-dev
liblua5.3-dev
liblua5.4-dev
libncurses-dev
libperl-dev
libphp-embed
@@ -131,7 +131,7 @@ jobs:
sudo apt-get update -qq
sudo apt-get --yes --no-install-recommends install ${{ env.CHECK_DEPS_UBUNTU }}
pipx install msgcheck ruff
cargo install --version 0.0.8 poexam
cargo install --version 0.0.10 poexam
- name: Check gettext files (msgcheck)
run: msgcheck po/*.po
@@ -280,8 +280,10 @@ jobs:
- name: Install dependencies
run: |
dnf install -y epel-release
dnf install -y epel-release dnf-plugins-core
dnf config-manager --set-enabled crb
# pin a working ruby stream (ruby:4.0 has broken module metadata on Rocky 9.8)
dnf module enable -y ruby:3.3
dnf install -y ${{ env.WEECHAT_DEPS_ROCKYLINUX }}
- name: Build and run tests
+7
View File
@@ -9,7 +9,14 @@ select = [
ignore = [
"brackets",
"double-quotes",
"double-words",
"header",
"html-tags",
"paths",
"punc-space-str",
"unchanged",
"unicode-ctrl",
"urls",
]
path_words = "."
langs = [
+23
View File
@@ -6,6 +6,29 @@ SPDX-License-Identifier: GPL-3.0-or-later
# WeeChat ChangeLog
## Version 4.9.2 (2026-06-07)
### Fixed
- api: fix infinite loop in function string_replace when the search string is empty
- irc: limit size of data received from the server to prevent memory exhaustion
- irc: fix out-of-bounds read on incoming DCC command with a quoted filename ending the message ([#2322](https://github.com/weechat/weechat/issues/2322))
- relay: limit size of received websocket frame and HTTP body to prevent memory exhaustion
- relay: limit size of partial message received while reading an HTTP request to prevent memory exhaustion
- relay: fix out-of-bounds read in dump of data ([#2324](https://github.com/weechat/weechat/issues/2324))
- xfer: replace directory separator in remote nick by underscore in download filename to prevent writing the file outside the download directory ([#2321](https://github.com/weechat/weechat/issues/2321))
- xfer: fix out-of-bounds read when receiving empty line in DCC chat ([#2323](https://github.com/weechat/weechat/issues/2323))
## 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
+1
View File
@@ -28,6 +28,7 @@ project(weechat C)
# 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
+1 -1
View File
@@ -12,7 +12,7 @@ Build-Depends:
libperl-dev,
python3-dev,
libaspell-dev,
liblua5.3-dev,
liblua5.4-dev,
tcl8.6-dev,
guile-3.0-dev,
php-dev, libphp-embed, libargon2-dev, libsodium-dev,
+1 -1
View File
@@ -12,7 +12,7 @@ Build-Depends:
libperl-dev,
python3-dev,
libaspell-dev,
liblua5.3-dev,
liblua5.4-dev,
tcl8.6-dev,
guile-3.0-dev,
php-dev, libphp-embed, libargon2-dev, libsodium-dev,
+10 -4
View File
@@ -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 @@ weecrypto_totp_validate (const char *secret_base32, time_t totp_time,
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);
+40
View File
@@ -922,6 +922,43 @@ string_strcmp_ignore_chars (const char *string1, const char *string2,
string_charcasecmp (string1, string2);
}
/*
* 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).
*
@@ -1928,6 +1965,9 @@ string_replace (const char *string, const char *search, const char *replace)
if (!string || !search || !replace)
return NULL;
if (!search[0])
return strdup (string);
length1 = strlen (search);
length2 = strlen (replace);
+2
View File
@@ -69,6 +69,8 @@ extern int string_strcmp_ignore_chars (const char *string1,
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);
+2
View File
@@ -125,7 +125,9 @@ struct t_url_constant url_auth[] =
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),
+6 -3
View File
@@ -372,7 +372,8 @@ gui_window_set_weechat_color (WINDOW *window, int num_color)
* 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 @@ gui_window_set_custom_color_fg (WINDOW *window, int fg)
* 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 @@ gui_window_set_custom_color_fg_bg (WINDOW *window, int fg, int bg,
* 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;
+3 -3
View File
@@ -857,7 +857,7 @@ irc_ctcp_recv_dcc (struct t_irc_protocol_ctxt *ctxt, const char *arguments)
* double-quote
*/
pos = strrchr (pos_file, '"');
if (!pos || (pos == pos_file))
if (!pos || (pos == pos_file) || !pos[1])
{
weechat_printf (
ctxt->server->buffer,
@@ -1032,7 +1032,7 @@ irc_ctcp_recv_dcc (struct t_irc_protocol_ctxt *ctxt, const char *arguments)
* double-quote
*/
pos = strrchr (pos_file, '"');
if (!pos || (pos == pos_file))
if (!pos || (pos == pos_file) || !pos[1])
{
weechat_printf (
ctxt->server->buffer,
@@ -1176,7 +1176,7 @@ irc_ctcp_recv_dcc (struct t_irc_protocol_ctxt *ctxt, const char *arguments)
* double-quote
*/
pos = strrchr (pos_file, '"');
if (!pos || (pos == pos_file))
if (!pos || (pos == pos_file) || !pos[1])
{
weechat_printf (
ctxt->server->buffer,
+36 -16
View File
@@ -208,7 +208,8 @@ irc_protocol_tags_add_cb (void *data,
*/
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 @@ irc_protocol_tags (struct t_irc_protocol_ctxt *ctxt, const char *extra_tags)
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 @@ irc_protocol_tags (struct t_irc_protocol_ctxt *ctxt, const char *extra_tags)
}
}
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 @@ irc_protocol_tags (struct t_irc_protocol_ctxt *ctxt, const char *extra_tags)
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]) ? "," : "",
@@ -322,6 +323,16 @@ irc_protocol_tags (struct t_irc_protocol_ctxt *ctxt, const char *extra_tags)
return (string[0] == ',') ? string + 1 : string;
}
/*
* 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.
*
@@ -4153,16 +4164,25 @@ IRC_PROTOCOL_CALLBACK(005)
if (ctxt->server->isupport)
{
length_isupport = strlen (ctxt->server->isupport);
isupport2 = realloc (ctxt->server->isupport,
length_isupport + /* existing */
1 + /* space */
length + /* new */
1);
if (isupport2)
/*
* limit the size of the accumulated ISUPPORT data: once the
* maximum is reached, ignore the extra data (protection against a
* server flooding "005" messages, which would consume all the
* memory)
*/
if (length_isupport + 1 + length < IRC_SERVER_ISUPPORT_MAX_LENGTH)
{
ctxt->server->isupport = isupport2;
strcat (ctxt->server->isupport, " ");
strcat (ctxt->server->isupport, str_info);
isupport2 = realloc (ctxt->server->isupport,
length_isupport + /* existing */
1 + /* space */
length + /* new */
1);
if (isupport2)
{
ctxt->server->isupport = isupport2;
strcat (ctxt->server->isupport, " ");
strcat (ctxt->server->isupport, str_info);
}
}
}
else
@@ -6659,7 +6679,7 @@ IRC_PROTOCOL_CALLBACK(366)
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,
+8
View File
@@ -3409,6 +3409,14 @@ irc_server_msgq_add_unterminated (struct t_irc_server *server,
if (server->unterminated_message)
{
/*
* limit the size of the unterminated message: once the maximum is
* reached, ignore the extra data (protection against a server sending
* a very long line without end-of-line, which would consume all the
* memory)
*/
if (strlen (server->unterminated_message) >= IRC_SERVER_RECV_MSG_MAX_LENGTH)
return;
unterminated_message2 =
realloc (server->unterminated_message,
(strlen (server->unterminated_message) +
+9
View File
@@ -144,6 +144,15 @@ enum t_irc_server_option
#define IRC_SERVER_MULTILINE_DEFAULT_MAX_BYTES 4096
#define IRC_SERVER_MULTILINE_DEFAULT_MAX_LINES 24
/*
* maximum length of an unterminated message (a received line without
* end-of-line) and of the accumulated "005" (ISUPPORT) data; these limits
* protect against a server sending a huge amount of data without end-of-line
* (or a flood of "005" messages), which would consume all the memory
*/
#define IRC_SERVER_RECV_MSG_MAX_LENGTH (64 * 1024)
#define IRC_SERVER_ISUPPORT_MAX_LENGTH (64 * 1024)
/* casemapping (string comparisons for nicks/channels) */
enum t_irc_server_casemapping
{
+1
View File
@@ -624,6 +624,7 @@ plugin_load (const char *filename, int init_plugin, int argc, char **argv)
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;
+2 -1
View File
@@ -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 @@ relay_irc_recv (struct t_relay_client *client, const char *data)
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",
+93 -10
View File
@@ -102,6 +102,66 @@ relay_auth_generate_nonce (int size)
return nonce_hexa;
}
/*
* 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.
*
@@ -127,7 +187,7 @@ relay_auth_check_password_plain (struct t_relay_client *client,
return -1;
}
return (strcmp (password, relay_password) == 0) ? 0 : -2;
return relay_auth_password_equals (password, relay_password) ? 0 : -2;
}
/*
@@ -347,8 +407,9 @@ relay_auth_check_hash_sha (const char *hash_algo,
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 @@ relay_auth_check_hash_sha (const char *hash_algo,
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 @@ relay_auth_check_hash_pbkdf2 (const char *hash_pbkdf2_algo,
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 @@ relay_auth_check_hash_pbkdf2 (const char *hash_pbkdf2_algo,
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);
}
}
+2
View File
@@ -39,6 +39,8 @@ extern char *relay_auth_password_hash_algo_name[];
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);
+22 -1
View File
@@ -513,6 +513,19 @@ relay_http_add_to_body (struct t_relay_http_request *request,
if (!partial_message || !*partial_message)
return;
/*
* reject the body if its announced length is too big: this prevents a
* client from forcing an unbounded allocation by announcing a huge
* "Content-Length"
*/
if (request->content_length > RELAY_HTTP_BODY_MAX_LENGTH)
{
free (*partial_message);
*partial_message = NULL;
request->status = RELAY_HTTP_END;
return;
}
num_bytes_missing = request->content_length
- request->body_size;
if (num_bytes_missing <= 0)
@@ -993,6 +1006,14 @@ relay_http_recv (struct t_relay_client *client, const char *data, int size)
if (client->partial_message)
{
/*
* limit the size of the partial message: once the maximum is reached,
* ignore the extra data (protection against a client sending a huge
* amount of data without any end-of-line and dribbling it, which would
* consume all the memory)
*/
if (strlen (client->partial_message) >= RELAY_HTTP_PARTIAL_MESSAGE_MAX_LENGTH)
return;
new_partial = realloc (client->partial_message,
strlen (client->partial_message) +
strlen (data) + 1);
@@ -1695,7 +1716,7 @@ relay_http_print_log_request (struct t_relay_http_request *request)
weechat_log_printf (" path_items. . . . . . . : %p", request->path_items);
if (request->path_items)
{
for (i = 0; request->path_items[0]; i++)
for (i = 0; request->path_items[i]; i++)
{
weechat_log_printf (" '%s'", request->path_items[i]);
}
+16
View File
@@ -57,6 +57,22 @@ enum t_relay_client_http_status
#define RELAY_HTTP_ERROR_METHOD_NOT_ALLOWED "Method Not Allowed"
#define RELAY_HTTP_ERROR_OUT_OF_MEMORY "Out of memory"
/*
* maximum length of an HTTP request body: used as an upper bound on the
* "Content-Length" accepted from a client, to prevent a client from forcing
* an unbounded allocation by announcing a huge body
*/
#define RELAY_HTTP_BODY_MAX_LENGTH (8 * 1024 * 1024)
/*
* maximum length of the partial message accumulated while reading an HTTP
* request: once this limit is reached, the extra data is ignored; this
* protects against a client sending a huge amount of data without any
* end-of-line (an unterminated method or header line), which would consume
* all the memory
*/
#define RELAY_HTTP_PARTIAL_MESSAGE_MAX_LENGTH (8 * 1024 * 1024)
struct t_relay_http_request
{
enum t_relay_client_http_status status; /* HTTP status */
+29 -5
View File
@@ -532,7 +532,7 @@ relay_websocket_inflate (const void *data, size_t size, z_stream *strm,
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 @@ relay_websocket_inflate (const void *data, size_t size, z_stream *strm,
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 @@ relay_websocket_inflate (const void *data, size_t size, z_stream *strm,
&& (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;
@@ -687,6 +703,14 @@ relay_websocket_decode_frame (const unsigned char *buffer,
index_buffer += length_frame_size;
}
/*
* reject the frame if its announced length is too big: this prevents
* a client from forcing an unbounded allocation (and unbounded
* accumulation of partial frames) by announcing a huge frame
*/
if (length_frame > WEBSOCKET_FRAME_MAX_LENGTH)
return 0;
if (masked_frame)
{
/* read mask (4 bytes) */
+15
View File
@@ -41,6 +41,21 @@
#define WEBSOCKET_SUB_PROTOCOL_API_WEECHAT "api.weechat"
/*
* maximum length of a websocket frame received from a client (or a remote
* WeeChat): used as an upper bound on the announced frame payload length, to
* prevent a client from forcing an unbounded allocation by announcing a huge
* frame and dribbling its payload
*/
#define WEBSOCKET_FRAME_MAX_LENGTH (8 * 1024 * 1024)
/*
* 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;
+6 -1
View File
@@ -76,7 +76,7 @@ struct t_weelist_item;
* 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 @@ struct t_weechat_plugin
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 @@ extern int weechat_plugin_end (struct t_weechat_plugin *plugin);
(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) \
+1 -1
View File
@@ -162,7 +162,7 @@ xfer_chat_recv_cb (const void *pointer, void *data, int fd)
{
ctcp_action = 0;
length = strlen (ptr_buf);
if (ptr_buf[length - 1] == '\r')
if ((length > 0) && (ptr_buf[length - 1] == '\r'))
{
ptr_buf[length - 1] = '\0';
length--;
+11 -3
View File
@@ -251,7 +251,7 @@ xfer_file_find_suffix (struct t_xfer *xfer)
void
xfer_file_find_filename (struct t_xfer *xfer)
{
char *dir_separator, *path;
char *dir_separator, *path, *nick;
struct t_hashtable *options;
if (!XFER_IS_FILE(xfer->type))
@@ -287,12 +287,20 @@ xfer_file_find_filename (struct t_xfer *xfer)
{
strcat (xfer->local_filename, dir_separator);
}
free (dir_separator);
if (weechat_config_boolean (xfer_config_file_use_nick_in_filename))
{
strcat (xfer->local_filename, xfer->remote_nick);
/*
* the remote nick comes from the server and can contain a directory
* separator: replace it so the nick cannot make the file be written
* outside the download directory
*/
nick = (dir_separator) ?
weechat_string_replace (xfer->remote_nick, dir_separator, "_") : NULL;
strcat (xfer->local_filename, (nick) ? nick : xfer->remote_nick);
free (nick);
strcat (xfer->local_filename, ".");
}
free (dir_separator);
strcat (xfer->local_filename, xfer->filename);
free (path);
+34
View File
@@ -800,6 +800,38 @@ TEST(CoreString, StringComparison)
LONGS_EQUAL(-2, string_strcmp_ignore_chars ("è", "ê", "", 1));
}
/*
* 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
@@ -1422,6 +1454,8 @@ TEST(CoreString, Replace)
WEE_TEST_STR(NULL, string_replace ("string", NULL, "replace"));
WEE_TEST_STR(NULL, string_replace (NULL, "search", "replace"));
WEE_TEST_STR("test abc def", string_replace("test abc def", "", "xxx"));
WEE_TEST_STR("test abc def", string_replace("test abc def", "xyz", "xxx"));
WEE_TEST_STR("test xxx def", string_replace("test abc def", "abc", "xxx"));
WEE_TEST_STR("xxx test xxx def xxx",
+39 -1
View File
@@ -305,7 +305,7 @@ TEST_GROUP(IrcProtocolWithServer)
void server_recv (const char *command)
{
char str_command[4096];
char str_command[8192];
record_start ();
arraylist_clear (sent_messages);
@@ -3866,6 +3866,44 @@ TEST(IrcProtocolWithServer, 005_full)
STRCMP_EQUAL(IRC_MSG_005 " " IRC_MSG_005, ptr_server->isupport);
}
/*
* Test functions:
* irc_protocol_cb_005 (accumulated ISUPPORT is bounded)
*/
TEST(IrcProtocolWithServer, 005_limit)
{
char str_msg[4096], str_value[3500];
size_t length1, length2;
int i;
SRV_INIT;
memset (str_value, 'X', sizeof (str_value) - 1);
str_value[sizeof (str_value) - 1] = '\0';
snprintf (str_msg, sizeof (str_msg),
":server 005 alice TEST=%s :are supported", str_value);
/* flood the server with "005" messages */
for (i = 0; i < 100; i++)
{
server_recv (str_msg);
}
CHECK(ptr_server->isupport);
length1 = strlen (ptr_server->isupport);
/* the accumulated ISUPPORT data must be bounded */
CHECK(length1 <= IRC_SERVER_ISUPPORT_MAX_LENGTH + sizeof (str_value));
/* receiving more "005" messages must not grow it any further */
for (i = 0; i < 100; i++)
{
server_recv (str_msg);
}
length2 = strlen (ptr_server->isupport);
LONGS_EQUAL(length1, length2);
}
/*
* Test functions:
* irc_protocol_cb_005 (infos from server, multiple messages)
@@ -65,6 +65,49 @@ TEST(IrcServer, Valid)
irc_server_free (server);
}
/*
* Test functions:
* irc_server_msgq_add_unterminated (via irc_server_msgq_add_buffer)
*
* Check that data received without any end-of-line does not grow the
* unterminated message buffer without limit.
*/
TEST(IrcServer, MsgqAddBufferLimit)
{
struct t_irc_server *server;
char chunk[4097];
int i;
size_t length1, length2;
server = irc_server_alloc ("server_msgq");
CHECK(server);
memset (chunk, 'a', sizeof (chunk) - 1);
chunk[sizeof (chunk) - 1] = '\0';
/* feed a lot of data with no end-of-line */
for (i = 0; i < 100; i++)
{
irc_server_msgq_add_buffer (server, chunk);
}
CHECK(server->unterminated_message);
length1 = strlen (server->unterminated_message);
/* the buffer must be bounded (not ~400 KB) */
CHECK(length1 <= IRC_SERVER_RECV_MSG_MAX_LENGTH + sizeof (chunk));
/* feeding more data must not grow the buffer any further */
for (i = 0; i < 100; i++)
{
irc_server_msgq_add_buffer (server, chunk);
}
length2 = strlen (server->unterminated_message);
LONGS_EQUAL(length1, length2);
irc_server_free (server);
}
/*
* Test functions:
* irc_server_search
@@ -109,6 +109,31 @@ TEST(RelayAuth, GenerateNonce)
free (nonce);
}
/*
* 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
@@ -41,6 +41,7 @@ extern "C"
#include "src/plugins/relay/relay-client.h"
#include "src/plugins/relay/relay-config.h"
#include "src/plugins/relay/relay-http.h"
#include "src/plugins/relay/relay-server.h"
#include "src/plugins/relay/relay-websocket.h"
#include "src/plugins/weechat-plugin.h"
@@ -161,6 +162,35 @@ TEST(RelayHttp, RequestAllocReinitFree)
relay_http_request_free (request);
}
/*
* Test functions:
* relay_http_add_to_body (body too large is rejected)
*/
TEST(RelayHttp, AddToBodyLimit)
{
struct t_relay_http_request *request;
char *partial;
request = relay_http_request_alloc ();
CHECK(request);
/* announce a body larger than the maximum allowed */
request->status = RELAY_HTTP_BODY;
request->content_length = RELAY_HTTP_BODY_MAX_LENGTH + 1;
partial = strdup ("some body data");
relay_http_add_to_body (request, &partial);
/* the body must be rejected: nothing allocated, request ended */
POINTERS_EQUAL(NULL, request->body);
LONGS_EQUAL(0, request->body_size);
POINTERS_EQUAL(NULL, partial);
LONGS_EQUAL(RELAY_HTTP_END, request->status);
relay_http_request_free (request);
}
/*
* Test functions:
* relay_http_url_decode
@@ -992,6 +1022,69 @@ TEST(RelayHttp, Recv)
/* TODO: write tests */
}
/*
* Test functions:
* relay_http_recv (partial message accumulated is bounded)
*
* Check that data received without any end-of-line does not grow the partial
* message buffer without limit.
*/
TEST(RelayHttp, RecvLimit)
{
struct t_relay_server *server;
struct t_relay_client *client;
char *chunk;
int chunk_size, i;
size_t length1, length2;
/* disable auto-open of relay buffer (it would pollute other tests) */
config_file_option_set (relay_config_look_auto_open_buffer, "off", 1);
server = relay_server_new ("weechat", RELAY_PROTOCOL_WEECHAT, NULL,
9000,
NULL, /* path */
1, /* ipv4 */
0, /* ipv6 */
0, /* tls */
0); /* unix_socket */
CHECK(server);
client = relay_client_new (-1, "test", server);
CHECK(client);
chunk_size = 1024 * 1024;
chunk = (char *)malloc (chunk_size + 1);
CHECK(chunk);
memset (chunk, 'a', chunk_size);
chunk[chunk_size] = '\0';
/* feed more than the maximum, with no end-of-line (16 MB) */
for (i = 0; i < 16; i++)
{
relay_http_recv (client, chunk, chunk_size);
}
CHECK(client->partial_message);
length1 = strlen (client->partial_message);
/* the partial message must be bounded (not ~16 MB) */
CHECK(length1 <= RELAY_HTTP_PARTIAL_MESSAGE_MAX_LENGTH + (size_t)chunk_size);
/* feeding more data must not grow it any further */
for (i = 0; i < 16; i++)
{
relay_http_recv (client, chunk, chunk_size);
}
length2 = strlen (client->partial_message);
LONGS_EQUAL(length1, length2);
free (chunk);
relay_client_free (client);
relay_server_free (server);
/* restore auto-open of relay buffer */
config_file_option_reset (relay_config_look_auto_open_buffer, 1);
}
/*
* Test functions:
* relay_http_compress
@@ -466,6 +466,41 @@ TEST(RelayWebsocket, Inflate)
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);
}
/*
@@ -475,7 +510,44 @@ TEST(RelayWebsocket, Inflate)
TEST(RelayWebsocket, DecodeFrame)
{
/* TODO: write tests */
struct t_relay_websocket_frame *frames;
char *partial_ws_frame;
int num_frames, partial_ws_frame_size;
/* small unmasked binary frame with payload "hello" */
unsigned char frame_ok[7] = { 0x82, 0x05, 'h', 'e', 'l', 'l', 'o' };
/* masked frame announcing a 1 GB payload (64-bit length field) */
unsigned char frame_too_big[10] = {
0x82, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
};
/* a valid small frame is decoded */
frames = NULL;
num_frames = 0;
partial_ws_frame = NULL;
partial_ws_frame_size = 0;
LONGS_EQUAL(1, relay_websocket_decode_frame (
frame_ok, sizeof (frame_ok), 0, NULL,
&frames, &num_frames, &partial_ws_frame,
&partial_ws_frame_size));
LONGS_EQUAL(1, num_frames);
CHECK(frames);
LONGS_EQUAL(5, frames[0].payload_size);
MEMCMP_EQUAL("hello", frames[0].payload, 5);
free (frames[0].payload);
free (frames);
free (partial_ws_frame);
/* a frame announcing an oversized payload is rejected (return 0) */
frames = NULL;
num_frames = 0;
partial_ws_frame = NULL;
partial_ws_frame_size = 0;
LONGS_EQUAL(0, relay_websocket_decode_frame (
frame_too_big, sizeof (frame_too_big), 1, NULL,
&frames, &num_frames, &partial_ws_frame,
&partial_ws_frame_size));
free (frames);
free (partial_ws_frame);
}
/*
+65 -1
View File
@@ -25,6 +25,11 @@
extern "C"
{
#include <stdlib.h>
#include <string.h>
#include "src/core/core-config-file.h"
#include "src/plugins/xfer/xfer.h"
#include "src/plugins/xfer/xfer-config.h"
#include "src/plugins/xfer/xfer-file.h"
}
@@ -32,6 +37,42 @@ TEST_GROUP(XferFile)
{
};
/*
* Build a "file recv" xfer with the given remote nick (and a fixed filename),
* call xfer_file_find_filename and return a copy of the basename of the local
* filename (the part after the last directory separator).
*
* Note: result must be freed after use.
*/
static char *
test_find_filename_basename (const char *remote_nick)
{
struct t_xfer xfer;
char *pos, *result;
memset (&xfer, 0, sizeof (xfer));
xfer.type = XFER_TYPE_FILE_RECV_ACTIVE;
xfer.remote_nick = strdup (remote_nick);
xfer.filename = strdup ("test.txt");
xfer_file_find_filename (&xfer);
result = NULL;
if (xfer.local_filename)
{
pos = strrchr (xfer.local_filename, DIR_SEPARATOR_CHAR);
result = strdup ((pos) ? pos + 1 : xfer.local_filename);
}
free (xfer.remote_nick);
free (xfer.filename);
free (xfer.local_filename);
free (xfer.temp_local_filename);
return result;
}
/*
* Test functions:
* xfer_file_search_crc32
@@ -91,7 +132,30 @@ TEST(XferFile, FindSuffix)
TEST(XferFile, FindFilename)
{
/* TODO: write tests */
char *basename;
config_file_option_set (xfer_config_file_download_path, "/tmp/weechat_test_xfer", 1);
/* remote nick without directory separator: used as-is */
basename = test_find_filename_basename ("alice");
STRCMP_EQUAL("alice.test.txt", basename);
free (basename);
/*
* remote nick with a directory separator: the separator is replaced by
* "_" so the nick cannot make the file be written outside the download
* directory
*/
basename = test_find_filename_basename ("../foo");
STRCMP_EQUAL(".._foo.test.txt", basename);
free (basename);
/* all directory separators in the nick are replaced */
basename = test_find_filename_basename ("a/b/c");
STRCMP_EQUAL("a_b_c.test.txt", basename);
free (basename);
config_file_option_unset (xfer_config_file_download_path);
}
/*
+2 -2
View File
@@ -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.2"
weechat_devel="4.9.2"
stable_major=$(echo "${weechat_stable}" | cut -d"." -f1)
stable_minor=$(echo "${weechat_stable}" | cut -d"." -f2)