mirror of
https://github.com/sol1/rustguac.git
synced 2026-09-11 01:55:38 +00:00
Add fuzz testing infrastructure and fix UTF-8 boundary panic
Add cargo-fuzz targets for the Guacamole protocol parser:
- protocol_parse: single instruction parsing
- protocol_stream: streaming parser with chunked input
Fix panic in Instruction::parse when a length prefix splits a multi-byte
UTF-8 character (found by fuzzer within seconds). Now returns
ParseError::Truncated instead of panicking on invalid char boundary.
Run with: cargo +nightly fuzz run protocol_parse
cargo +nightly fuzz run protocol_stream
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
target
|
||||
artifacts
|
||||
coverage
|
||||
corpus
|
||||
@@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "rustguac-fuzz"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.4"
|
||||
|
||||
[dependencies.rustguac]
|
||||
path = ".."
|
||||
|
||||
[[bin]]
|
||||
name = "protocol_parse"
|
||||
path = "fuzz_targets/protocol_parse.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
[[bin]]
|
||||
name = "protocol_stream"
|
||||
path = "fuzz_targets/protocol_stream.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
@@ -0,0 +1,10 @@
|
||||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use rustguac::protocol::Instruction;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
if let Ok(s) = std::str::from_utf8(data) {
|
||||
// Fuzz single-instruction parsing
|
||||
let _ = Instruction::parse(s);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use rustguac::protocol::InstructionParser;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
if let Ok(s) = std::str::from_utf8(data) {
|
||||
// Fuzz the streaming parser: split input into random-sized chunks
|
||||
// to exercise buffer accumulation and boundary handling
|
||||
let mut parser = InstructionParser::new();
|
||||
let bytes = s.as_bytes();
|
||||
let mut pos = 0;
|
||||
let mut chunk_size = 1;
|
||||
while pos < bytes.len() {
|
||||
let end = (pos + chunk_size).min(bytes.len());
|
||||
if let Ok(chunk) = std::str::from_utf8(&bytes[pos..end]) {
|
||||
for result in parser.receive(chunk) {
|
||||
// Exercise encode on successfully parsed instructions
|
||||
if let Ok(inst) = result {
|
||||
let _ = inst.encode();
|
||||
}
|
||||
}
|
||||
}
|
||||
pos = end;
|
||||
// Vary chunk sizes: 1, 2, 4, 8, ... then wrap back
|
||||
chunk_size = if chunk_size >= 64 { 1 } else { chunk_size * 2 };
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,2 @@
|
||||
/// Re-export modules for fuzz targets and testing.
|
||||
pub mod protocol;
|
||||
+4
-1
@@ -49,10 +49,13 @@ impl Instruction {
|
||||
.map_err(|_| ParseError::InvalidLength)?;
|
||||
remaining = &remaining[dot_pos + 1..];
|
||||
|
||||
// Extract element value
|
||||
// Extract element value (length is in bytes per Guacamole spec)
|
||||
if remaining.len() < len {
|
||||
return Err(ParseError::Truncated);
|
||||
}
|
||||
if !remaining.is_char_boundary(len) {
|
||||
return Err(ParseError::Truncated);
|
||||
}
|
||||
elements.push(remaining[..len].to_string());
|
||||
remaining = &remaining[len..];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user