From 55c73ee21a9156614a83700b144d1af24b5d9378 Mon Sep 17 00:00:00 2001 From: "jabber.developer2" Date: Fri, 19 Jun 2026 10:43:14 +0300 Subject: [PATCH] =?UTF-8?q?security:=20validate=20=20'from'=20per?= =?UTF-8?q?=20XEP-0203=20=C2=A74?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reject client-forged timestamps in the incoming message handlers. The displayed timestamp was taken from an unfiltered delay scan, so a remote peer could place a message anywhere in history by injecting . - _handle_groupchat: oldest delay from room bare JID -> room domain -> our server domain, else now. - _handle_muc_private_message / _handle_chat: delay from our server domain -> sender domain, else now. Add stanza_get_oldest_delay_from(); stanza_get_oldest_delay() delegates with from=NULL. The MAM path stays unfiltered (trusted by construction). The original PR's XEP-0359 'by' edits are dropped: master already validates 'by'. --- src/xmpp/message.c | 35 ++++++++++++++++++++++++++--------- src/xmpp/stanza.c | 26 +++++++++++++++++++++----- src/xmpp/stanza.h | 3 ++- 3 files changed, 49 insertions(+), 15 deletions(-) diff --git a/src/xmpp/message.c b/src/xmpp/message.c index a7b2b058..6dddf116 100644 --- a/src/xmpp/message.c +++ b/src/xmpp/message.c @@ -1132,12 +1132,17 @@ _handle_groupchat(xmpp_stanza_t* const stanza) message->timestamp = NULL; } - // we want to display the oldest delay - message->timestamp = stanza_get_oldest_delay(stanza); - - // now this has nothing to do with MUC history - // it's just setting the time to the received time so upon displaying we can use this time - // for example in win_println_incoming_muc_msg() + // XEP-0203 §4: trust only from the room or our server, not a client-forged stamp + message->timestamp = stanza_get_oldest_delay_from(stanza, from_jid->barejid); + if (!message->timestamp && from_jid->domainpart) { + message->timestamp = stanza_get_oldest_delay_from(stanza, from_jid->domainpart); + } + if (!message->timestamp) { + const char* my_domain = connection_get_domain(); + if (my_domain) { + message->timestamp = stanza_get_oldest_delay_from(stanza, my_domain); + } + } if (!message->timestamp) { message->timestamp = g_date_time_new_now_local(); } @@ -1268,7 +1273,15 @@ _handle_muc_private_message(xmpp_stanza_t* const stanza) _receive_omemo(stanza, message); #endif - message->timestamp = stanza_get_delay(stanza); + // XEP-0203 §4: trust only from a server domain, not a client-forged stamp + const char* my_domain = connection_get_domain(); + message->timestamp = stanza_get_delay_from(stanza, my_domain); + if (!message->timestamp && message->from_jid->domainpart) { + message->timestamp = stanza_get_delay_from(stanza, message->from_jid->domainpart); + } + if (!message->timestamp) { + message->timestamp = g_date_time_new_now_local(); + } message->body = xmpp_message_get_body(stanza); if (!message->plain && !message->body) { @@ -1419,8 +1432,12 @@ _handle_chat(xmpp_stanza_t* const stanza, gboolean is_mam, gboolean is_carbon, c // timestamp provided outside like in a by MAM message->timestamp = timestamp; } else { - // timestamp in the message stanza or use time of receival (now) - message->timestamp = stanza_get_delay(stanza); + // XEP-0203 §4: trust only from a server domain, not a client-forged stamp + const char* my_domain = connection_get_domain(); + message->timestamp = stanza_get_delay_from(stanza, my_domain); + if (!message->timestamp && jid->domainpart) { + message->timestamp = stanza_get_delay_from(stanza, jid->domainpart); + } if (!message->timestamp) { message->timestamp = g_date_time_new_now_local(); } diff --git a/src/xmpp/stanza.c b/src/xmpp/stanza.c index 4fde3ce0..513322b8 100644 --- a/src/xmpp/stanza.c +++ b/src/xmpp/stanza.c @@ -1163,7 +1163,7 @@ _stanza_get_delay_timestamp_xep0091(xmpp_stanza_t* const x_stanza) } GDateTime* -stanza_get_delay_from(xmpp_stanza_t* const stanza, gchar* from) +stanza_get_delay_from(xmpp_stanza_t* const stanza, const char* const from) { xmpp_stanza_t* delay = NULL; @@ -1195,6 +1195,12 @@ stanza_get_delay_from(xmpp_stanza_t* const stanza, gchar* from) GDateTime* stanza_get_oldest_delay(xmpp_stanza_t* const stanza) +{ + return stanza_get_oldest_delay_from(stanza, NULL); +} + +GDateTime* +stanza_get_oldest_delay_from(xmpp_stanza_t* const stanza, const char* const from) { xmpp_stanza_t* child; const char* child_name; @@ -1205,27 +1211,37 @@ stanza_get_oldest_delay(xmpp_stanza_t* const stanza) child_name = xmpp_stanza_get_name(child); if (child_name && g_strcmp0(child_name, STANZA_NAME_DELAY) == 0) { + if (from) { + const char* child_from = xmpp_stanza_get_attribute(child, STANZA_ATTR_FROM); + if (!child_from || g_strcmp0(child_from, from) != 0) + continue; + } GDateTime* tmp = _stanza_get_delay_timestamp_xep0203(child); if (oldest == NULL) { oldest = tmp; - } else if (g_date_time_compare(oldest, tmp) == 1) { + } else if (tmp && g_date_time_compare(oldest, tmp) == 1) { g_date_time_unref(oldest); oldest = tmp; - } else { + } else if (tmp) { g_date_time_unref(tmp); } } if (child_name && g_strcmp0(child_name, STANZA_NAME_X) == 0) { + if (from) { + const char* child_from = xmpp_stanza_get_attribute(child, STANZA_ATTR_FROM); + if (!child_from || g_strcmp0(child_from, from) != 0) + continue; + } GDateTime* tmp = _stanza_get_delay_timestamp_xep0091(child); if (oldest == NULL) { oldest = tmp; - } else if (g_date_time_compare(oldest, tmp) == 1) { + } else if (tmp && g_date_time_compare(oldest, tmp) == 1) { g_date_time_unref(oldest); oldest = tmp; - } else { + } else if (tmp) { g_date_time_unref(tmp); } } diff --git a/src/xmpp/stanza.h b/src/xmpp/stanza.h index e50b5c78..f34828db 100644 --- a/src/xmpp/stanza.h +++ b/src/xmpp/stanza.h @@ -305,8 +305,9 @@ xmpp_stanza_t* stanza_create_mediated_invite(xmpp_ctx_t* ctx, const char* const gboolean stanza_contains_chat_state(xmpp_stanza_t* stanza); GDateTime* stanza_get_delay(xmpp_stanza_t* const stanza); -GDateTime* stanza_get_delay_from(xmpp_stanza_t* const stanza, gchar* from); +GDateTime* stanza_get_delay_from(xmpp_stanza_t* const stanza, const char* const from); GDateTime* stanza_get_oldest_delay(xmpp_stanza_t* const stanza); +GDateTime* stanza_get_oldest_delay_from(xmpp_stanza_t* const stanza, const char* const from); gboolean stanza_is_muc_presence(xmpp_stanza_t* const stanza); gboolean stanza_is_muc_self_presence(xmpp_stanza_t* const stanza,