Spindle
Spindle is a simple and efficient expression and byte sequence generator to aid fuzz testing parsers and de-serializers. Spindle spins raw, untyped byte buffers into structured data.
Overview
Spindle's syntax, similar to Extended Backus–Naur form, lets users define the structure of generated data. This syntax compiles to Grammar, a state machine that can be arbitrarily traversed to produce structure-aware, matching expressions.
Spindle works with fuzzers such as cargo-fuzz or AFL because it is an extension of arbitrary; the traversal of the state machine is deterministically dependent on Unstructured.
Spindle is particularily useful for generating semi-correct and interesting inputs that attack edge cases of parsers and de-serializers, such as mixing familar tokens in incorrect places or sprinkling in Unicode characters.
Spindle is developed and leveraged by AWS to fuzz test the parsers and de-serializers in their backend systems.
Examples
For more examples, see the examples folder.
use Grammar;
use Unstructured;
let math: Grammar = r#"
expr : u16 | paren | expr symbol expr ;
paren : "(" expr symbol expr ")" ;
symbol : r"-|\+|\*|÷" ;
"#.parse.unwrap;
let mut wool = new;
let yarn: String = math.expression.unwrap;
// (21359*39933))+13082-62216
The state machine traversal always starts at the first rule. In the example,
expris the first rule and evaluates to eitheru16,paren, or the concatenation ofexprandsymbolandexpr.;delimits different rules.u16is a pre-defined data types that directly evaluates tou16::arbitrary(u).parenevaluates to the concatenation of the literal"(",expr,symbol,exprand,")".symbolevaluates to the an arbitrary string matching the regex-|\+|\*|÷.
Semi-Correct Expression
This grammar is similar to the well formed math expression grammar, but sometimes includes an extra closing parenthesis and/or an arbitrary symbol.
use Grammar;
use Unstructured;
let math: Grammar = r#"
expr : u16 | paren | expr symbol expr ;
paren : "(" expr symbol expr ")" ")"? ;
symbol : r"-|\+|\*|÷" | String ;
"#.parse.unwrap;
let mut wool = new;
let yarn: String = math.expression.unwrap;
// (44637*32200)Ѱ'x124928390-27338)
Usage in Fuzzer
use Grammar;
use fuzz_target;
use ;
use LazyLock;
static GRAMMAR: = new;
;
fuzz_target!;
Grammar Syntax
For examples see examples.
The operators of Spindle's grammar are:
- Optional:
X?evaluates to eitherXor nothing. - Repetition:
X+evaluates toX1 or more times (up to and including [crate::MAX_REPEAT])X*evaluates toX0 or more times (up to and including [crate::MAX_REPEAT])X{k}evaluates toXexactly k times, where k is au32.X{min,max}evaluatesXat leastmintimes and at most (including)maxtimes.minandmaxareu32.
- Or:
X | Yevaluates to eitherXorY. - Literal:
- String:
"X"evaluates to the literal value inside the quotes, e.g."foo". - Bytes:
[X]evaluates to the literalVec<u8>, e.g.[1, 2].
- String:
- Regex:
r"X"arbitrarily evaluates the regex inside the quotes, e.g.r"[A-Z]+". - Concat:
X Yevaluates toXand thenY. - Group:
(X)groups the expression insdie the parenthesis, e.g.(X | Y)+. - Reference:
rule : X ;defines a rule with name "rule" with some patternX. "rule" can be referenced in the same grammar, e.g.another_rule : rule+ ; - Predefined:
u16is a pre-defined rule that evaluate tou16::arbitrary(u). All pre-defined rules evaluate toT::arbitrary(u). See more. All pre-defined rules are:- String
- char
- u8
- u16
- u32
- u64
- u128
- usize
- i8
- i16
- i32
- i64
- i128
- isize
- f32
- f64
Visitor
A Visitor is some state that is initialized before traversal and mutated as different rules are visited during the traversal, e.g. visit_or. Vistors that are already implemented are String and Vec<u8> for output buffers, and u64 for classification.
Users can use their own implementation of Visitor, for example if they want to
- use a different output buffer
- use a different classification
- gather data
- build an abstract syntax tree
Example
use ;
let math: Grammar = r#"
expr : u16 | paren | expr symbol expr ;
paren : "(" expr symbol expr ")" ;
symbol : r"-|\+|\*|÷" ;
"#.parse.unwrap;
/// Detects if any prime numbers were generated.
;
let mut wool = new;
let : = math.expression.unwrap;
let yarn: String = math.expression.unwrap;
assert!;
Example
In this example, a math specific abstract syntax tree (AST) is built during the arbitrary traversal.
use ;
let math: Grammar = r#"
expr : u16 | paren | expr symbol expr ;
paren : "(" expr symbol expr ")" ;
symbol : r"-|\+|\*|÷" ;
"#.parse.unwrap;
let mut wool = new;
// MathAst { cur_op: None, stack: [Expr(Num(13108), '*', Num(0))] }
let yarn: MathAst = math.expression.unwrap;