basic idea: generate lots of random inputs — ‘‘fuzzing’’
look for memory errors
fn fuzz_test_image_parser(original_image: Vec<u8>) {
let rng = rand::rng();
for _ in 0..NUM_TRIES {
let mut test_image = original_image.clone();
let number_of_changes = rng.random_range(0..MAX_CHANGES);
for _ in 0..number_of_changes {
test_image[rng.random_range(0..test_image.len())] ^= rng.random::<u8>();
}
let result = try_to_parse_image(&test_image);
if result == CRASH {
// report that test_image triggers a bug
}
}
}works with unmodified software
works with many kinds of input
easy to parallelize
has actually found lots of bugs
easiest to find crashes
otherwise: how do we know if test cases are useful?
need some way to know if test result is correct
example: fuzz-testing of C compilers versus other C compilers
fuzzing for cross-site scripting bugs?
fuzzing for SQL injection?
operating system?
(less likely) fuzzing for permissions issues?
<html><head><title>A</title></head><body>B</body></html>
<html*<head><title>A</title></head><body>B</body></html>
<html><iead><title>C</title></head><body>B</body></html>
<html*<head and <head><iead> worth testing…“… Like most DOM fuzzers, Domato is generative, meaning that the fuzzer generates a sample from scratch given a set of grammars that describes HTML/CSS structure as well as various JavaScript objects, properties and functions…”
<html root=true> = <lt>!-- saved from url=(0014)about:internet --<gt><lf><lt>html<gt><head><body><newline><lt>/html<gt>
...
<body> = <newline><lt>body onload=jsfuzzer()<gt><newline><lt>!--beginhtml--<gt><bodyelements><lt>!--endhtml--<gt><newline><lt>/body<gt>
...
<bodyelements> = <newline><element><newline><element><newline><element><newline><element><newline><element><newline><element><newline><element><newline><element><newline><element><newline><element><newline>
...
<element> = <HTMLAnchorElement>
<element> = <HTMLBaseElement>
<element> = <HTMLBaseFontElement>
...
<element> = <HTMLHeadingElement>
...
<HTMLAnchorElement> = <lt>a <a_attributes> <attributes><gt><innerelements><lt>/a<gt>
...
<HTMLHeadingElement> = <lt>h1 <h1_attributes> <attributes><gt><innerelements><lt>/h1<gt>
<HTMLHeadingElement> = <lt>h2 <h2_attributes> <attributes><gt><innerelements><lt>/h2<gt>
<HTMLHeadingElement> = <lt>h3 <h3_attributes> <attributes><gt><innerelements><lt>/h3<gt>
...
‘‘coverage’’: metric for how good tests are
% of code reached
easy to measure
correlates with bugs found
conceptual idea: look at code, go down all paths
seems automatable?
initial test case A:
a = 0x17, b = 0x08; covers: WZ
generate random test cases based on A:
a = 0x37, b = 0x08; covers: WZ
a = 0x15, b = 0x08; covers: WZ
a = 0x17, b = 0x0c; covers: WZ
a = 0x13, b = 0x08; covers: WZ
a = 0x17, b = 0x08; covers: WZ
…
a = 0x17, b = 0x00; covers: WY
found new test case with different coverage
initial test case A:
a = 0x17, b = 0x08; covers: WZ
found test case B (derived from A):
a = 0x17, b = 0x00; covers: WY
generate random test cases based on A and B:
a = 0x37, b = 0x08; covers: WZ
a = 0x04, b = 0x00; covers: WY
a = 0x17, b = 0x01; covers: WZ
a = 0x16, b = 0x00; covers: WY
…
a = 0x97, b = 0x00; covers: WYQ
…
a = 0x00, b = 0x08; covers: XY
initial test case A:
a = 0x17, b = 0x08; covers: WZ
found test case B (derived from A):
a = 0x17, b = 0x00; covers: WY
found test case C (derived from B):
a = 0x97, b = 0x00; covers: WYQ
found test case D (derived from A):
a = 0x00, b = 0x08; covers: XY
generate random test cases based on A and B and C and D:
…
initial test case A:
a = 0x17, b = 0x08, c = 0x00; covers: WZ
generate random test cases based on A:
a = 0x37, b = 0x08, c = 0x00; covers: WZ
a = 0x15, b = 0x08, c = 0x02; covers: WZ
a = 0x17, b = 0x0c, c = 0x00; covers: WZ
a = 0x13, b = 0x08, c = 0x40; covers: WZ
a = 0x17, b = 0x08, c = 0x10; covers: WZ
…
a = 0x17, b = 0x00, c = 0x01; covers: WXY
initial test case A:
a = 0x17, b = 0x08, c = 0x00; covers: WZ
found test case B (based on A):
a = 0x17, b = 0x00, c = 0x01; covers: WXY
generate random test cases based on A and B:
a = 0x37, b = 0x08, c = 0x00; covers: WZ
a = 0x17, b = 0x00, c = 0x03; covers: WXY
a = 0x17, b = 0x0c, c = 0x00; covers: WZ
a = 0x37, b = 0x00, c = 0x03; covers: WXY
a = 0x17, b = 0x08, c = 0x10; covers: WZ
…
a = 0x17, b = 0x00, c = 0x81; covers: WY
CoverageMap[EdgeID]++;
example crash: A00ABDBBBDEEDDDCCCBBBDDDAAAA00000000000000000000000B
would really prefer minimal example: AA00000000000B
but look for same result and/or coverage
systematic simplifications:
keep simplifications that don’t change result
AFL uses some of this strategy to help get better ‘base’ tests
Regher et al, “Test-Case Reduction for C Compiler Bugs” (PLDI 2012)
take a C program that triggers bug…
try removing things to make it smaller
needed: automated way of checking “is bug still there”
same idea applies to security bugs
| input | comparisons |
|---|---|
| a=0, b=0, c=0 | 0 != 0, 0 < 5, 0 > 0, 4 != 7 |
| a=1, b=0, c=0 | 1 != 0, 0 < 5, 1 > 0, 5 != 0 |
| a=0, b=1, c=0 | 0 != 0, 1 < 5, 0 > 0, 5 != 0 |
| a=0, b=0, c=1 | 0 != 0, 0 < 5, 0 > 1, 4 != 0 |
| input | comparisons |
|---|---|
| a=0, b=0, c=0 | 0 < 100, 0 == 0x1234 |
| a=1, b=0, c=0 | 1 < 100, 0 == 0x1235 |
| a=2, b=0, c=0 | 2 < 100, 0 == 0x1236 |
| a=0, b=1, c=0 | 0 < 100, 0 == 0x1244 |
| a=0, b=2, c=0 | 0 < 100, 0 == 0x1254 |
| a=0, b=0, c=1 | 0 < 100, 1 == 0x1334 |
| a=0, b=0, c=2 | 0 < 100, 2 == 0x1334 |
| input | comparisons |
|---|---|
aaaaaaaa |
0x61616161 == 0xDEADFF, 0x61616161 == 0x12345678, 'a' == '#' |
baaaaaaa |
0x62616161 == 0xDEADFF, 0x61616162 == 0x12345678, 'b' == '#' |
caaaaaaa |
0x63616161 == 0xDEADFF, 0x61616163 == 0x12345678, 'c' == '#' |
abaaaaaa |
0x61626161 == 0xDEADFF, 0x61616261 == 0x12345678, 'a' == '#' |
acaaaaaa |
0x61636161 == 0xDEADFF, 0x61616361 == 0x12345678, 'a' == '#' |
aabaaaaa |
0x61616261 == 0xDEADFF, 0x61626161 == 0x12345678, 'a' == '#' |
aacaaaaa |
0x61616361 == 0xDEADFF, 0x61636161 == 0x12345678, 'a' == '#' |
aaabaaaa |
0x61616162 == 0xDEADFF, 0x62616161 == 0x12345678, 'a' == '#' |
aaacaaaa |
0x61616163 == 0xDEADFF, 0x63616161 == 0x12345678, 'a' == '#' |