Compare commits

...
This repository has been archived on 2025-08-14. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.

5 commits

Author SHA1 Message Date
strawberry
29283942a4 support blocking servers from fetching remote media from
akin to synapse's `prevent_media_downloads_from`

Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-01-17 23:22:55 -05:00
strawberry
432e36aa63 more error checking for deserialising events and canonical JSON
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-01-17 22:07:43 -05:00
strawberry
7534ee3946 return proper error if we fail to convert to canonical JSON
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-01-17 19:58:10 -05:00
strawberry
4ac794a6ab log error and PDU for failed auth checks
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-01-17 19:57:21 -05:00
strawberry
46327021f4 update few endpoint docs versions
Signed-off-by: strawberry <strawberry@puppygock.gay>
2024-01-17 19:54:17 -05:00
7 changed files with 115 additions and 14 deletions

View file

@ -52,4 +52,5 @@
- Only follow 6 redirects total in our default reqwest ClientBuilder
- Generate passwords with 25 characters instead of 15
- Add missing `reason` field to user ban events (`/ban`)
- For all [`/report`](https://spec.matrix.org/v1.9/client-server-api/#post_matrixclientv3roomsroomidreporteventid) requests: check if the reported event ID belongs to the reported room ID, raise report reasoning character limit to 750, fix broken formatting, make a small delayed random response per spec suggestion on privacy, and check if the sender user is in the reported room.
- For all [`/report`](https://spec.matrix.org/v1.9/client-server-api/#post_matrixclientv3roomsroomidreporteventid) requests: check if the reported event ID belongs to the reported room ID, raise report reasoning character limit to 750, fix broken formatting, make a small delayed random response per spec suggestion on privacy, and check if the sender user is in the reported room.
- Support blocking servers from downloading remote media from

View file

@ -8,6 +8,7 @@ use ruma::api::client::{
get_media_config,
},
};
use tracing::info;
/// generated MXC ID (`media-id`) length
const MXC_LENGTH: usize = 32;
@ -65,6 +66,17 @@ pub async fn get_remote_content(
server_name: &ruma::ServerName,
media_id: String,
) -> Result<get_content::v3::Response, Error> {
// we'll lie to the client and say the blocked server's media was not found and log.
// the client has no way of telling anyways so this is a security bonus.
if services()
.globals
.prevent_media_downloads_from()
.contains(&server_name.to_owned())
{
info!("Received request for remote media `{}` but server is in our media server blocklist. Returning 404.", mxc);
return Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."));
}
let content_response = services()
.sending
.send_federation_request(
@ -92,7 +104,7 @@ pub async fn get_remote_content(
Ok(content_response)
}
/// # `GET /_matrix/media/r0/download/{serverName}/{mediaId}`
/// # `GET /_matrix/media/v3/download/{serverName}/{mediaId}`
///
/// Load media from our server or over federation.
///
@ -123,7 +135,7 @@ pub async fn get_content_route(
}
}
/// # `GET /_matrix/media/r0/download/{serverName}/{mediaId}/{fileName}`
/// # `GET /_matrix/media/v3/download/{serverName}/{mediaId}/{fileName}`
///
/// Load media from our server or over federation, permitting desired filename.
///
@ -158,7 +170,7 @@ pub async fn get_content_as_filename_route(
}
}
/// # `GET /_matrix/media/r0/thumbnail/{serverName}/{mediaId}`
/// # `GET /_matrix/media/v3/thumbnail/{serverName}/{mediaId}`
///
/// Load media thumbnail from our server or over federation.
///
@ -189,6 +201,17 @@ pub async fn get_content_thumbnail_route(
cross_origin_resource_policy: Some("cross-origin".to_owned()),
})
} else if &*body.server_name != services().globals.server_name() && body.allow_remote {
// we'll lie to the client and say the blocked server's media was not found and log.
// the client has no way of telling anyways so this is a security bonus.
if services()
.globals
.prevent_media_downloads_from()
.contains(&body.server_name.to_owned())
{
info!("Received request for remote media `{}` but server is in our media server blocklist. Returning 404.", mxc);
return Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."));
}
let get_thumbnail_response = services()
.sending
.send_federation_request(

View file

@ -9,12 +9,13 @@ use ruma::{
},
events::{StateEventType, TimelineEventType},
};
use serde_json::from_str;
use std::{
collections::{BTreeMap, HashSet},
sync::Arc,
};
/// # `PUT /_matrix/client/r0/rooms/{roomId}/send/{eventType}/{txnId}`
/// # `PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}`
///
/// Send a message event into the room.
///
@ -48,6 +49,52 @@ pub async fn send_message_event_route(
));
}
// certain event types require certain fields to be valid in request bodies.
// this helps prevent attempting to handle events that we can't deserialise later so don't waste resources on it.
//
// see https://spec.matrix.org/v1.9/client-server-api/#events-2 for what's required per event type.
match body.event_type.to_string().into() {
TimelineEventType::RoomMessage => {
let body_field = body.body.body.get_field::<String>("body");
let msgtype_field = body.body.body.get_field::<String>("msgtype");
if body_field.is_err() {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"'body' field in JSON request is invalid",
));
}
if msgtype_field.is_err() {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"'msgtype' field in JSON request is invalid",
));
}
}
TimelineEventType::RoomName => {
let name_field = body.body.body.get_field::<String>("name");
if name_field.is_err() {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"'name' field in JSON request is invalid",
));
}
}
TimelineEventType::RoomTopic => {
let topic_field = body.body.body.get_field::<String>("topic");
if topic_field.is_err() {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"'topic' field in JSON request is invalid",
));
}
}
_ => {} // event may be custom/experimental or can be empty don't do anything with it
};
// Check if this is a new transaction id
if let Some(response) =
services()
@ -79,7 +126,7 @@ pub async fn send_message_event_route(
.build_and_append_pdu(
PduBuilder {
event_type: body.event_type.to_string().into(),
content: serde_json::from_str(body.body.body.json().get())
content: from_str(body.body.body.json().get())
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid JSON body."))?,
unsigned: Some(unsigned),
state_key: None,

View file

@ -1822,8 +1822,10 @@ pub async fn create_invite_route(
));
}
let mut signed_event = utils::to_canonical_object(&body.event)
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invite event is invalid."))?;
let mut signed_event = utils::to_canonical_object(&body.event).map_err(|e| {
error!("Failed to convert invite event to canonical JSON: {}", e);
Error::BadRequest(ErrorKind::InvalidParam, "Invite event is invalid.")
})?;
ruma::signatures::hash_and_sign_event(
services().globals.server_name().as_str(),

View file

@ -128,6 +128,9 @@ pub struct Config {
#[serde(default)]
pub allow_guest_registration: bool,
#[serde(default = "Vec::new")]
pub prevent_media_downloads_from: Vec<OwnedServerName>,
#[serde(flatten)]
pub catchall: BTreeMap<String, IgnoredAny>,
}
@ -305,6 +308,13 @@ impl fmt::Display for Config {
"RocksDB database optimize for spinning disks",
&self.rocksdb_optimize_for_spinning_disks.to_string(),
),
("Prevent Media Downloads From", {
let mut lst = vec![];
for domain in &self.prevent_media_downloads_from {
lst.push(domain.host());
}
&lst.join(", ")
}),
];
let mut msg: String = "Active config values:\n\n".to_owned();

View file

@ -423,6 +423,10 @@ impl Service<'_> {
self.config.rocksdb_optimize_for_spinning_disks
}
pub fn prevent_media_downloads_from(&self) -> &[OwnedServerName] {
&self.config.prevent_media_downloads_from
}
pub fn supported_room_versions(&self) -> Vec<RoomVersionId> {
let mut room_versions: Vec<RoomVersionId> = vec![];
room_versions.extend(self.stable_room_versions.clone());

View file

@ -260,8 +260,17 @@ impl Service {
unsigned.insert(
"prev_content".to_owned(),
CanonicalJsonValue::Object(
utils::to_canonical_object(prev_state.content.clone())
.expect("event is valid, we just created it"),
utils::to_canonical_object(prev_state.content.clone()).map_err(
|e| {
error!(
"Failed to convert prev_state to canonical JSON: {}",
e
);
Error::bad_database(
"Failed to convert prev_state to canonical JSON.",
)
},
)?,
),
);
}
@ -802,7 +811,7 @@ impl Service {
|k, s| auth_events.get(&(k.clone(), s.to_owned())),
)
.map_err(|e| {
error!("{:?}", e);
error!("Auth check failed: {:?}", e);
Error::bad_database("Auth check failed.")
})?;
@ -814,8 +823,10 @@ impl Service {
}
// Hash and sign
let mut pdu_json =
utils::to_canonical_object(&pdu).expect("event is valid, we just created it");
let mut pdu_json = utils::to_canonical_object(&pdu).map_err(|e| {
error!("Failed to convert PDU to canonical JSON: {}", e);
Error::bad_database("Failed to convert PDU to canonical JSON.")
})?;
pdu_json.remove("event_id");
@ -1103,7 +1114,10 @@ impl Service {
pdu.redact(room_version_id, reason)?;
self.replace_pdu(
&pdu_id,
&utils::to_canonical_object(&pdu).expect("PDU is an object"),
&utils::to_canonical_object(&pdu).map_err(|e| {
error!("Failed to convert PDU to canonical JSON: {}", e);
Error::bad_database("Failed to convert PDU to canonical JSON.")
})?,
&pdu,
)?;
}