Fix missing validations of federation member event stubs.

Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
Jason Volk
2025-12-30 07:18:04 +00:00
parent b412aafaf8
commit 5a92a00939
3 changed files with 111 additions and 42 deletions

View File

@@ -168,6 +168,7 @@ pub async fn join_remote(
let room_version_rules = room_version::rules(&room_version_id)?;
let (mut join_event, event_id, join_authorized_via_users_server) = self
.create_join_event(
room_id,
sender_user,
&make_join_response.event,
&room_version_id,
@@ -636,6 +637,7 @@ pub async fn join_local(
let room_version_rules = room_version::rules(&room_version_id)?;
let (join_event, event_id, _) = self
.create_join_event(
room_id,
sender_user,
&make_join_response.event,
&room_version_id,
@@ -689,6 +691,7 @@ pub async fn join_local(
#[tracing::instrument(name = "make_join", level = "debug", skip_all)]
async fn create_join_event(
&self,
room_id: &RoomId,
sender_user: &UserId,
join_event_stub: &RawJsonValue,
room_version_id: &RoomVersionId,
@@ -711,22 +714,6 @@ async fn create_join_event(
})
.and_then(|s| OwnedUserId::try_from(s.as_str().unwrap_or_default()).ok());
event.insert(
"origin".into(),
CanonicalJsonValue::String(
self.services
.globals
.server_name()
.as_str()
.to_owned(),
),
);
event.insert(
"origin_server_ts".into(),
CanonicalJsonValue::Integer(utils::millis_since_unix_epoch().try_into()?),
);
let displayname = self.services.users.displayname(sender_user).ok();
let avatar_url = self.services.users.avatar_url(sender_user).ok();
@@ -747,6 +734,30 @@ async fn create_join_event(
})?,
);
event.insert(
"origin".into(),
CanonicalJsonValue::String(
self.services
.globals
.server_name()
.as_str()
.to_owned(),
),
);
event.insert(
"origin_server_ts".into(),
CanonicalJsonValue::Integer(utils::millis_since_unix_epoch().try_into()?),
);
event.insert("room_id".into(), CanonicalJsonValue::String(room_id.as_str().into()));
event.insert("sender".into(), CanonicalJsonValue::String(sender_user.as_str().into()));
event.insert("state_key".into(), CanonicalJsonValue::String(sender_user.as_str().into()));
event.insert("type".into(), CanonicalJsonValue::String("m.room.member".into()));
let event_id = self
.services
.server_keys

View File

@@ -257,6 +257,17 @@ async fn knock_room_helper_local(
.expect("event is valid, we just created it"),
);
knock_event_stub
.insert("room_id".into(), CanonicalJsonValue::String(room_id.as_str().into()));
knock_event_stub
.insert("state_key".into(), CanonicalJsonValue::String(sender_user.as_str().into()));
knock_event_stub
.insert("sender".into(), CanonicalJsonValue::String(sender_user.as_str().into()));
knock_event_stub.insert("type".into(), CanonicalJsonValue::String("m.room.member".into()));
// In order to create a compatible ref hash (EventID) the `hashes` field needs
// to be present
self.services
@@ -419,6 +430,17 @@ async fn knock_room_helper_remote(
.expect("event is valid, we just created it"),
);
knock_event_stub
.insert("room_id".into(), CanonicalJsonValue::String(room_id.as_str().into()));
knock_event_stub
.insert("state_key".into(), CanonicalJsonValue::String(sender_user.as_str().into()));
knock_event_stub
.insert("sender".into(), CanonicalJsonValue::String(sender_user.as_str().into()));
knock_event_stub.insert("type".into(), CanonicalJsonValue::String("m.room.member".into()));
// In order to create a compatible ref hash (EventID) the `hashes` field needs
// to be present
self.services

View File

@@ -1,9 +1,14 @@
use std::collections::HashSet;
use futures::{FutureExt, StreamExt, TryFutureExt, future::ready, pin_mut};
use futures::{
FutureExt, StreamExt, TryFutureExt,
future::{join3, ready},
pin_mut,
};
use ruma::{
CanonicalJsonObject, CanonicalJsonValue, OwnedServerName, RoomId, UserId,
api::federation,
canonical_json::to_canonical_value,
events::{
StateEventType,
room::member::{MembershipState, RoomMemberEventContent},
@@ -14,7 +19,10 @@ use tuwunel_core::{
matrix::{PduCount, room_version},
pdu::PduBuilder,
state_res,
utils::{self, FutureBoolExt, future::ReadyBoolExt},
utils::{
self, FutureBoolExt,
future::{ReadyBoolExt, TryExtExt},
},
warn,
};
@@ -96,7 +104,11 @@ pub async fn leave(
// Ask a remote server if we don't have this room and are not knocking on it
if remote_leave_now || dont_have_room.and(not_knocked).await {
if let Err(e) = self.remote_leave(user_id, room_id).boxed().await {
if let Err(e) = self
.remote_leave(user_id, room_id, reason)
.boxed()
.await
{
warn!(%user_id, "Failed to leave room {room_id} remotely: {e}");
// Don't tell the client about this error
}
@@ -178,7 +190,12 @@ pub async fn leave(
#[implement(Service)]
#[tracing::instrument(name = "remote", level = "debug", skip_all)]
async fn remote_leave(&self, user_id: &UserId, room_id: &RoomId) -> Result {
async fn remote_leave(
&self,
user_id: &UserId,
room_id: &RoomId,
reason: Option<String>,
) -> Result {
let mut make_leave_response_and_server =
Err!(BadServerResponse("No remote server available to assist in leaving {room_id}."));
@@ -281,17 +298,34 @@ async fn remote_leave(&self, user_id: &UserId, room_id: &RoomId) -> Result {
let room_version_rules = room_version::rules(&room_version_id)?;
let mut leave_event_stub = serde_json::from_str::<CanonicalJsonObject>(
make_leave_response.event.get(),
)
.map_err(|e| {
err!(BadServerResponse(warn!(
"Invalid make_leave event json received from {remote_server} for {room_id}: {e:?}"
)))
})?;
let mut event = serde_json::from_str::<CanonicalJsonObject>(make_leave_response.event.get())
.map_err(|e| {
err!(BadServerResponse(warn!(
"Invalid make_leave event json received from {remote_server} for {room_id}: \
{e:?}"
)))
})?;
// TODO: Is origin needed?
leave_event_stub.insert(
let displayname = self.services.users.displayname(user_id).ok();
let avatar_url = self.services.users.avatar_url(user_id).ok();
let blurhash = self.services.users.blurhash(user_id).ok();
let (displayname, avatar_url, blurhash) = join3(displayname, avatar_url, blurhash).await;
event.insert(
"content".into(),
to_canonical_value(RoomMemberEventContent {
displayname,
avatar_url,
blurhash,
reason,
..RoomMemberEventContent::new(MembershipState::Leave)
})?,
);
event.insert(
"origin".into(),
CanonicalJsonValue::String(
self.services
@@ -301,24 +335,26 @@ async fn remote_leave(&self, user_id: &UserId, room_id: &RoomId) -> Result {
.to_owned(),
),
);
leave_event_stub.insert(
event.insert(
"origin_server_ts".into(),
CanonicalJsonValue::Integer(
utils::millis_since_unix_epoch()
.try_into()
.expect("Timestamp is valid js_int value"),
),
CanonicalJsonValue::Integer(utils::millis_since_unix_epoch().try_into()?),
);
event.insert("room_id".into(), CanonicalJsonValue::String(room_id.as_str().into()));
event.insert("state_key".into(), CanonicalJsonValue::String(user_id.as_str().into()));
event.insert("sender".into(), CanonicalJsonValue::String(user_id.as_str().into()));
event.insert("type".into(), CanonicalJsonValue::String("m.room.member".into()));
let event_id = self
.services
.server_keys
.gen_id_hash_and_sign_event(&mut leave_event_stub, &room_version_id)?;
.gen_id_hash_and_sign_event(&mut event, &room_version_id)?;
state_res::check_pdu_format(&leave_event_stub, &room_version_rules.event_format)?;
// It has enough fields to be called a proper event now
let leave_event = leave_event_stub;
state_res::check_pdu_format(&event, &room_version_rules.event_format)?;
self.services
.federation
@@ -328,7 +364,7 @@ async fn remote_leave(&self, user_id: &UserId, room_id: &RoomId) -> Result {
pdu: self
.services
.federation
.format_pdu_into(leave_event.clone(), Some(&room_version_id))
.format_pdu_into(event.clone(), Some(&room_version_id))
.await,
})
.await?;