From a118e91bf56551352afd3193fcec57fb0678cbe7 Mon Sep 17 00:00:00 2001 From: Patrick Meredith Date: Fri, 20 Dec 2024 10:10:33 -0500 Subject: [PATCH 01/13] Devin message header fuzz --- Cargo.toml | 5 +- fuzz/.gitignore | 4 + fuzz/Cargo.toml | 18 ++++ fuzz/fuzz_targets/fuzz_target_1.rs | 7 ++ fuzz/fuzz_targets/header_length.rs | 21 ++++ src/cmap.rs | 5 + src/cmap/conn.rs | 5 + src/cmap/conn/wire.rs | 18 +++- src/cmap/conn/wire/header.rs | 60 ++++++++++-- src/cmap/conn/wire/message.rs | 150 +++++++++++++++++++++-------- src/fuzz/message_flags.rs | 17 ++++ src/fuzz/mod.rs | 11 +++ src/fuzz/raw_document.rs | 26 +++++ src/lib.rs | 7 +- 14 files changed, 303 insertions(+), 51 deletions(-) create mode 100644 fuzz/.gitignore create mode 100644 fuzz/Cargo.toml create mode 100644 fuzz/fuzz_targets/fuzz_target_1.rs create mode 100644 fuzz/fuzz_targets/header_length.rs create mode 100644 src/fuzz/message_flags.rs create mode 100644 src/fuzz/mod.rs create mode 100644 src/fuzz/raw_document.rs diff --git a/Cargo.toml b/Cargo.toml index 3ca3de6fd..ab7643dae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,11 +67,14 @@ in-use-encryption-unstable = ["in-use-encryption"] # The tracing API is unstable and may have backwards-incompatible changes in minor version updates. # TODO: pending https://github.com/tokio-rs/tracing/issues/2036 stop depending directly on log. tracing-unstable = ["dep:tracing", "dep:log"] +fuzzing = ["dep:bitflags", "dep:arbitrary", "dep:byteorder", "arbitrary/derive"] [dependencies] async-trait = "0.1.42" base64 = "0.13.0" -bitflags = "1.1.0" +bitflags = { version = "1.1.0", optional = true } +arbitrary = { version = "1.3", optional = true } +byteorder = { version = "1.4", optional = true } bson = { git = "https://github.com/mongodb/bson-rust", branch = "main", version = "2.11.0" } chrono = { version = "0.4.7", default-features = false, features = [ "clock", diff --git a/fuzz/.gitignore b/fuzz/.gitignore new file mode 100644 index 000000000..1a45eee77 --- /dev/null +++ b/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml new file mode 100644 index 000000000..73cdd9b09 --- /dev/null +++ b/fuzz/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "mongodb-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +mongodb = { path = "..", features = ["fuzzing"] } + +[[bin]] +name = "header_length" +path = "fuzz_targets/header_length.rs" +test = false +doc = false diff --git a/fuzz/fuzz_targets/fuzz_target_1.rs b/fuzz/fuzz_targets/fuzz_target_1.rs new file mode 100644 index 000000000..43a88c14f --- /dev/null +++ b/fuzz/fuzz_targets/fuzz_target_1.rs @@ -0,0 +1,7 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + // fuzzed code goes here +}); diff --git a/fuzz/fuzz_targets/header_length.rs b/fuzz/fuzz_targets/header_length.rs new file mode 100644 index 000000000..13754ed09 --- /dev/null +++ b/fuzz/fuzz_targets/header_length.rs @@ -0,0 +1,21 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; +use mongodb::cmap::conn::wire::header::Header; + +fuzz_target!(|data: &[u8]| { + if data.len() < Header::LENGTH { + return; + } + if let Ok(header) = Header::from_slice(data) { + if header.length < 0 { + panic!("Negative length detected: {}", header.length); + } + if header.length as usize > std::usize::MAX - Header::LENGTH { + panic!("Integer overflow detected in length calculation"); + } + let total_size = Header::LENGTH + header.length as usize; + if total_size > data.len() { + return; + } + } +}); diff --git a/src/cmap.rs b/src/cmap.rs index b0a02f8ed..0f9f16235 100644 --- a/src/cmap.rs +++ b/src/cmap.rs @@ -1,7 +1,12 @@ #[cfg(test)] pub(crate) mod test; +#[cfg(feature = "fuzzing")] +pub mod conn; + +#[cfg(not(feature = "fuzzing"))] pub(crate) mod conn; + mod connection_requester; pub(crate) mod establish; mod manager; diff --git a/src/cmap/conn.rs b/src/cmap/conn.rs index f1ddc5910..f2807b6d8 100644 --- a/src/cmap/conn.rs +++ b/src/cmap/conn.rs @@ -1,6 +1,11 @@ mod command; pub(crate) mod pooled; mod stream_description; + +#[cfg(feature = "fuzzing")] +pub mod wire; + +#[cfg(not(feature = "fuzzing"))] pub(crate) mod wire; use std::{sync::Arc, time::Instant}; diff --git a/src/cmap/conn/wire.rs b/src/cmap/conn/wire.rs index 76ef2cfab..71e57a1df 100644 --- a/src/cmap/conn/wire.rs +++ b/src/cmap/conn/wire.rs @@ -1,8 +1,18 @@ -mod header; -pub(crate) mod message; -mod util; +pub mod header; +pub mod message; +pub mod util; pub(crate) use self::{ - message::{Message, MessageFlags}, util::next_request_id, }; + +#[cfg(feature = "fuzzing")] +pub use self::{ + message::Message, +}; + +#[cfg(feature = "fuzzing")] +pub use crate::fuzz::message_flags::MessageFlags; + +#[cfg(not(feature = "fuzzing"))] +pub use self::message::MessageFlags; diff --git a/src/cmap/conn/wire/header.rs b/src/cmap/conn/wire/header.rs index 791395f4c..5c3636588 100644 --- a/src/cmap/conn/wire/header.rs +++ b/src/cmap/conn/wire/header.rs @@ -1,10 +1,15 @@ +use std::io::Cursor; +use byteorder::{ReadBytesExt, LittleEndian}; use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt}; - use crate::error::{ErrorKind, Result}; +#[cfg(feature = "fuzzing")] +use arbitrary::Arbitrary; + /// The wire protocol op codes. #[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub(crate) enum OpCode { +#[cfg_attr(feature = "fuzzing", derive(Arbitrary))] +pub enum OpCode { Reply = 1, Query = 2004, Message = 2013, @@ -13,7 +18,7 @@ pub(crate) enum OpCode { impl OpCode { /// Attempt to infer the op code based on the numeric value. - fn from_i32(i: i32) -> Result { + pub fn from_i32(i: i32) -> Result { match i { 1 => Ok(OpCode::Reply), 2004 => Ok(OpCode::Query), @@ -28,8 +33,9 @@ impl OpCode { } /// The header for any wire protocol message. -#[derive(Debug)] -pub(crate) struct Header { +#[derive(Debug, Clone)] +#[cfg_attr(feature = "fuzzing", derive(Arbitrary))] +pub struct Header { pub length: i32, pub request_id: i32, pub response_to: i32, @@ -37,9 +43,50 @@ pub(crate) struct Header { } impl Header { + #[cfg(feature = "fuzzing")] + pub const LENGTH: usize = 4 * std::mem::size_of::(); + + #[cfg(not(feature = "fuzzing"))] pub(crate) const LENGTH: usize = 4 * std::mem::size_of::(); - /// Serializes the Header and writes the bytes to `w`. + #[cfg(feature = "fuzzing")] + pub fn from_slice(data: &[u8]) -> Result { + if data.len() < Self::LENGTH { + return Err(ErrorKind::InvalidResponse { + message: format!( + "Header requires {} bytes but only got {}", + Self::LENGTH, + data.len() + ), + } + .into()); + } + let mut cursor = Cursor::new(data); + + let length = ReadBytesExt::read_i32::(&mut cursor).map_err(|e| ErrorKind::InvalidResponse { + message: format!("Failed to read length: {}", e), + })?; + + let request_id = ReadBytesExt::read_i32::(&mut cursor).map_err(|e| ErrorKind::InvalidResponse { + message: format!("Failed to read request_id: {}", e), + })?; + + let response_to = ReadBytesExt::read_i32::(&mut cursor).map_err(|e| ErrorKind::InvalidResponse { + message: format!("Failed to read response_to: {}", e), + })?; + + let op_code = OpCode::from_i32(ReadBytesExt::read_i32::(&mut cursor).map_err(|e| ErrorKind::InvalidResponse { + message: format!("Failed to read op_code: {}", e), + })?)?; + + Ok(Self { + length, + request_id, + response_to, + op_code, + }) + } + pub(crate) async fn write_to(&self, stream: &mut W) -> Result<()> { stream.write_all(&self.length.to_le_bytes()).await?; stream.write_all(&self.request_id.to_le_bytes()).await?; @@ -51,7 +98,6 @@ impl Header { Ok(()) } - /// Reads bytes from `r` and deserializes them into a header. pub(crate) async fn read_from( reader: &mut R, ) -> Result { diff --git a/src/cmap/conn/wire/message.rs b/src/cmap/conn/wire/message.rs index c746c8b95..084c4bf93 100644 --- a/src/cmap/conn/wire/message.rs +++ b/src/cmap/conn/wire/message.rs @@ -1,57 +1,99 @@ use std::io::Read; -use bitflags::bitflags; -use bson::{doc, Array, Document}; +use bson::{doc, Array, Document, RawDocumentBuf}; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; -#[cfg(any( - feature = "zstd-compression", - feature = "zlib-compression", - feature = "snappy-compression" -))] -use crate::options::Compressor; +#[cfg(feature = "fuzzing")] +use arbitrary::Arbitrary; + +#[cfg(feature = "fuzzing")] +use crate::fuzz::{ + message_flags::MessageFlags, + raw_document::{FuzzDocumentSequenceImpl, FuzzRawDocumentImpl}, +}; + +#[cfg(not(feature = "fuzzing"))] +use bitflags::bitflags; + +#[cfg(not(feature = "fuzzing"))] +bitflags::bitflags! { + pub struct MessageFlags: u32 { + const NONE = 0; + const CHECKSUM_PRESENT = 0x04; + const MORE_TO_COME = 0x02; + const EXHAUST_ALLOWED = 0x10000; + } +} + use crate::{ - bson::RawDocumentBuf, bson_util, checked::Checked, - cmap::{conn::wire::util::SyncCountReader, Command}, + cmap::conn::{ + wire::{header::Header, header::OpCode, next_request_id, util::SyncCountReader}, + Command, + }, compression::decompress::decompress_message, error::{Error, ErrorKind, Result}, - runtime::SyncLittleEndianRead, }; -use super::{ - header::{Header, OpCode}, - next_request_id, -}; +#[cfg(feature = "fuzzing")] +fn generate_raw_document(u: &mut arbitrary::Unstructured) -> arbitrary::Result { + let doc = FuzzRawDocumentImpl::arbitrary(u)?; + Ok(doc.into()) +} + +#[cfg(feature = "fuzzing")] +fn generate_document_sequences(u: &mut arbitrary::Unstructured) -> arbitrary::Result> { + let seq = FuzzDocumentSequenceImpl::arbitrary(u)?; + Ok(vec![DocumentSequence { + identifier: String::arbitrary(u)?, + documents: seq.0.into_iter().map(Into::into).collect(), + }]) +} + +const DEFAULT_MAX_MESSAGE_SIZE_BYTES: i32 = 48 * 1024 * 1024; /// Represents an OP_MSG wire protocol operation. -#[derive(Debug)] -pub(crate) struct Message { - /// OP_MSG payload type 0. - pub(crate) document_payload: RawDocumentBuf, +#[derive(Debug, Clone)] +#[cfg_attr(feature = "fuzzing", derive(Arbitrary))] +pub struct Message { + /// OP_MSG flags + pub flags: MessageFlags, - /// OP_MSG payload type 1. - pub(crate) document_sequences: Vec, + /// OP_MSG payload type 0 (the main document) + #[cfg_attr(feature = "fuzzing", arbitrary(with = generate_raw_document))] + pub document_payload: RawDocumentBuf, - pub(crate) response_to: i32, + /// OP_MSG payload type 1 (document sequences) + #[cfg_attr(feature = "fuzzing", arbitrary(with = generate_document_sequences))] + pub document_sequences: Vec, - pub(crate) flags: MessageFlags, + /// Optional CRC32C checksum + pub checksum: Option, - pub(crate) checksum: Option, + /// Request ID for the message + pub request_id: Option, - pub(crate) request_id: Option, + /// Response to request ID + pub response_to: i32, - /// Whether the message should be compressed by the driver. + /// Whether the message should be compressed #[cfg(any( feature = "zstd-compression", feature = "zlib-compression", feature = "snappy-compression" ))] - pub(crate) should_compress: bool, + pub should_compress: bool, } #[derive(Clone, Debug)] +#[cfg(feature = "fuzzing")] +pub struct DocumentSequence { + pub identifier: String, + pub documents: Vec, +} + +#[cfg(not(feature = "fuzzing"))] pub(crate) struct DocumentSequence { pub(crate) identifier: String, pub(crate) documents: Vec, @@ -77,7 +119,7 @@ impl TryFrom for Message { Ok(Self { document_payload, - document_sequences: command.document_sequences, + document_sequences: command.document_sequences.into_iter().map(Into::into).collect(), response_to: 0, flags, checksum: None, @@ -92,7 +134,41 @@ impl TryFrom for Message { } } +#[cfg(feature = "fuzzing")] +impl From for Vec { + fn from(seq: FuzzDocumentSequenceImpl) -> Self { + vec![DocumentSequence { + identifier: "documents".to_string(), + documents: seq.0.into_iter().map(Into::into).collect(), + }] + } +} + impl Message { + #[cfg(feature = "fuzzing")] + pub fn read_from_slice(data: &[u8], header: Header) -> Result { + if data.len() < Header::LENGTH { + return Err(ErrorKind::InvalidResponse { + message: format!("Message data too short: {} bytes", data.len()), + } + .into()); + } + let data = &data[Header::LENGTH..]; + if header.op_code == OpCode::Message { + return Self::read_op_common(data, data.len(), &header); + } + Err(Error::new( + ErrorKind::InvalidResponse { + message: format!( + "Invalid op code for fuzzing, expected {} but got {}", + OpCode::Message as u32, + header.op_code as u32 + ), + }, + Option::>::None, + )) + } + /// Gets this message's command as a Document. If serialization fails, returns a document /// containing the error. pub(crate) fn get_command_document(&self) -> Document { @@ -166,6 +242,8 @@ impl Message { mut reader: T, header: &Header, ) -> Result { + use crate::runtime::SyncLittleEndianRead; + let length = Checked::::try_from(header.length)?; let length_remaining = length - Header::LENGTH; let mut buffer = vec![0u8; length_remaining.get()?]; @@ -208,6 +286,7 @@ impl Message { } fn read_op_common(mut reader: &[u8], length_remaining: usize, header: &Header) -> Result { + use crate::runtime::SyncLittleEndianRead; let mut length_remaining = Checked::new(length_remaining); let flags = MessageFlags::from_bits_truncate(reader.read_u32_sync()?); length_remaining -= std::mem::size_of::(); @@ -383,16 +462,10 @@ impl Message { } } -const DEFAULT_MAX_MESSAGE_SIZE_BYTES: i32 = 48 * 1024 * 1024; -bitflags! { - /// Represents the bitwise flags for an OP_MSG as defined in the spec. - pub(crate) struct MessageFlags: u32 { - const CHECKSUM_PRESENT = 0b_0000_0000_0000_0000_0000_0000_0000_0001; - const MORE_TO_COME = 0b_0000_0000_0000_0000_0000_0000_0000_0010; - const EXHAUST_ALLOWED = 0b_0000_0000_0000_0001_0000_0000_0000_0000; - } -} + + + /// Represents a section as defined by the OP_MSG spec. #[derive(Debug)] @@ -403,7 +476,8 @@ enum MessageSection { impl MessageSection { /// Reads bytes from `reader` and deserializes them into a MessageSection. - fn read(reader: &mut R) -> Result { + fn read(reader: &mut SyncCountReader) -> Result { + use crate::runtime::SyncLittleEndianRead; let payload_type = reader.read_u8_sync()?; if payload_type == 0 { diff --git a/src/fuzz/message_flags.rs b/src/fuzz/message_flags.rs new file mode 100644 index 000000000..d757f528b --- /dev/null +++ b/src/fuzz/message_flags.rs @@ -0,0 +1,17 @@ +use arbitrary::Arbitrary; + +bitflags::bitflags! { + pub struct MessageFlags: u32 { + const NONE = 0; + const CHECKSUM_PRESENT = 0x04; + const MORE_TO_COME = 0x02; + const EXHAUST_ALLOWED = 0x10000; + } +} + +impl<'a> Arbitrary<'a> for MessageFlags { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + let bits = u.arbitrary::()?; + Ok(MessageFlags::from_bits(bits).unwrap_or(MessageFlags::empty())) + } +} diff --git a/src/fuzz/mod.rs b/src/fuzz/mod.rs new file mode 100644 index 000000000..d74bd32dd --- /dev/null +++ b/src/fuzz/mod.rs @@ -0,0 +1,11 @@ +//! Fuzzing support module for MongoDB wire protocol messages + +#[cfg(feature = "fuzzing")] +pub mod message_flags; +#[cfg(feature = "fuzzing")] +pub mod raw_document; + +#[cfg(feature = "fuzzing")] +pub use message_flags::MessageFlags; +#[cfg(feature = "fuzzing")] +pub use raw_document::{FuzzDocumentSequenceImpl, FuzzRawDocumentImpl}; diff --git a/src/fuzz/raw_document.rs b/src/fuzz/raw_document.rs new file mode 100644 index 000000000..1b4128184 --- /dev/null +++ b/src/fuzz/raw_document.rs @@ -0,0 +1,26 @@ +use arbitrary::Arbitrary; +use bson::RawDocumentBuf; + +#[derive(Debug, Arbitrary)] +pub struct FuzzRawDocumentImpl { + bytes: Vec, +} + +impl From for RawDocumentBuf { + fn from(doc: FuzzRawDocumentImpl) -> Self { + RawDocumentBuf::from_bytes(doc.bytes).unwrap_or_else(|_| { + RawDocumentBuf::from_bytes(vec![5, 0, 0, 0, 0]).unwrap() + }) + } +} + +#[derive(Debug, Arbitrary)] +pub struct FuzzDocumentSequenceImpl(pub Vec); + +impl From for Vec { + fn from(seq: FuzzDocumentSequenceImpl) -> Self { + seq.0.into_iter() + .map(Into::into) + .collect() + } +} diff --git a/src/lib.rs b/src/lib.rs index 62f7d1a39..fb5407456 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,8 +26,11 @@ mod bson_util; pub mod change_stream; pub(crate) mod checked; mod client; +#[cfg(feature = "fuzzing")] +pub mod cmap; +#[cfg(not(feature = "fuzzing"))] mod cmap; -mod coll; +pub mod coll; mod collation; mod compression; mod concern; @@ -53,6 +56,8 @@ pub mod sync; mod test; #[cfg(feature = "tracing-unstable")] mod trace; +#[cfg(feature = "fuzzing")] +pub mod fuzz; pub(crate) mod tracking_arc; #[cfg(feature = "in-use-encryption")] From 041093fc69f6c535b362a8bb00e990a6de5ab61a Mon Sep 17 00:00:00 2001 From: Patrick Meredith Date: Fri, 20 Dec 2024 11:29:55 -0500 Subject: [PATCH 02/13] Cleanup Devin's imports --- Cargo.toml | 6 +++--- src/cmap.rs | 1 + src/cmap/conn.rs | 1 + src/cmap/conn/wire.rs | 22 +++++++++++++++------- src/cmap/conn/wire/header.rs | 35 +++++++++++++++++++++++------------ src/cmap/conn/wire/message.rs | 25 ++++++++++++++----------- src/lib.rs | 8 +++++--- 7 files changed, 62 insertions(+), 36 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ab7643dae..27188755b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,14 +67,14 @@ in-use-encryption-unstable = ["in-use-encryption"] # The tracing API is unstable and may have backwards-incompatible changes in minor version updates. # TODO: pending https://github.com/tokio-rs/tracing/issues/2036 stop depending directly on log. tracing-unstable = ["dep:tracing", "dep:log"] -fuzzing = ["dep:bitflags", "dep:arbitrary", "dep:byteorder", "arbitrary/derive"] +fuzzing = ["dep:arbitrary", "arbitrary/derive"] [dependencies] async-trait = "0.1.42" base64 = "0.13.0" -bitflags = { version = "1.1.0", optional = true } +bitflags = { version = "1.1.0" } arbitrary = { version = "1.3", optional = true } -byteorder = { version = "1.4", optional = true } +byteorder = { version = "1.4" } bson = { git = "https://github.com/mongodb/bson-rust", branch = "main", version = "2.11.0" } chrono = { version = "0.4.7", default-features = false, features = [ "clock", diff --git a/src/cmap.rs b/src/cmap.rs index 0f9f16235..8c108ebe1 100644 --- a/src/cmap.rs +++ b/src/cmap.rs @@ -2,6 +2,7 @@ pub(crate) mod test; #[cfg(feature = "fuzzing")] +#[allow(missing_docs)] pub mod conn; #[cfg(not(feature = "fuzzing"))] diff --git a/src/cmap/conn.rs b/src/cmap/conn.rs index f2807b6d8..fb5f76b42 100644 --- a/src/cmap/conn.rs +++ b/src/cmap/conn.rs @@ -3,6 +3,7 @@ pub(crate) mod pooled; mod stream_description; #[cfg(feature = "fuzzing")] +#[allow(missing_docs)] pub mod wire; #[cfg(not(feature = "fuzzing"))] diff --git a/src/cmap/conn/wire.rs b/src/cmap/conn/wire.rs index 71e57a1df..ddb4ea342 100644 --- a/src/cmap/conn/wire.rs +++ b/src/cmap/conn/wire.rs @@ -1,18 +1,26 @@ +#[cfg(feature = "fuzzing")] +#[allow(missing_docs)] pub mod header; +#[cfg(not(feature = "fuzzing"))] +mod header; +#[cfg(feature = "fuzzing")] +#[allow(missing_docs)] pub mod message; +#[cfg(not(feature = "fuzzing"))] +pub(crate) mod message; +#[cfg(feature = "fuzzing")] +#[allow(missing_docs)] pub mod util; +#[cfg(not(feature = "fuzzing"))] +mod util; -pub(crate) use self::{ - util::next_request_id, -}; +pub(crate) use self::util::next_request_id; #[cfg(feature = "fuzzing")] -pub use self::{ - message::Message, -}; +pub use self::message::Message; #[cfg(feature = "fuzzing")] pub use crate::fuzz::message_flags::MessageFlags; #[cfg(not(feature = "fuzzing"))] -pub use self::message::MessageFlags; +pub use message::{Message, MessageFlags}; diff --git a/src/cmap/conn/wire/header.rs b/src/cmap/conn/wire/header.rs index 5c3636588..a0f46f3f8 100644 --- a/src/cmap/conn/wire/header.rs +++ b/src/cmap/conn/wire/header.rs @@ -1,10 +1,12 @@ -use std::io::Cursor; -use byteorder::{ReadBytesExt, LittleEndian}; -use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt}; use crate::error::{ErrorKind, Result}; +use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt}; #[cfg(feature = "fuzzing")] use arbitrary::Arbitrary; +#[cfg(feature = "fuzzing")] +use byteorder::{LittleEndian, ReadBytesExt}; +#[cfg(feature = "fuzzing")] +use std::io::Cursor; /// The wire protocol op codes. #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -63,21 +65,30 @@ impl Header { } let mut cursor = Cursor::new(data); - let length = ReadBytesExt::read_i32::(&mut cursor).map_err(|e| ErrorKind::InvalidResponse { - message: format!("Failed to read length: {}", e), + let length = ReadBytesExt::read_i32::(&mut cursor).map_err(|e| { + ErrorKind::InvalidResponse { + message: format!("Failed to read length: {}", e), + } })?; - let request_id = ReadBytesExt::read_i32::(&mut cursor).map_err(|e| ErrorKind::InvalidResponse { - message: format!("Failed to read request_id: {}", e), + let request_id = ReadBytesExt::read_i32::(&mut cursor).map_err(|e| { + ErrorKind::InvalidResponse { + message: format!("Failed to read request_id: {}", e), + } })?; - let response_to = ReadBytesExt::read_i32::(&mut cursor).map_err(|e| ErrorKind::InvalidResponse { - message: format!("Failed to read response_to: {}", e), + let response_to = ReadBytesExt::read_i32::(&mut cursor).map_err(|e| { + ErrorKind::InvalidResponse { + message: format!("Failed to read response_to: {}", e), + } })?; - let op_code = OpCode::from_i32(ReadBytesExt::read_i32::(&mut cursor).map_err(|e| ErrorKind::InvalidResponse { - message: format!("Failed to read op_code: {}", e), - })?)?; + let op_code = + OpCode::from_i32(ReadBytesExt::read_i32::(&mut cursor).map_err( + |e| ErrorKind::InvalidResponse { + message: format!("Failed to read op_code: {}", e), + }, + )?)?; Ok(Self { length, diff --git a/src/cmap/conn/wire/message.rs b/src/cmap/conn/wire/message.rs index 084c4bf93..ec4ec85ca 100644 --- a/src/cmap/conn/wire/message.rs +++ b/src/cmap/conn/wire/message.rs @@ -12,9 +12,6 @@ use crate::fuzz::{ raw_document::{FuzzDocumentSequenceImpl, FuzzRawDocumentImpl}, }; -#[cfg(not(feature = "fuzzing"))] -use bitflags::bitflags; - #[cfg(not(feature = "fuzzing"))] bitflags::bitflags! { pub struct MessageFlags: u32 { @@ -29,7 +26,11 @@ use crate::{ bson_util, checked::Checked, cmap::conn::{ - wire::{header::Header, header::OpCode, next_request_id, util::SyncCountReader}, + wire::{ + header::{Header, OpCode}, + next_request_id, + util::SyncCountReader, + }, Command, }, compression::decompress::decompress_message, @@ -43,7 +44,9 @@ fn generate_raw_document(u: &mut arbitrary::Unstructured) -> arbitrary::Result arbitrary::Result> { +fn generate_document_sequences( + u: &mut arbitrary::Unstructured, +) -> arbitrary::Result> { let seq = FuzzDocumentSequenceImpl::arbitrary(u)?; Ok(vec![DocumentSequence { identifier: String::arbitrary(u)?, @@ -93,6 +96,7 @@ pub struct DocumentSequence { pub documents: Vec, } +#[derive(Clone, Debug)] #[cfg(not(feature = "fuzzing"))] pub(crate) struct DocumentSequence { pub(crate) identifier: String, @@ -119,7 +123,11 @@ impl TryFrom for Message { Ok(Self { document_payload, - document_sequences: command.document_sequences.into_iter().map(Into::into).collect(), + document_sequences: command + .document_sequences + .into_iter() + .map(Into::into) + .collect(), response_to: 0, flags, checksum: None, @@ -462,11 +470,6 @@ impl Message { } } - - - - - /// Represents a section as defined by the OP_MSG spec. #[derive(Debug)] enum MessageSection { diff --git a/src/lib.rs b/src/lib.rs index fb5407456..6ff6fd675 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,10 +27,11 @@ pub mod change_stream; pub(crate) mod checked; mod client; #[cfg(feature = "fuzzing")] +#[allow(missing_docs)] pub mod cmap; #[cfg(not(feature = "fuzzing"))] mod cmap; -pub mod coll; +mod coll; mod collation; mod compression; mod concern; @@ -38,6 +39,9 @@ mod cursor; mod db; pub mod error; pub mod event; +#[cfg(feature = "fuzzing")] +#[allow(missing_docs)] +pub mod fuzz; pub mod gridfs; mod hello; pub(crate) mod id_set; @@ -56,8 +60,6 @@ pub mod sync; mod test; #[cfg(feature = "tracing-unstable")] mod trace; -#[cfg(feature = "fuzzing")] -pub mod fuzz; pub(crate) mod tracking_arc; #[cfg(feature = "in-use-encryption")] From 91cf2edb20b08c8a87f8b8f2e9e6d194d9cea621 Mon Sep 17 00:00:00 2001 From: Patrick Meredith Date: Fri, 20 Dec 2024 11:32:04 -0500 Subject: [PATCH 03/13] Remove weird pointless file Devin added --- fuzz/fuzz_targets/fuzz_target_1.rs | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 fuzz/fuzz_targets/fuzz_target_1.rs diff --git a/fuzz/fuzz_targets/fuzz_target_1.rs b/fuzz/fuzz_targets/fuzz_target_1.rs deleted file mode 100644 index 43a88c14f..000000000 --- a/fuzz/fuzz_targets/fuzz_target_1.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -use libfuzzer_sys::fuzz_target; - -fuzz_target!(|data: &[u8]| { - // fuzzed code goes here -}); From e2fc68454f11429aa8f29eea43cab4fd1facc43d Mon Sep 17 00:00:00 2001 From: Patrick Meredith Date: Fri, 20 Dec 2024 11:51:45 -0500 Subject: [PATCH 04/13] Make the fuzz test test what we actually care about --- fuzz/fuzz_targets/header_length.rs | 14 ++++---------- src/cmap/conn/wire/header.rs | 2 ++ 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/fuzz/fuzz_targets/header_length.rs b/fuzz/fuzz_targets/header_length.rs index 13754ed09..91ddb4c1a 100644 --- a/fuzz/fuzz_targets/header_length.rs +++ b/fuzz/fuzz_targets/header_length.rs @@ -1,21 +1,15 @@ #![no_main] use libfuzzer_sys::fuzz_target; -use mongodb::cmap::conn::wire::header::Header; +use mongodb::cmap::conn::wire::{header::Header, message::Message}; fuzz_target!(|data: &[u8]| { if data.len() < Header::LENGTH { return; } if let Ok(header) = Header::from_slice(data) { - if header.length < 0 { - panic!("Negative length detected: {}", header.length); - } - if header.length as usize > std::usize::MAX - Header::LENGTH { - panic!("Integer overflow detected in length calculation"); - } - let total_size = Header::LENGTH + header.length as usize; - if total_size > data.len() { - return; + let data = &data[Header::LENGTH..]; + if let Ok(message) = Message::read_from_slice(data, header) { + let _ = message; } } }); diff --git a/src/cmap/conn/wire/header.rs b/src/cmap/conn/wire/header.rs index a0f46f3f8..78f63cdad 100644 --- a/src/cmap/conn/wire/header.rs +++ b/src/cmap/conn/wire/header.rs @@ -51,6 +51,8 @@ impl Header { #[cfg(not(feature = "fuzzing"))] pub(crate) const LENGTH: usize = 4 * std::mem::size_of::(); + // generates a Header from a randomly generated slice of bytes, as long as the slice is at least + // 16 bytes long this is used for fuzzing #[cfg(feature = "fuzzing")] pub fn from_slice(data: &[u8]) -> Result { if data.len() < Self::LENGTH { From 6d92b56374ef7a15044321c826ad655893a0f3a7 Mon Sep 17 00:00:00 2001 From: Patrick Meredith Date: Fri, 20 Dec 2024 12:10:51 -0500 Subject: [PATCH 05/13] Make the fuzz test better, still relying on infra setup by Devin at least --- fuzz/fuzz_targets/header_length.rs | 19 ++++++++++++++++--- src/cmap/conn/wire/message.rs | 3 +++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/fuzz/fuzz_targets/header_length.rs b/fuzz/fuzz_targets/header_length.rs index 91ddb4c1a..f77c6cb92 100644 --- a/fuzz/fuzz_targets/header_length.rs +++ b/fuzz/fuzz_targets/header_length.rs @@ -1,13 +1,26 @@ #![no_main] use libfuzzer_sys::fuzz_target; -use mongodb::cmap::conn::wire::{header::Header, message::Message}; +use mongodb::cmap::conn::wire::{ + header::{Header, OpCode}, + message::Message, +}; fuzz_target!(|data: &[u8]| { if data.len() < Header::LENGTH { return; } - if let Ok(header) = Header::from_slice(data) { - let data = &data[Header::LENGTH..]; + if let Ok(mut header) = Header::from_slice(data) { + // read_from_slice will adjust the data for the header length, this first check will + // almost always have length mismatches, but length mismatches are a possible attack + // vector. + // This will also often have the wrong opcode, but that is also a possible attack vector. + if let Ok(message) = Message::read_from_slice(data, header.clone()) { + let _ = message; + } + // try again with the header.length set to the data length and the header.opcode == + // OpCode::Message to catch other attack vectors. + header.length = data.len() as i32 - Header::LENGTH as i32; + header.op_code = OpCode::Message; if let Ok(message) = Message::read_from_slice(data, header) { let _ = message; } diff --git a/src/cmap/conn/wire/message.rs b/src/cmap/conn/wire/message.rs index ec4ec85ca..cf14f7a79 100644 --- a/src/cmap/conn/wire/message.rs +++ b/src/cmap/conn/wire/message.rs @@ -155,6 +155,9 @@ impl From for Vec { impl Message { #[cfg(feature = "fuzzing")] pub fn read_from_slice(data: &[u8], header: Header) -> Result { + // this case should not actually be hit during fuzzing since we create the Header + // from the data first, but this is a good sanity check, ensuring that we will + // not panic if we do hit this case. if data.len() < Header::LENGTH { return Err(ErrorKind::InvalidResponse { message: format!("Message data too short: {} bytes", data.len()), From 72407e17f2291f457cc69808b49f0510e73729fc Mon Sep 17 00:00:00 2001 From: Patrick Meredith Date: Fri, 20 Dec 2024 12:51:36 -0500 Subject: [PATCH 06/13] Fix bug found by fuzz test --- src/bson_util.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/bson_util.rs b/src/bson_util.rs index 4a5aeae9e..f0e3e33d0 100644 --- a/src/bson_util.rs +++ b/src/bson_util.rs @@ -160,7 +160,8 @@ fn num_decimal_digits(mut n: usize) -> usize { pub(crate) fn read_document_bytes(mut reader: R) -> Result> { let length = reader.read_i32_sync()?; - let mut bytes = Vec::with_capacity(length as usize); + let mut bytes = Vec::with_capacity(Checked::new(length).try_into()?); + // this is safe because the Checked::try_into would fail above if length was negative bytes.write_all(&length.to_le_bytes())?; reader.take(length as u64 - 4).read_to_end(&mut bytes)?; From 421ef814ff9b78fdafdf94b98d07ae1195fbbb0b Mon Sep 17 00:00:00 2001 From: Patrick Meredith Date: Fri, 20 Dec 2024 13:21:38 -0500 Subject: [PATCH 07/13] Fix bug found by fuzz test in totality! --- src/bson_util.rs | 11 ++++++----- src/client/options/parse.rs | 1 + src/gridfs/download.rs | 1 + src/sdam/description/topology.rs | 1 + 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/bson_util.rs b/src/bson_util.rs index f0e3e33d0..d15d2b670 100644 --- a/src/bson_util.rs +++ b/src/bson_util.rs @@ -158,13 +158,14 @@ fn num_decimal_digits(mut n: usize) -> usize { /// Read a document's raw BSON bytes from the provided reader. pub(crate) fn read_document_bytes(mut reader: R) -> Result> { - let length = reader.read_i32_sync()?; + let length = Checked::new(reader.read_i32_sync()?); - let mut bytes = Vec::with_capacity(Checked::new(length).try_into()?); - // this is safe because the Checked::try_into would fail above if length was negative - bytes.write_all(&length.to_le_bytes())?; + let mut bytes = Vec::with_capacity(length.try_into()?); + bytes.write_all(&length.try_into::()?.to_le_bytes())?; - reader.take(length as u64 - 4).read_to_end(&mut bytes)?; + reader + .take((length - 4).try_into()?) + .read_to_end(&mut bytes)?; Ok(bytes) } diff --git a/src/client/options/parse.rs b/src/client/options/parse.rs index 8aed36ae4..aba7b21e4 100644 --- a/src/client/options/parse.rs +++ b/src/client/options/parse.rs @@ -80,6 +80,7 @@ impl Action for ParseConnectionString { "srvMaxHosts and loadBalanced=true cannot both be present", )); } + // max is u32, so this is safe. config.hosts = crate::sdam::choose_n(&config.hosts, max as usize) .cloned() .collect(); diff --git a/src/gridfs/download.rs b/src/gridfs/download.rs index 7569538b2..50251bedd 100644 --- a/src/gridfs/download.rs +++ b/src/gridfs/download.rs @@ -201,6 +201,7 @@ async fn get_bytes( let expected_len = FilesCollectionDocument::expected_chunk_length_from_vals(file_len, chunk_size_bytes, n); + // expected_len is u32, so we can safely cast it to usize if chunk_bytes.len() != (expected_len as usize) { return Err(ErrorKind::GridFs(GridFsErrorKind::WrongSizeChunk { actual_size: chunk_bytes.len(), diff --git a/src/sdam/description/topology.rs b/src/sdam/description/topology.rs index 23ea188ef..44b4334f5 100644 --- a/src/sdam/description/topology.rs +++ b/src/sdam/description/topology.rs @@ -391,6 +391,7 @@ impl TopologyDescription { } } if let Some(max) = self.srv_max_hosts { + // max is u32, so this is safe. let max = max as usize; if max > 0 && max < self.servers.len() + new.len() { new = choose_n(&new, max.saturating_sub(self.servers.len())) From a9acfdb329970ca292105798828b0bd29e193c3c Mon Sep 17 00:00:00 2001 From: Patrick Meredith Date: Fri, 20 Dec 2024 14:11:41 -0500 Subject: [PATCH 08/13] Add fuzzer to CI. Devin could do this, but I just did it myself because of patching annoyances --- .evergreen/config.yml | 22 ++++++++++++++++++++++ .evergreen/install-fuzzer.sh | 7 +++++++ .evergreen/run-fuzzer.sh | 21 +++++++++++++++++++++ Cargo.toml | 4 ++-- 4 files changed, 52 insertions(+), 2 deletions(-) create mode 100755 .evergreen/install-fuzzer.sh create mode 100755 .evergreen/run-fuzzer.sh diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 352e674f9..69cb6f297 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -66,6 +66,13 @@ buildvariants: tasks: - name: .lint + - name: fuzzer + display_name: "Fuzzer" + run_on: + - ubuntu2204-large + tasks: + "run-fuzzer" + - name: rhel-8 display_name: "RHEL 8" run_on: @@ -777,6 +784,10 @@ tasks: commands: - func: "check rustdoc" + - name: "run-fuzzer" + commands: + - func: "run fuzzer" + # Driver test suite runs for the full set of versions and topologies are in # suite-tasks.yml, generated by .evergreen/generate_tasks. @@ -1264,6 +1275,17 @@ functions: - AWS_SECRET_ACCESS_KEY - AWS_SESSION_TOKEN + "run fuzzer": + - command: shell.exec + type: test + params: + shell: bash + working_dir: src + script: | + ${PREPARE_SHELL} + .evergreen/install-fuzzer.sh + .evergreen/run-fuzzer.sh + "run aws auth test with regular aws credentials": - command: subprocess.exec type: test diff --git a/.evergreen/install-fuzzer.sh b/.evergreen/install-fuzzer.sh new file mode 100755 index 000000000..970e13afd --- /dev/null +++ b/.evergreen/install-fuzzer.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +set -o errexit + +. ~/.cargo/env + +cargo install cargo-fuzz diff --git a/.evergreen/run-fuzzer.sh b/.evergreen/run-fuzzer.sh new file mode 100755 index 000000000..83e49cf6d --- /dev/null +++ b/.evergreen/run-fuzzer.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +set -o errexit + +. ~/.cargo/env + +mkdir -p artifacts + +# Function to run fuzzer and collect crashes +run_fuzzer() { + target=$1 + echo "Running fuzzer for $target" + # Run fuzzer and redirect crashes to artifacts directory + RUST_BACKTRACE=1 cargo +nightly fuzz run $target -- \ + -rss_limit_mb=4096 \ + -max_total_time=360 \ + -artifact_prefix=artifacts/ \ + -print_final_stats=1 +} + +run_fuzzer header_length diff --git a/Cargo.toml b/Cargo.toml index 27188755b..5e412e2c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,14 +67,14 @@ in-use-encryption-unstable = ["in-use-encryption"] # The tracing API is unstable and may have backwards-incompatible changes in minor version updates. # TODO: pending https://github.com/tokio-rs/tracing/issues/2036 stop depending directly on log. tracing-unstable = ["dep:tracing", "dep:log"] -fuzzing = ["dep:arbitrary", "arbitrary/derive"] +fuzzing = ["dep:arbitrary", "arbitrary/derive", "dep:byteorder"] [dependencies] async-trait = "0.1.42" base64 = "0.13.0" bitflags = { version = "1.1.0" } arbitrary = { version = "1.3", optional = true } -byteorder = { version = "1.4" } +byteorder = { version = "1.4", optional = true } bson = { git = "https://github.com/mongodb/bson-rust", branch = "main", version = "2.11.0" } chrono = { version = "0.4.7", default-features = false, features = [ "clock", From f98c827d7e9f637a03c5ed884ea987c3c00411da Mon Sep 17 00:00:00 2001 From: Patrick Meredith Date: Sat, 21 Dec 2024 15:00:15 -0500 Subject: [PATCH 09/13] Fix bizzare change for MessageFlags from binary to hex introduced by Devin. Weird --- src/cmap/conn/wire/message.rs | 14 ++++++++++---- src/fuzz/message_flags.rs | 7 +++---- src/fuzz/raw_document.rs | 9 +++------ 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/cmap/conn/wire/message.rs b/src/cmap/conn/wire/message.rs index cf14f7a79..772536606 100644 --- a/src/cmap/conn/wire/message.rs +++ b/src/cmap/conn/wire/message.rs @@ -3,6 +3,13 @@ use std::io::Read; use bson::{doc, Array, Document, RawDocumentBuf}; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; +#[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" +))] +use crate::options::Compressor; + #[cfg(feature = "fuzzing")] use arbitrary::Arbitrary; @@ -15,10 +22,9 @@ use crate::fuzz::{ #[cfg(not(feature = "fuzzing"))] bitflags::bitflags! { pub struct MessageFlags: u32 { - const NONE = 0; - const CHECKSUM_PRESENT = 0x04; - const MORE_TO_COME = 0x02; - const EXHAUST_ALLOWED = 0x10000; + const CHECKSUM_PRESENT = 0b_0000_0000_0000_0000_0000_0000_0000_0001; + const MORE_TO_COME = 0b_0000_0000_0000_0000_0000_0000_0000_0010; + const EXHAUST_ALLOWED = 0b_0000_0000_0000_0001_0000_0000_0000_0000; } } diff --git a/src/fuzz/message_flags.rs b/src/fuzz/message_flags.rs index d757f528b..62f0e85da 100644 --- a/src/fuzz/message_flags.rs +++ b/src/fuzz/message_flags.rs @@ -2,10 +2,9 @@ use arbitrary::Arbitrary; bitflags::bitflags! { pub struct MessageFlags: u32 { - const NONE = 0; - const CHECKSUM_PRESENT = 0x04; - const MORE_TO_COME = 0x02; - const EXHAUST_ALLOWED = 0x10000; + const CHECKSUM_PRESENT = 0b_0000_0000_0000_0000_0000_0000_0000_0001; + const MORE_TO_COME = 0b_0000_0000_0000_0000_0000_0000_0000_0010; + const EXHAUST_ALLOWED = 0b_0000_0000_0000_0001_0000_0000_0000_0000; } } diff --git a/src/fuzz/raw_document.rs b/src/fuzz/raw_document.rs index 1b4128184..ee5f3e148 100644 --- a/src/fuzz/raw_document.rs +++ b/src/fuzz/raw_document.rs @@ -8,9 +8,8 @@ pub struct FuzzRawDocumentImpl { impl From for RawDocumentBuf { fn from(doc: FuzzRawDocumentImpl) -> Self { - RawDocumentBuf::from_bytes(doc.bytes).unwrap_or_else(|_| { - RawDocumentBuf::from_bytes(vec![5, 0, 0, 0, 0]).unwrap() - }) + RawDocumentBuf::from_bytes(doc.bytes) + .unwrap_or_else(|_| RawDocumentBuf::from_bytes(vec![5, 0, 0, 0, 0]).unwrap()) } } @@ -19,8 +18,6 @@ pub struct FuzzDocumentSequenceImpl(pub Vec); impl From for Vec { fn from(seq: FuzzDocumentSequenceImpl) -> Self { - seq.0.into_iter() - .map(Into::into) - .collect() + seq.0.into_iter().map(Into::into).collect() } } From 82b81838dc930d98bf70a6e64f95e679b3ace0c6 Mon Sep 17 00:00:00 2001 From: Patrick Meredith Date: Sat, 21 Dec 2024 15:06:38 -0500 Subject: [PATCH 10/13] Fix install-fuzzer --- .evergreen/install-fuzzer.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.evergreen/install-fuzzer.sh b/.evergreen/install-fuzzer.sh index 970e13afd..a9b6df30a 100755 --- a/.evergreen/install-fuzzer.sh +++ b/.evergreen/install-fuzzer.sh @@ -2,6 +2,6 @@ set -o errexit -. ~/.cargo/env +source ./.evergreen/env.sh cargo install cargo-fuzz From eac43d421439d19bed59403ee1e867aef6e2a226 Mon Sep 17 00:00:00 2001 From: Patrick Meredith Date: Sat, 21 Dec 2024 15:17:55 -0500 Subject: [PATCH 11/13] Fix run-fuzzer in the exact same way, heh --- .evergreen/run-fuzzer.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.evergreen/run-fuzzer.sh b/.evergreen/run-fuzzer.sh index 83e49cf6d..2062811d3 100755 --- a/.evergreen/run-fuzzer.sh +++ b/.evergreen/run-fuzzer.sh @@ -2,7 +2,7 @@ set -o errexit -. ~/.cargo/env +source ./.evergreen/env.sh mkdir -p artifacts From 2a128de5b88b71593c883b7a93363611b020a6ca Mon Sep 17 00:00:00 2001 From: Patrick Meredith Date: Sat, 21 Dec 2024 15:49:15 -0500 Subject: [PATCH 12/13] Fix public visibility that Devin added for no reason --- src/cmap/conn/wire/message.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/cmap/conn/wire/message.rs b/src/cmap/conn/wire/message.rs index 772536606..79d1949f6 100644 --- a/src/cmap/conn/wire/message.rs +++ b/src/cmap/conn/wire/message.rs @@ -71,17 +71,17 @@ pub struct Message { /// OP_MSG payload type 0 (the main document) #[cfg_attr(feature = "fuzzing", arbitrary(with = generate_raw_document))] - pub document_payload: RawDocumentBuf, + pub(crate) document_payload: RawDocumentBuf, /// OP_MSG payload type 1 (document sequences) #[cfg_attr(feature = "fuzzing", arbitrary(with = generate_document_sequences))] - pub document_sequences: Vec, + pub(crate) document_sequences: Vec, /// Optional CRC32C checksum - pub checksum: Option, + pub(crate) checksum: Option, /// Request ID for the message - pub request_id: Option, + pub(crate) request_id: Option, /// Response to request ID pub response_to: i32, @@ -92,7 +92,7 @@ pub struct Message { feature = "zlib-compression", feature = "snappy-compression" ))] - pub should_compress: bool, + pub(crate) should_compress: bool, } #[derive(Clone, Debug)] From 9cafae28bce8611368db9f55f5b96171ae53d584 Mon Sep 17 00:00:00 2001 From: Patrick Meredith Date: Mon, 30 Dec 2024 18:09:30 -0500 Subject: [PATCH 13/13] Fix accidental private api leaking --- src/cmap/conn/wire.rs | 2 +- src/cmap/conn/wire/header.rs | 22 +++++++++++++++++++++ src/cmap/conn/wire/message.rs | 36 ++++++++++++++++++++++++++++++++++- 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/src/cmap/conn/wire.rs b/src/cmap/conn/wire.rs index ddb4ea342..24d4cc643 100644 --- a/src/cmap/conn/wire.rs +++ b/src/cmap/conn/wire.rs @@ -23,4 +23,4 @@ pub use self::message::Message; pub use crate::fuzz::message_flags::MessageFlags; #[cfg(not(feature = "fuzzing"))] -pub use message::{Message, MessageFlags}; +pub(crate) use message::{Message, MessageFlags}; diff --git a/src/cmap/conn/wire/header.rs b/src/cmap/conn/wire/header.rs index 78f63cdad..721d3bdaf 100644 --- a/src/cmap/conn/wire/header.rs +++ b/src/cmap/conn/wire/header.rs @@ -10,6 +10,7 @@ use std::io::Cursor; /// The wire protocol op codes. #[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[cfg(feature = "fuzzing")] #[cfg_attr(feature = "fuzzing", derive(Arbitrary))] pub enum OpCode { Reply = 1, @@ -18,6 +19,16 @@ pub enum OpCode { Compressed = 2012, } +/// The wire protocol op codes. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[cfg(not(feature = "fuzzing"))] +pub(crate) enum OpCode { + Reply = 1, + Query = 2004, + Message = 2013, + Compressed = 2012, +} + impl OpCode { /// Attempt to infer the op code based on the numeric value. pub fn from_i32(i: i32) -> Result { @@ -36,6 +47,7 @@ impl OpCode { /// The header for any wire protocol message. #[derive(Debug, Clone)] +#[cfg(feature = "fuzzing")] #[cfg_attr(feature = "fuzzing", derive(Arbitrary))] pub struct Header { pub length: i32, @@ -44,6 +56,16 @@ pub struct Header { pub op_code: OpCode, } +/// The header for any wire protocol message. +#[derive(Debug, Clone)] +#[cfg(not(feature = "fuzzing"))] +pub(crate) struct Header { + pub length: i32, + pub request_id: i32, + pub response_to: i32, + pub op_code: OpCode, +} + impl Header { #[cfg(feature = "fuzzing")] pub const LENGTH: usize = 4 * std::mem::size_of::(); diff --git a/src/cmap/conn/wire/message.rs b/src/cmap/conn/wire/message.rs index 79d1949f6..d6f125449 100644 --- a/src/cmap/conn/wire/message.rs +++ b/src/cmap/conn/wire/message.rs @@ -21,7 +21,7 @@ use crate::fuzz::{ #[cfg(not(feature = "fuzzing"))] bitflags::bitflags! { - pub struct MessageFlags: u32 { + pub(crate) struct MessageFlags: u32 { const CHECKSUM_PRESENT = 0b_0000_0000_0000_0000_0000_0000_0000_0001; const MORE_TO_COME = 0b_0000_0000_0000_0000_0000_0000_0000_0010; const EXHAUST_ALLOWED = 0b_0000_0000_0000_0001_0000_0000_0000_0000; @@ -64,6 +64,7 @@ const DEFAULT_MAX_MESSAGE_SIZE_BYTES: i32 = 48 * 1024 * 1024; /// Represents an OP_MSG wire protocol operation. #[derive(Debug, Clone)] +#[cfg(feature = "fuzzing")] #[cfg_attr(feature = "fuzzing", derive(Arbitrary))] pub struct Message { /// OP_MSG flags @@ -95,6 +96,39 @@ pub struct Message { pub(crate) should_compress: bool, } +/// Represents an OP_MSG wire protocol operation. +#[derive(Debug, Clone)] +#[cfg(not(feature = "fuzzing"))] +pub(crate) struct Message { + /// OP_MSG flags + pub flags: MessageFlags, + + /// OP_MSG payload type 0 (the main document) + #[cfg_attr(feature = "fuzzing", arbitrary(with = generate_raw_document))] + pub(crate) document_payload: RawDocumentBuf, + + /// OP_MSG payload type 1 (document sequences) + #[cfg_attr(feature = "fuzzing", arbitrary(with = generate_document_sequences))] + pub(crate) document_sequences: Vec, + + /// Optional CRC32C checksum + pub(crate) checksum: Option, + + /// Request ID for the message + pub(crate) request_id: Option, + + /// Response to request ID + pub response_to: i32, + + /// Whether the message should be compressed + #[cfg(any( + feature = "zstd-compression", + feature = "zlib-compression", + feature = "snappy-compression" + ))] + pub(crate) should_compress: bool, +} + #[derive(Clone, Debug)] #[cfg(feature = "fuzzing")] pub struct DocumentSequence {