- Add VfsFile: Send supertrait for Mutex compatibility - Fix SmbServerCommand: struct → Subcommand enum with Start variant - Fix tracing_subscriber::init() → try_init() to avoid panic when logger already initialized - Fix CLI subcommand name: smb-server → smb-start (flatten naming) - Add #[command(name = "smb-start")] for CLI disambiguation - Fix unused variable warnings (smb_fs.rs, smb_server_backend.rs) - Remove unused VfsFile imports (webdav.rs, scp_handler.rs) - Integration test: Docker smbclient verified (list, upload, read)
186 lines
8.3 KiB
Rust
186 lines
8.3 KiB
Rust
//! Fuzzing entry points for `fuzz/` targets.
|
|
//!
|
|
//! This module is feature-gated behind `fuzzing` and only exists to give
|
|
//! `cargo-fuzz` targets stable, public access to otherwise-internal parse
|
|
//! functions. Applications must not depend on it -- it's unstable by
|
|
//! design, and enabling the feature pulls in nothing of runtime value.
|
|
//!
|
|
//! Every function here takes untrusted bytes and returns either a parsed
|
|
//! value or a clean typed error. No function here is allowed to panic on
|
|
//! bad input; that's what the fuzzer tests.
|
|
//!
|
|
//! Targets (see `fuzz/fuzz_targets/`):
|
|
//!
|
|
//! - [`fuzz_header_parse`] -- SMB2 header (`msg::header::Header`).
|
|
//! - [`fuzz_transform_header_parse`] -- encryption transform header.
|
|
//! - [`fuzz_compression_transform_header_parse`] -- compression wrapper.
|
|
//! - [`fuzz_compound_split`] -- `client::connection::split_compound`.
|
|
//! - [`fuzz_frame_parse`] -- compound split + per-sub-frame header parse,
|
|
//! which is the real receiver-loop path up to the body.
|
|
//! - [`fuzz_sub_frame_parse`] -- header + body (dispatched by `Command`).
|
|
//! - [`fuzz_negotiate_request_parse`] / [`fuzz_negotiate_response_parse`]
|
|
//! - [`fuzz_create_request_parse`] / [`fuzz_create_response_parse`]
|
|
//! -- CreateContext list lives inside these bodies.
|
|
//! - [`fuzz_query_info_response_parse`] -- opaque output buffer sharp edge.
|
|
//! - [`fuzz_dfs_referral_response_parse`] -- manual offset arithmetic,
|
|
//! obvious fuzzing target.
|
|
|
|
use crate::msg::header::Header;
|
|
use crate::msg::transform::{CompressionTransformHeader, TransformHeader};
|
|
use crate::pack::{ReadCursor, Unpack};
|
|
use crate::types::Command;
|
|
|
|
/// Fuzz the top-level SMB2 header parser.
|
|
pub fn fuzz_header_parse(data: &[u8]) {
|
|
let mut cursor = ReadCursor::new(data);
|
|
let _ = Header::unpack(&mut cursor);
|
|
}
|
|
|
|
/// Fuzz the encryption transform header parser.
|
|
pub fn fuzz_transform_header_parse(data: &[u8]) {
|
|
let mut cursor = ReadCursor::new(data);
|
|
let _ = TransformHeader::unpack(&mut cursor);
|
|
}
|
|
|
|
/// Fuzz the compression transform header parser.
|
|
pub fn fuzz_compression_transform_header_parse(data: &[u8]) {
|
|
let mut cursor = ReadCursor::new(data);
|
|
let _ = CompressionTransformHeader::unpack(&mut cursor);
|
|
}
|
|
|
|
/// Fuzz the compound-frame splitter. Takes a preprocessed (already decrypted
|
|
/// and decompressed) buffer and returns the sub-frame byte slices.
|
|
pub fn fuzz_compound_split(data: &[u8]) {
|
|
let _ = crate::client::connection::split_compound(data);
|
|
}
|
|
|
|
/// Fuzz the full receiver-loop parse path: compound split, plus parsing the
|
|
/// header of every sub-frame. Mirrors what `prepare_sub_frame` does before
|
|
/// it dispatches on `Command`.
|
|
pub fn fuzz_frame_parse(data: &[u8]) {
|
|
let subs = match crate::client::connection::split_compound(data) {
|
|
Ok(s) => s,
|
|
Err(_) => return,
|
|
};
|
|
for sub in subs {
|
|
let mut cursor = ReadCursor::new(&sub);
|
|
let _ = Header::unpack(&mut cursor);
|
|
}
|
|
}
|
|
|
|
/// Fuzz header + body (dispatched by `Command`). Much wider surface than
|
|
/// [`fuzz_frame_parse`] because it actually parses the response body for
|
|
/// every command type.
|
|
pub fn fuzz_sub_frame_parse(data: &[u8]) {
|
|
if data.len() < Header::SIZE {
|
|
return;
|
|
}
|
|
let mut cursor = ReadCursor::new(data);
|
|
let header = match Header::unpack(&mut cursor) {
|
|
Ok(h) => h,
|
|
Err(_) => return,
|
|
};
|
|
|
|
let body = &data[Header::SIZE..];
|
|
let is_response = header.is_response();
|
|
dispatch_body(header.command, is_response, body);
|
|
}
|
|
|
|
fn dispatch_body(command: Command, is_response: bool, body: &[u8]) {
|
|
use crate::msg;
|
|
|
|
// Unpack the given type from `body` and discard the result. Parse errors
|
|
// are fine (boring path); panics / UB are what libfuzzer catches.
|
|
macro_rules! try_unpack {
|
|
($ty:ty) => {{
|
|
let mut cursor = ReadCursor::new(body);
|
|
let _ = <$ty as Unpack>::unpack(&mut cursor);
|
|
}};
|
|
}
|
|
|
|
match (command, is_response) {
|
|
(Command::Negotiate, false) => try_unpack!(msg::negotiate::NegotiateRequest),
|
|
(Command::Negotiate, true) => try_unpack!(msg::negotiate::NegotiateResponse),
|
|
(Command::SessionSetup, false) => try_unpack!(msg::session_setup::SessionSetupRequest),
|
|
(Command::SessionSetup, true) => try_unpack!(msg::session_setup::SessionSetupResponse),
|
|
(Command::Logoff, false) => try_unpack!(msg::logoff::LogoffRequest),
|
|
(Command::Logoff, true) => try_unpack!(msg::logoff::LogoffResponse),
|
|
(Command::TreeConnect, false) => try_unpack!(msg::tree_connect::TreeConnectRequest),
|
|
(Command::TreeConnect, true) => try_unpack!(msg::tree_connect::TreeConnectResponse),
|
|
(Command::TreeDisconnect, false) => {
|
|
try_unpack!(msg::tree_disconnect::TreeDisconnectRequest)
|
|
}
|
|
(Command::TreeDisconnect, true) => {
|
|
try_unpack!(msg::tree_disconnect::TreeDisconnectResponse)
|
|
}
|
|
(Command::Create, false) => try_unpack!(msg::create::CreateRequest),
|
|
(Command::Create, true) => try_unpack!(msg::create::CreateResponse),
|
|
(Command::Close, false) => try_unpack!(msg::close::CloseRequest),
|
|
(Command::Close, true) => try_unpack!(msg::close::CloseResponse),
|
|
(Command::Flush, false) => try_unpack!(msg::flush::FlushRequest),
|
|
(Command::Flush, true) => try_unpack!(msg::flush::FlushResponse),
|
|
(Command::Read, false) => try_unpack!(msg::read::ReadRequest),
|
|
(Command::Read, true) => try_unpack!(msg::read::ReadResponse),
|
|
(Command::Write, false) => try_unpack!(msg::write::WriteRequest),
|
|
(Command::Write, true) => try_unpack!(msg::write::WriteResponse),
|
|
(Command::Lock, false) => try_unpack!(msg::lock::LockRequest),
|
|
(Command::Lock, true) => try_unpack!(msg::lock::LockResponse),
|
|
(Command::Ioctl, false) => try_unpack!(msg::ioctl::IoctlRequest),
|
|
(Command::Ioctl, true) => try_unpack!(msg::ioctl::IoctlResponse),
|
|
(Command::Cancel, false) => try_unpack!(msg::cancel::CancelRequest),
|
|
(Command::Echo, false) => try_unpack!(msg::echo::EchoRequest),
|
|
(Command::Echo, true) => try_unpack!(msg::echo::EchoResponse),
|
|
(Command::QueryDirectory, false) => {
|
|
try_unpack!(msg::query_directory::QueryDirectoryRequest)
|
|
}
|
|
(Command::QueryDirectory, true) => {
|
|
try_unpack!(msg::query_directory::QueryDirectoryResponse)
|
|
}
|
|
(Command::ChangeNotify, false) => try_unpack!(msg::change_notify::ChangeNotifyRequest),
|
|
(Command::ChangeNotify, true) => try_unpack!(msg::change_notify::ChangeNotifyResponse),
|
|
(Command::QueryInfo, false) => try_unpack!(msg::query_info::QueryInfoRequest),
|
|
(Command::QueryInfo, true) => try_unpack!(msg::query_info::QueryInfoResponse),
|
|
(Command::SetInfo, false) => try_unpack!(msg::set_info::SetInfoRequest),
|
|
(Command::SetInfo, true) => try_unpack!(msg::set_info::SetInfoResponse),
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
/// Fuzz `NegotiateRequest::unpack` directly.
|
|
pub fn fuzz_negotiate_request_parse(data: &[u8]) {
|
|
let mut cursor = ReadCursor::new(data);
|
|
let _ = crate::msg::negotiate::NegotiateRequest::unpack(&mut cursor);
|
|
}
|
|
|
|
/// Fuzz `NegotiateResponse::unpack` directly. Covers negotiate-context parsing.
|
|
pub fn fuzz_negotiate_response_parse(data: &[u8]) {
|
|
let mut cursor = ReadCursor::new(data);
|
|
let _ = crate::msg::negotiate::NegotiateResponse::unpack(&mut cursor);
|
|
}
|
|
|
|
/// Fuzz `CreateRequest::unpack` directly. Covers create-context list parsing.
|
|
pub fn fuzz_create_request_parse(data: &[u8]) {
|
|
let mut cursor = ReadCursor::new(data);
|
|
let _ = crate::msg::create::CreateRequest::unpack(&mut cursor);
|
|
}
|
|
|
|
/// Fuzz `CreateResponse::unpack` directly.
|
|
pub fn fuzz_create_response_parse(data: &[u8]) {
|
|
let mut cursor = ReadCursor::new(data);
|
|
let _ = crate::msg::create::CreateResponse::unpack(&mut cursor);
|
|
}
|
|
|
|
/// Fuzz `QueryInfoResponse::unpack`, which has the tricky
|
|
/// output-buffer-offset-from-header arithmetic.
|
|
pub fn fuzz_query_info_response_parse(data: &[u8]) {
|
|
let mut cursor = ReadCursor::new(data);
|
|
let _ = crate::msg::query_info::QueryInfoResponse::unpack(&mut cursor);
|
|
}
|
|
|
|
/// Fuzz the DFS referral response parser. Manual offset arithmetic makes
|
|
/// this a classic sharp-edge target.
|
|
pub fn fuzz_dfs_referral_response_parse(data: &[u8]) {
|
|
let mut cursor = ReadCursor::new(data);
|
|
let _ = crate::msg::dfs::RespGetDfsReferral::unpack(&mut cursor);
|
|
}
|