matrix-fuzz

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

commit 6de2661b4c739c27ce2089931f38d415a0cfa58f
parent 80139672bea0b9548ac7dfdee54315f4fa6214ff
Author: MTRNord <mtrnord1@gmail.com>
Date:   Sat, 13 Aug 2022 12:36:11 +0200

Add basic login fuzzing

Diffstat:
MCargo.toml | 4++++
Aafl/createRoom/in/10 | 2++
Aafl/createRoom/in/14 | 2++
Aafl/createRoom/in/15 | 2++
Aafl/createRoom/in/8 | 2++
Aafl/createRoom/in/9 | 2++
Msrc/fuzzTargets/createRoom.rs | 26+++++++++++++++-----------
Msrc/lib.rs | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Msrc/types.rs | 42++++++++++++++++++++++++++++++++++++++++++
9 files changed, 164 insertions(+), 27 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -19,3 +19,7 @@ once_cell = "1.13.0" reqwest = {version = "0.11.11", features = ["blocking", "json", "gzip"]} serde = {version = "1.0", features = ["derive"]} serde_json = "1.0.83" + +[features] +default = ["fuzzing"] +fuzzing = [] diff --git a/afl/createRoom/in/10 b/afl/createRoom/in/10 @@ -0,0 +1 @@ +z"room_version":"","topic":"��"} +\ No newline at end of file diff --git a/afl/createRoom/in/14 b/afl/createRoom/in/14 @@ -0,0 +1 @@ +{"ronm_version":"","topic":"��"} +\ No newline at end of file diff --git a/afl/createRoom/in/15 b/afl/createRoom/in/15 @@ -0,0 +1 @@ +{"rooí_version":"","topic":"��"} +\ No newline at end of file diff --git a/afl/createRoom/in/8 b/afl/createRoom/in/8 @@ -0,0 +1 @@ +{"rnom_version":"","topic":"��"} +\ No newline at end of file diff --git a/afl/createRoom/in/9 b/afl/createRoom/in/9 @@ -0,0 +1 @@ +û"room_version":"","topic":"��"} +\ No newline at end of file diff --git a/src/fuzzTargets/createRoom.rs b/src/fuzzTargets/createRoom.rs @@ -3,24 +3,28 @@ use matrix_fuzz::{client, secrets::ACCESS_TOKEN, types::create_room::CreateRoomM fn main() { afl::fuzz_nohook!(|data: CreateRoomMagicJSON| { - // FIXME: We probably should set it to null and not do a false positive + let mut data = data; // HACK due to https://github.com/matrix-org/synapse/issues/13510 if let Some(room_alias_name) = &data.room_alias_name { if room_alias_name.contains('\0') { - return; + data.room_alias_name = None; } } + // HACK due to NUL in type or state_key - if let Some(initial_state) = &data.initial_state { - for state in initial_state { - if state._type.contains('\0') { - return; - } - if state.state_key.contains('\0') { - return; - } - } + if let Some(initial_state) = &mut data.initial_state { + initial_state + .retain(|state| !(state._type.contains('\0') || state.state_key.contains('\0'))); + // for state in initial_state { + // if state._type.contains('\0') { + // return; + // } + // if state.state_key.contains('\0') { + // return; + // } + // } } + /*// HACK due to https://github.com/matrix-org/synapse/issues/13511 if let Some(pids) = &data.invite_3pid { for pid in pids { diff --git a/src/lib.rs b/src/lib.rs @@ -128,27 +128,105 @@ mod tests { #[cfg(all(fuzzing, test))] mod tests { - use crate::types::create_room::{CreateRoomMagic, CreateRoomMagicJSON}; + use crate::types::{ + create_room::{CreateRoomMagic, CreateRoomMagicJSON}, + LoginPostReq, + }; + + fn login(data: &LoginPostReq) -> bool { + let mut data = data.clone(); + // We hardcode the type for better fuzzing + data._type = "m.login.password".to_string(); + + if let Some(user) = &data.user { + if user.contains("\0") { + data.user = None; + } + } + if let Some(medium) = &data.medium { + if medium.contains("\0") { + data.medium = None; + } + } + if let Some(address) = &data.address { + if address.contains("\0") { + data.address = None; + } + } + if let Some(user) = &data.user { + if user.contains("\0") { + data.user = None; + } + } + + let client = crate::client(); + let resp = client + .post("http://localhost:8008/_matrix/client/v3/login") + .json(&data) + .send(); + if let Ok(resp) = resp { + let status = resp.status(); + if !status.is_success() { + if status == 400 { + return true; + } + let content = resp.text(); + if let Ok(ref content) = content { + if content.contains("Unknown login type") + || content.contains("Invalid login submission") + || content.contains("Invalid username or password") + { + return true; + } + } + println!("Status: {:?}", status); + println!("Content: {:?}", content); + } + } + false + } + + #[test] + fn fuzz_login() { + let client = crate::client(); + let resp = client + .get("http://localhost:8008/_matrix/key/v2/server") + .send() + .unwrap(); + if !resp.status().is_success() { + panic!("Failed to connect"); + } + + let result = fuzzcheck::fuzz_test(login) + .default_options() + .stop_after_first_test_failure(true) + .launch(); + assert!(!result.found_test_failure); + } fn create_room(data: &CreateRoomMagic) -> bool { + let mut json_data: CreateRoomMagicJSON = data.into(); // FIXME: We probably should set it to null and not do a false positive // HACK due to https://github.com/matrix-org/synapse/issues/13510 - if let Some(room_alias_name) = &data.room_alias_name { + if let Some(room_alias_name) = &json_data.room_alias_name { if room_alias_name.contains('\0') { - return true; + json_data.room_alias_name = None; } } // HACK due to NUL in type or state_key - if let Some(initial_state) = &data.initial_state { - for state in initial_state { - if state._type.contains('\0') { - return true; - } - if state.state_key.contains('\0') { - return true; - } - } + if let Some(initial_state) = &mut json_data.initial_state { + initial_state + .retain(|state| !(state._type.contains('\0') || state.state_key.contains('\0'))); + // for state in initial_state { + // if state._type.contains('\0') { + // return; + // } + // if state.state_key.contains('\0') { + // return; + // } + // } } + /*// HACK due to https://github.com/matrix-org/synapse/issues/13511 if let Some(pids) = &data.invite_3pid { for pid in pids { @@ -161,7 +239,6 @@ mod tests { // TODO: Login once and reuse the access token let access_token = crate::access_token(); let client = crate::client(); - let json_data: CreateRoomMagicJSON = data.into(); let resp = client .post("http://localhost:8008/_matrix/client/v3/createRoom") .header("Authorization", format!("Bearer {}", access_token)) @@ -180,9 +257,9 @@ mod tests { || content.contains("Invalid user_id") || content.contains("is not a valid preset") || content.contains("You are not allowed to set others state") - { - return true; - } + { + return true; + } } println!("Content: {:?}", content); diff --git a/src/types.rs b/src/types.rs @@ -1,5 +1,7 @@ pub mod create_room; +use arbitrary::Arbitrary; +use fuzzcheck::DefaultMutator; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] @@ -19,3 +21,43 @@ pub struct LoginPost { pub access_token: String, pub home_server: String, } + +#[derive(Debug, Clone, Serialize, Deserialize, DefaultMutator, Arbitrary)] +pub struct LoginPostReq { + #[serde(skip_serializing_if = "Option::is_none")] + pub address: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub device_id: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub identifier: Option<Identifier>, + #[serde(skip_serializing_if = "Option::is_none")] + pub initial_device_display_name: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub medium: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub password: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub refresh_token: Option<bool>, + #[serde(skip_serializing_if = "Option::is_none")] + pub token: Option<String>, + #[serde(rename = "type")] + pub _type: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub user: Option<String>, +} + +#[derive(Debug, Clone, Serialize, Deserialize, DefaultMutator, Arbitrary)] +pub struct Identifier { + #[serde(rename = "type")] + pub _type: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub user: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub medium: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub address: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub country: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub phone: Option<String>, +}