matrix-fuzz

git clone git://archive.git.mtrnord.blog/MTRNord/matrix-fuzz.git
Log | Files | Refs | README | LICENSE

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 }