lib.rs (16075B)
1 #![feature(no_coverage)] 2 #![feature(type_alias_impl_trait)] 3 #![allow(dead_code)] 4 #![allow(clippy::too_many_arguments)] 5 6 use crate::types::{Flow, LoginGet, LoginPost}; 7 use once_cell::sync::OnceCell; 8 use std::{collections::HashMap, env}; 9 10 pub mod types; 11 12 #[no_coverage] 13 pub fn access_token() -> &'static String { 14 static INSTANCE: OnceCell<String> = OnceCell::new(); 15 INSTANCE.get_or_init(login) 16 } 17 18 #[no_coverage] 19 pub fn client() -> &'static reqwest::blocking::Client { 20 static INSTANCE: OnceCell<reqwest::blocking::Client> = OnceCell::new(); 21 INSTANCE.get_or_init(|| { 22 reqwest::blocking::Client::builder() 23 .connect_timeout(Some(std::time::Duration::from_secs(30))) 24 .user_agent("synapse-fuzzer") 25 .gzip(true) 26 .build() 27 .unwrap() 28 }) 29 } 30 31 #[no_coverage] 32 fn login() -> String { 33 let server = match env::var("MATRIX_SERVER") { 34 Ok(v) => v, 35 Err(_) => "http://localhost:8008".to_string(), 36 }; 37 let username = match env::var("MATRIX_USERNAME") { 38 Ok(v) => v, 39 Err(e) => panic!("$MATRIX_USERNAME is not set ({})", e), 40 }; 41 let password = match env::var("MATRIX_PASSWORD") { 42 Ok(v) => v, 43 Err(e) => panic!("$MATRIX_PASSWORD is not set ({})", e), 44 }; 45 let client = crate::client(); 46 let res: LoginGet = client 47 .get(format!("{}/_matrix/client/v3/login", server)) 48 .send() 49 .unwrap() 50 .json() 51 .unwrap(); 52 assert!(res.flows.contains(&Flow { 53 type_: "m.login.password".to_string(), 54 })); 55 56 let mut map = HashMap::new(); 57 map.insert("type", "m.login.password"); 58 map.insert("user", &username); 59 map.insert("password", &password); 60 let res: LoginPost = client 61 .post(format!("{}/_matrix/client/v3/login", server)) 62 .json(&map) 63 .send() 64 .unwrap() 65 .json() 66 .unwrap(); 67 68 res.access_token 69 } 70 71 #[cfg(all(test, not(fuzzing)))] 72 mod tests { 73 use std::{env, time::Instant}; 74 75 use reqwest::header::{HeaderValue, CONTENT_TYPE}; 76 use serde_json::json; 77 78 use crate::types::create_room::CreateRoomMagicJSON; 79 80 #[test] 81 #[no_coverage] 82 fn connection_test() { 83 let server = match env::var("MATRIX_SERVER") { 84 Ok(v) => v, 85 Err(_) => "http://localhost:8008".to_string(), 86 }; 87 let client = crate::client(); 88 let resp = client 89 .get(format!("{}/_matrix/key/v2/server", server)) 90 .send() 91 .unwrap(); 92 assert!(resp.status().is_success()); 93 } 94 95 #[test] 96 #[no_coverage] 97 fn null_in_room() { 98 let content = CreateRoomMagicJSON { 99 name: Some("a".to_string()), 100 //room_alias_name: Some("\0".to_string()), 101 visibility: Some("a".to_string()), 102 is_direct: Some(false), 103 topic: Some("a".to_string()), 104 ..Default::default() 105 }; 106 let access_token = crate::access_token(); 107 let client = crate::client(); 108 let server = match env::var("MATRIX_SERVER") { 109 Ok(v) => v, 110 Err(_) => "http://localhost:8008".to_string(), 111 }; 112 let resp = client 113 .post(format!("{}/_matrix/client/v3/createRoom", server)) 114 .header("Authorization", format!("Bearer {}", access_token)) 115 .json(&content) 116 .send() 117 .unwrap(); 118 119 assert!(!resp.status().is_success()); 120 assert!(resp.text().unwrap().contains("Internal server error")); 121 } 122 123 #[test] 124 #[no_coverage] 125 fn sql_injection_test() { 126 let content = CreateRoomMagicJSON { 127 name: Some("beep; pg_sleep(50);--".to_string()), 128 initial_state: vec![ 129 /*crate::types::create_room::StateEventJSON { 130 _type: "m.room.member".to_string(), 131 state_key: "@fuzzer:localhost".to_string(), 132 content: json!({ 133 "membership": "join", 134 "displayname": "beep\0' pg_sleep(50);--" 135 }), 136 },*/ 137 crate::types::create_room::StateEventJSON { 138 _type: "beep; pg_sleep(50);--".to_string(), 139 state_key: "beep; pg_sleep(50);--".to_string(), 140 content: json!({}), 141 }, 142 crate::types::create_room::StateEventJSON { 143 _type: "beep%00; pg_sleep(50);--".to_string(), 144 state_key: "beep%00; pg_sleep(50);--".to_string(), 145 content: json!({}), 146 }, 147 crate::types::create_room::StateEventJSON { 148 _type: "beep\0; pg_sleep(50);--".to_string(), 149 state_key: "beep\0; pg_sleep(50);--".to_string(), 150 content: json!({}), 151 }, 152 crate::types::create_room::StateEventJSON { 153 _type: "beep; pg_sleep(50);".to_string(), 154 state_key: "beep; pg_sleep(50);".to_string(), 155 content: json!({}), 156 }, 157 crate::types::create_room::StateEventJSON { 158 _type: "beep%00; pg_sleep(50);".to_string(), 159 state_key: "beep%00; pg_sleep(50);".to_string(), 160 content: json!({}), 161 }, 162 crate::types::create_room::StateEventJSON { 163 _type: "beep\0; pg_sleep(50);".to_string(), 164 state_key: "beep\0; pg_sleep(50);".to_string(), 165 content: json!({}), 166 }, 167 crate::types::create_room::StateEventJSON { 168 _type: "beep' pg_sleep(50);".to_string(), 169 state_key: "beep' pg_sleep(50);".to_string(), 170 content: json!({}), 171 }, 172 crate::types::create_room::StateEventJSON { 173 _type: "beep%00' pg_sleep(50);".to_string(), 174 state_key: "beep%00' pg_sleep(50);".to_string(), 175 content: json!({}), 176 }, 177 crate::types::create_room::StateEventJSON { 178 _type: "beep\0' pg_sleep(50);".to_string(), 179 state_key: "beep\0' pg_sleep(50);".to_string(), 180 content: json!({}), 181 }, 182 crate::types::create_room::StateEventJSON { 183 _type: "beep' pg_sleep(50);--".to_string(), 184 state_key: "beep' pg_sleep(50);--".to_string(), 185 content: json!({}), 186 }, 187 crate::types::create_room::StateEventJSON { 188 _type: "beep%00' pg_sleep(50);--".to_string(), 189 state_key: "beep%00' pg_sleep(50);--".to_string(), 190 content: json!({}), 191 }, 192 crate::types::create_room::StateEventJSON { 193 _type: "beep\0' pg_sleep(50);--".to_string(), 194 state_key: "beep\0' pg_sleep(50);--".to_string(), 195 content: json!({}), 196 }, 197 ], 198 ..Default::default() 199 }; 200 let access_token = crate::access_token(); 201 let client = crate::client(); 202 let start = Instant::now(); 203 let server = match env::var("MATRIX_SERVER") { 204 Ok(v) => v, 205 Err(_) => "http://localhost:8008".to_string(), 206 }; 207 let resp = client 208 .post(format!("{}/_matrix/client/v3/createRoom", server)) 209 .header("Authorization", format!("Bearer {}", access_token)) 210 .json(&content) 211 .send() 212 .unwrap(); 213 let duration = start.elapsed(); 214 println!("Time elapsed in request is: {:?}", duration); 215 println!("{:?}", resp); 216 let status = resp.status(); 217 let text = resp.text().unwrap(); 218 println!("{}", text); 219 220 assert!(!status.is_success()); 221 assert!(text.contains("Internal server error")); 222 } 223 224 #[test] 225 #[no_coverage] 226 fn weird_req() { 227 let content = std::fs::read_to_string("./weird_ones/af84a60a1b7997b4.json").unwrap(); 228 let access_token = crate::access_token(); 229 let client = crate::client(); 230 let server = match env::var("MATRIX_SERVER") { 231 Ok(v) => v, 232 Err(_) => "http://localhost:8008".to_string(), 233 }; 234 let resp = client 235 .post(format!("{}/_matrix/client/v3/createRoom", server)) 236 .header("Authorization", format!("Bearer {}", access_token)) 237 .header(CONTENT_TYPE, HeaderValue::from_static("application/json")) 238 .body(content) 239 .send(); 240 assert!(resp.is_err()) 241 } 242 } 243 244 #[cfg(all(fuzzing, test))] 245 mod tests { 246 use crate::types::{create_room::CreateRoomMagicJSON, LoginPostReq}; 247 use std::env; 248 249 fn login(data: &LoginPostReq) -> bool { 250 let mut json_data = data.clone(); 251 // We hardcode the type for better fuzzing 252 cfg_if::cfg_if! { 253 if #[cfg(feature = "token_auth")] { 254 json_data._type = "com.devture.shared_secret_auth".to_string(); 255 } else { 256 json_data._type = "m.login.password".to_string(); 257 } 258 } 259 260 let username = match env::var("MATRIX_USERNAME") { 261 Ok(v) => v, 262 Err(e) => panic!("$MATRIX_USERNAME is not set ({})", e), 263 }; 264 265 if json_data.user.is_some() { 266 json_data.user = Some(username.clone()); 267 } 268 if let Some(identifier) = &mut json_data.identifier { 269 identifier.user = username; 270 identifier._type = "m.id.user".to_string(); 271 } 272 273 if let Some(user) = &json_data.user { 274 if user.contains('\0') { 275 json_data.user = Some(user.replace('\0', "")); 276 } 277 } 278 if let Some(medium) = &json_data.medium { 279 if medium.contains('\0') { 280 json_data.medium = Some(medium.replace('\0', "")); 281 } 282 } 283 if let Some(address) = &json_data.address { 284 if address.contains('\0') { 285 json_data.address = Some(address.replace('\0', "")); 286 } 287 } 288 if let Some(user) = &json_data.user { 289 if user.contains('\0') { 290 json_data.user = Some(user.replace('\0', "")); 291 } 292 } 293 /*if let Some(password) = &json_data.password { 294 if password.contains('\0' { 295 json_data.password = Some(password.replace('\0', "")); 296 } 297 }*/ 298 299 let client = crate::client(); 300 let server = match env::var("MATRIX_SERVER") { 301 Ok(v) => v, 302 Err(_) => "http://localhost:8008".to_string(), 303 }; 304 let resp = client 305 .post(format!("{}/_matrix/client/v3/login", server)) 306 .json(&json_data) 307 .send(); 308 if let Ok(resp) = resp { 309 let status = resp.status(); 310 if !status.is_success() { 311 /*if status == 400 { 312 return true; 313 }*/ 314 let content = resp.text(); 315 if let Ok(ref content) = content { 316 if content.contains("Unknown login type") 317 || content.contains("Invalid login submission") 318 || content.contains("Invalid username or password") 319 { 320 return true; 321 } 322 } 323 println!("Status: {:?}", status); 324 println!("Content: {:?}", content); 325 } 326 } 327 false 328 } 329 330 #[test] 331 fn fuzz_login() { 332 let client = crate::client(); 333 let server = match env::var("MATRIX_SERVER") { 334 Ok(v) => v, 335 Err(_) => "http://localhost:8008".to_string(), 336 }; 337 let resp = client 338 .get(format!("{}/_matrix/key/v2/server", server)) 339 .send() 340 .unwrap(); 341 if !resp.status().is_success() { 342 panic!("Failed to connect"); 343 } 344 345 let result = fuzzcheck::fuzz_test(login) 346 .default_options() 347 .stop_after_first_test_failure(true) 348 .launch(); 349 assert!(!result.found_test_failure); 350 } 351 352 fn create_room(data: &CreateRoomMagicJSON) -> bool { 353 let mut json_data = data.clone(); 354 for mut state in &mut json_data.initial_state { 355 if state.content.is_array() 356 || state.content.is_boolean() 357 || state.content.is_null() 358 || state.content.is_string() 359 || state.content.is_u64() 360 { 361 state.content = serde_json::Value::Object(serde_json::Map::new()); 362 } 363 } 364 365 // HACK due to https://github.com/matrix-org/synapse/issues/13510 366 /*if let Some(room_alias_name) = &json_data.room_alias_name { 367 if room_alias_name.contains('\0') { 368 json_data.room_alias_name = Some(room_alias_name.replace('\0', "")); 369 } 370 }*/ 371 // HACK due to NUL in type or state_key 372 for state in json_data.initial_state.iter_mut() { 373 state._type = state._type.replace('\0', ""); 374 state.state_key = state.state_key.replace('\0', ""); 375 } 376 377 /*// HACK due to https://github.com/matrix-org/synapse/issues/13511 378 if let Some(pids) = &data.invite_3pid { 379 for pid in pids { 380 if pid.address.is_empty() { 381 return true; 382 } 383 } 384 }*/ 385 386 // TODO: Login once and reuse the access token 387 let access_token = crate::access_token(); 388 let client = crate::client(); 389 let server = match env::var("MATRIX_SERVER") { 390 Ok(v) => v, 391 Err(_) => "http://localhost:8008".to_string(), 392 }; 393 let resp = client 394 .post(format!("{}/_matrix/client/v3/createRoom", server)) 395 .header("Authorization", format!("Bearer {}", access_token)) 396 .json(&json_data) 397 .send(); 398 if let Ok(resp) = resp { 399 let status = resp.status(); 400 if !status.is_success() { 401 //println!("Status: {:?}", status); 402 let content = resp.text(); 403 if let Ok(ref content) = content { 404 if content.contains("M_ROOM_IN_USE") 405 || content.contains("Invalid characters in room alias") 406 || content.contains("':' is not permitted in the room alias name. Please note this expects a local part — 'wombat', not '#wombat:example.com'.") 407 || content.contains("M_UNSUPPORTED_ROOM_VERSION") 408 || content.contains("Invalid user_id") 409 || content.contains("is not a valid preset") 410 || content.contains("You are not allowed to set others state") 411 || content.contains("JSON integer out of range") 412 || content.contains(" too large") 413 { 414 return true; 415 } 416 } 417 println!("Content: {:?}", content); 418 419 return false; 420 } 421 } 422 true 423 } 424 425 #[test] 426 fn fuzz_create_room() { 427 let client = crate::client(); 428 let server = match env::var("MATRIX_SERVER") { 429 Ok(v) => v, 430 Err(_) => "http://localhost:8008".to_string(), 431 }; 432 let resp = client 433 .get(format!("{}/_matrix/key/v2/server", server)) 434 .send() 435 .unwrap(); 436 if !resp.status().is_success() { 437 panic!("Failed to connect"); 438 } 439 440 let result = fuzzcheck::fuzz_test(create_room) 441 .default_options() 442 .stop_after_first_test_failure(true) 443 .launch(); 444 assert!(!result.found_test_failure); 445 } 446 }