Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Specification for Free: Behavior-Driven Fuzzin...

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

Specification for Free: Behavior-Driven Fuzzing with Inferred Specifications

A bug uncovered by fuzzing is seldom the product of a single input. More often, it's a family of inputs that reliably triggers the same underlying flaw.
Validating a fix means testing against that entire family, not just the one input that happened to surface the problem. In this lecture, I'll show how to
capture these families as specialized grammars that encode specific program behaviors, particularly faulty ones. You'll learn how to extract and merge such
patterns into targeted grammars, and how to use them to focus your fuzzing efforts. These grammars compose naturally with smart fuzzers, giving you fine-
grained control over input generation and making tests consistent and reproducible.

A persistent challenge in fuzzing is that leveraging input structure requires accurate specifications, which aren't always on hand. I'll show how to
sidestep this by deriving input grammars directly from the program. When source code is available, static and dynamic analysis can trace input-handling
logic from first principles. Without it, we can infer structure by observing how the program responds to inputs and iteratively refining our model through
feedback.

Together, these techniques make specification-free fuzzing practical and lay the foundation for the behavior-driven strategies we'll develop throughout the
session.

Avatar for Rahul Gopinath

Rahul Gopinath

May 28, 2026

More Decks by Rahul Gopinath

Other Decks in Research

Transcript

  1. Prerequisites https://github.com/vrthra/summer2026-nus • Install Python 3.12 • Install Graphviz (optional)

    • Install Jupyter • Start Jupyter 3 $ jupyter notebook --ip='*' --NotebookApp.token='' --NotebookApp.password=''
  2. Prerequisites https://github.com/vrthra/summer2026-nus • Install Python 3.12 • Install Graphviz (optional)

    • Install Jupyter • Start Jupyter 4 $ jupyter notebook --ip='*' --NotebookApp.token='' --NotebookApp.password=''
  3. 14 $ ./fuzz [;x1-GPZ+wcckc];,N9J+?#6^6\e?]9lu
 2_%'4GX"0VUB[E/r ~fApu6b8<{%siq8Z
 h.6{V,hr?;{Ti.r3PIxMMMv6{xS^+'Hq!
 AxB"YXRS@!Kd6;wtAMefFWM(`|J_<1~o}
 z3K(CCzRH JIIvHz>_*.\>JrlU32~eGP?


    lR=bF3+;y$3lodQ<B89!5"W2fK*vE7v{'
 )KC-i,c{<[~m!]o;{.'}Gj\(X}EtYetrp
 bY@aGZ1{P!AZU7x#4(Rtn!q4nCwqol^y6
 }0|Ko=*JK~;zMKV=9Nai:wxu{J&UV#HaU
 )*BiC<),`+t*gka<W=Z.%T5WGHZpI30D<
 Pq>&]BS6R&j?#tP7iaV}-}`\?[_[Z^LBM
 PG-FKj'\xwuZ1=Q`^`5,$N$Q@[!CuRzJ2
 D|vBy!^zkhdf3C5PAkR?V((-%><hn|3='
 i2Qx]D$qs4O`1@fevnG'2\11Vf3piU37@
 5:dfd45*(7^%5ap\zIyl"'f,$ee,J4Gw:
 cgNKLie3nx9(`efSlg6#[K"@WjhZ}r[Sc
 un&sBCS,T[/3]KAeEnQ7lU)3Pn,0)G/6N
 -wyzj/MTd#A;r*(ds./df3r8Odaf?/<#r Program ✘ Random Fuzzing
  4. @app.route('/admin') def admin(): username = request.cookies.get("username") if not username: return

    {"Error": "Specify username in Cookie"} username = urllib.quote(os.path.basename(username)) url = "http://permissions:5000/permissions/{}".format(username) resp = requests.request(method="GET", url=url) # "superadmin\ud888" will be simpli fi ed to "superadmin" ret = ujson.loads(resp.text) if resp.status_code == 200: if "superadmin" in ret["roles"]: return {"OK": "Superadmin Access granted"} else: e = u"Access denied. User has following roles: {}".format(ret["roles"]) return {"Error": e}, 401 else:return {"Error": ret["Error"]}, 500 [ ; x 1 - G P Z + w c c k c ] ; , N 9 J + ? # 6 ^ 6 \ e ? ] 9 l u 2 _ % ' 4 G X " 0 V U B [ E / r ~ f A p u 6 b 8 < { % s i q 8 Z h . 6 { V , h r ? ; {Ti.r3PIxMMMv6{xS^+'Hq!AxB"YXRS@! Kd6;wtAMefFWM(`|J_<1~o}z3K(CCzRH J I I v H z > _ * . \ > J r l U 3 2 ~ e G P ? lR=bF3+;y$3lodQ<B89!5"W2fK*vE7v{')KC- i,c{<[~m!]o;{.'}Gj\(X}EtYetrpbY@aGZ1{P! A Z U 7 x # 4 ( R t n ! q 4 n C w q o l ^ y 6 } 0 | Ko=*JK~;zMKV=9Nai:wxu{J&UV#HaU)*Bi C < ) , ` + t * g k a < W = Z . % T 5 W G H Z p I 3 0 D < P q > & ] B S 6 R & j ? # t P 7 i a V } - } ` \ ? [ _ [ Z ^ L B M P G - FKj'\xwuZ1=Q`^`5,$N$Q@[!CuRzJ2D|vBy! ^ z k h d f 3 C 5 P A k R ? V ( ( - % > < h n | 3='i2Qx]D$qs4O`1@fevnG'2\11Vf3piU37@ 5 : d f d 4 5 * ( 7 ^ % 5 a p \ z I y l " ' f , $ee,J4Gw:cgNKLie3nx9(`efSlg6#[K"@Wjh Z}r[Scun&sBCS,T[/3]KAeEnQ7lU)3Pn,0)G/ 6N-wyzj/MTd#A;r Program 18 https://www.fuzzingbook.org/html/Fuzzer.html Traditional Fuzzing
  5. • Insert Instrumentation • Generate inputs • Collect execution feedback

    • Branches covered during execution • Slightly Mutate Input and try again Collect inputs obtaining new coverage 21 Feedback Driven Fuzzing 21 https://www.fuzzingbook.org/html/MutationFuzzer.html
  6. 22 def triangle(a, b, c): if a == b: if

    b == c: return Equilateral else: return Isosceles else: if b == c: return Isosceles else: if a == c: return Isosceles else: return Scalene def triangle(a, b, c): __probe_enter() if a == b: __probe_1() if b == c: __probe_2() return Equilateral else: __probe_3() return Isosceles else: __probe_4() if b == c: __probe_5() return Isosceles else: __probe_6() if a == c: __probe_7() return Isosceles else: __probe_8() return Scalene def triangle(a, b, c): if a == b: if b == c: return Equilateral else: return Isosceles else: if b == c: return Isosceles else: if a == c: return Isosceles else: return Scalene • Insert Instrumentation • Generate inputs • Collect execution feedback • Branches covered during execution • Slightly Mutate Input and try again Collect inputs obtaining new coverage 22 https://www.fuzzingbook.org/html/MutationFuzzer.html Feedback Driven Fuzzing
  7. 23 triangle (1,1,1) def triangle(a, b, c): if a ==

    b: if b == c: return Equilateral else: return Isosceles else: if b == c: return Isosceles else: if a == c: return Isosceles else: return Scalene • Insert Instrumentation • Generate inputs • Collect execution feedback • Branches covered during execution • Slightly Mutate Input and try again Collect inputs obtaining new coverage 23 https://www.fuzzingbook.org/html/MutationFuzzer.html Feedback Driven Fuzzing
  8. 24 triangle (1,1,1) def triangle(a, b, c): if a ==

    b: if b == c: return Equilateral else: return Isosceles else: if b == c: return Isosceles else: if a == c: return Isosceles else: return Scalene • Insert Instrumentation • Generate inputs • Collect execution feedback • Branches covered during execution • Slightly Mutate Input and try again Collect inputs obtaining new coverage 24 https://www.fuzzingbook.org/html/MutationFuzzer.html Feedback Driven Fuzzing
  9. triangle (1,1,1) 25 triangle (1,1,2) def triangle(a, b, c): if

    a == b: if b == c: return Equilateral else: return Isosceles else: if b == c: return Isosceles else: if a == c: return Isosceles else: return Scalene • Insert Instrumentation • Generate inputs • Collect execution feedback • Branches covered during execution • Slightly Mutate Input and try again Collect inputs obtaining new coverage Mutated 25 https://www.fuzzingbook.org/html/MutationFuzzer.html Feedback Driven Fuzzing
  10. 26 triangle (1,1,3) def triangle(a, b, c): if a ==

    b: if b == c: return Equilateral else: return Isosceles else: if b == c: return Isosceles else: if a == c: return Isosceles else: return Scalene • Insert Instrumentation • Generate inputs • Collect execution feedback • Branches covered during execution • Slightly Mutate Input and try again Collect inputs obtaining new coverage Mutated 26 https://www.fuzzingbook.org/html/MutationFuzzer.html Feedback Driven Fuzzing
  11. Feedback Driven Fuzzing 27 triangle (1,1,2) def triangle(a, b, c):

    if a == b: if b == c: return Equilateral else: return Isosceles else: if b == c: return Isosceles else: if a == c: return Isosceles else: return Scalene • Insert Instrumentation • Generate inputs • Collect execution feedback • Branches covered during execution • Slightly Mutate Input and try again Collect inputs obtaining new coverage 27 https://www.fuzzingbook.org/html/MutationFuzzer.html triangle (1,1,1) triangle (1,1,3)
  12. Feedback Driven Fuzzing • Insert Instrumentation • Generate inputs •

    Collect execution feedback • Branches covered during execution • Slightly Mutate Input and try again Collect inputs obtaining new coverage 28 triangle (1,1,2) def triangle(a, b, c): if a == b: if b == c: return Equilateral else: return Isosceles else: if b == c: return Isosceles else: if a == c: return Isosceles else: return Scalene triangle (1,1,1) 28
  13. 30 Weakness: static int is_reserved_word_token(const char *s, int len) {

    const char *reserved[] = { "break", "case", "catch", "continue", "debugger", "default", "delete", "do", "else", "false", "finally", "for", "function", "if", "in", "instanceof", "new", "null", "return", "switch", "this", "throw", "true", "try", "typeof", "var", "void", "while", "with", "let", "undefined", ((void *)0)}; int i; if (!mjs_is_alpha(s[0])) return 0; for (i = 0; reserved[i] != ((void *)0); i++) { if (len == (int)strlen(reserved[i]) && strncmp(s, reserved[i], len) == 0) return i + 1; } return 0; } Tokens if (x > 100) { } coverage: 20% if (x > 100) { } e coverage: 5% if (x > 100) { } el coverage: 5% if (x > 100) { } els coverage: 5% if (x > 100) { } else coverage: 25% No smooth coverage gradient in
 parsers with lexical tokens 30 Note: Constant unrolling (e.g. AFL) does not help in such lexical tokens Feedback Driven Fuzzing
  14. 31 def json_raw(stm): while True: stm.skipspaces() c = stm.peek() if

    c == 't': return json_fixed(stm, 'true') elif c == 'f': return json_fixed(stm, 'false') elif c == 'n': return json_fixed(stm, 'null') elif c == '"': return json_string(stm) elif c == '{': return json_dict(stm) elif c == '[': return json_list(stm) elif c in NUMSTART: return json_number(stm) raise JSONError(E_MALF, stm, stm.pos) Weak points: • Need for smooth coverage gradient • Coverage only provides first level guidance 1. {"abc":[]} 2. [{"a":[]}, {"b":[]}, {"c":["ab","c"]}] 31 Feedback Driven Fuzzing
  15. 32 def json_raw(stm): while True: stm.skipspaces() c = stm.peek() if

    c == 't': return json_fixed(stm, 'true') elif c == 'f': return json_fixed(stm, 'false') elif c == 'n': return json_fixed(stm, 'null') elif c == '"': return json_string(stm) elif c == '{': return json_dict(stm) elif c == '[': return json_list(stm) elif c in NUMSTART: return json_number(stm) raise JSONError(E_MALF, stm, stm.pos) Weak points: • Need for smooth coverage gradient • Coverage only provides first level guidance 1. {"abc":[]} 2. [{"a":[]}, {"b":[]}, {"c":["ab","c"]}] 32 Feedback Driven Fuzzing
  16. 33 def json_raw(stm): while True: stm.skipspaces() c = stm.peek() if

    c == 't': return json_fixed(stm, 'true') elif c == 'f': return json_fixed(stm, 'false') elif c == 'n': return json_fixed(stm, 'null') elif c == '"': return json_string(stm) elif c == '{': return json_dict(stm) elif c == '[': return json_list(stm) elif c in NUMSTART: return json_number(stm) raise JSONError(E_MALF, stm, stm.pos) Weak points: • Need for smooth coverage gradient • Coverage only provides first level guidance 1. {"abc":[]} 2. [{"a":[]}, {"b":[]}, {"c":["ab","c"]}] 33 Feedback Driven Fuzzing
  17. 35

  18. 38 Fuzzing Parsers $ ./fuzz [;x1-GPZ+wcckc];,N9J+?#6^6\e?]9lu
 2_%'4GX"0VUB[E/r ~fApu6b8<{%siq8Z
 h.6{V,hr?;{Ti.r3PIxMMMv6{xS^+'Hq!
 AxB"YXRS@!Kd6;wtAMefFWM(`|J_<1~o}


    z3K(CCzRH JIIvHz>_*.\>JrlU32~eGP?
 lR=bF3+;y$3lodQ<B89!5"W2fK*vE7v{'
 )KC-i,c{<[~m!]o;{.'}Gj\(X}EtYetrp
 bY@aGZ1{P!AZU7x#4(Rtn!q4nCwqol^y6
 }0|Ko=*JK~;zMKV=9Nai:wxu{J&UV#HaU
 )*BiC<),`+t*gka<W=Z.%T5WGHZpI30D<
 Pq>&]BS6R&j?#tP7iaV}-}`\?[_[Z^LBM
 PG-FKj'\xwuZ1=Q`^`5,$N$Q@[!CuRzJ2
 D|vBy!^zkhdf3C5PAkR?V((-%><hn|3='
 i2Qx]D$qs4O`1@fevnG'2\11Vf3piU37@
 5:dfd45*(7^%5ap\zIyl"'f,$ee,J4Gw:
 cgNKLie3nx9(`efSlg6#[K"@WjhZ}r[Sc
 un&sBCS,T[/3]KAeEnQ7lU)3Pn,0)G/6N
 -wyzj/MTd#A;r*(ds./df3r8Odaf?/<#r Parser Syntax Error Interpreter #
  19. 39 Leveraging Instrumentation Based Feedback def parse_num(s,i): n = ''

    while s[i:] and is_digit(s[i]): n += s[i] i = i +1 return i,n def parse_paren(s, i): assert s[i] == '(' i, v = parse_expr(s, i+1) if s[i:] == '': raise Exception(s, i) assert s[i] == ')' return i+1, v def parse_expr(s, i = 0): expr = [] is_op = True while s[i:]: c = s[i] if c in string.digits: if not is_op: raise Exception(s,i) i,num = parse_num(s,i) expr.append(num) is_op = False elif c in ['+', '-', '*', '/']: if is_op: raise Exception(s,i) expr.append(c) is_op = True i = i + 1 elif c == '(': if not is_op: raise Exception(s,i) i, cexpr = parse_paren(s, i) expr.append(cexpr) is_op = False elif c == ')': break else: raise Exception(s,i) if is_op: raise Exception(s,i) return i, expr Parser Syntax Error Interpreter #
  20. 45 Speci fi cation Free Generator A ( 2 -

    B 9 ) 4 ) A ∉ (,+,-,1,2,3,4,5,6,7,8,9,0 B ∉ +,-,1,2,3,4,5,6,7,8,9,0,) ) ∉ +,-,1,2,3,4,5,6,7,8,9,0 (2-94)
  21. 46

  22. 50 Formal Languages Formal Language Descriptions 3. Regular Context Free

    Recursively Enumerable (Chomsky,1956) Easy to produce and parse Argument Stack Return Stack Easier to reason with
  23. 51 Grammar <start> := <expr> <expr> := <expr> '+' <expr>

    | <expr> '-' <expr> | <expr> '/' <expr> | <expr> '*' <expr> | '(' <expr> ')' | <number> <number> := <integer> | <integer> '.' <integer> <integer>:= <digit> <integer> | <digit> <digit> := [0-9] Arithmetic expression grammar De f inition for <expr> <expr> key
  24. 52 <start> := <expr> <expr> := <expr> '+' <expr> |

    <expr> '-' <expr> | <expr> '/' <expr> | <expr> '*' <expr> | '(' <expr> ')' | <number> <number> := <integer> | <integer> '.' <integer> <integer>:= <digit> <integer> | <digit> <digit> := [0-9] Grammar Arithmetic expression grammar Expansion Rule Terminal Symbol Nonterminal Symbol
  25. 54 Grammars For Parsing (8 / 3) * 49 <start>

    := <expr> <expr> := <expr> '+' <expr> | <expr> '-' <expr> | <expr> '/' <expr> | <expr> '*' <expr> | '(' <expr> ')' | <number> <number> := <integer> | <integer> '.' <integer> <integer>:= <digit> <integer> | <digit> <digit> := [0-9]
  26. 56 Parsing is Surprisingly Integral to Fuzzing 1. Parsing is

    necessary for decomposing and recomposing inputs without affecting the validity of inputs constructed (8 / 3) * 49
  27. 57 Parsing is Surprisingly Integral to Fuzzing 1. Parsing is

    necessary for decomposing and recomposing inputs without affecting the validity of inputs constructed (8 / 3) * 49 http://localhost:8888/notebooks/x0_3_Parser.ipynb#Arborist
  28. 58 Parsing is Surprisingly Integral to Fuzzing 1. Parsing is

    necessary for decomposing and recomposing inputs without affecting the validity of inputs constructed (2 + 1) * 49 1 2 + (8 / 3) * 49
  29. 59 Parsing is Surprisingly Integral to Fuzzing 2. Use parsers

    to mine the input distribution, and generate uncommon inputs
  30. 60 Parsing is Surprisingly Integral to Fuzzing 2. Use parsers

    to extract the complexity of an input or a set of inputs 18439249 (8/3)*49 https://rahul.gopinath.org/post/2024/03/23/k-paths-for-context-free-grammars/
  31. 61

  32. 62 Grammars 8.2 - 27 - -9 / +((+9 *

    --2 + --+-+- ((-1 * +(8 - 5 - 6)) * (-(a-+(((+(4) )))) - ++4) / +(-+---((5.6 - --(3 * -1.8 * +(6 * +-(((-(-6) * ---+6)) / +--(+-+-7 * (-0 * (+(((((2)) + 8 - 3 - ++9.0 + ---(--+7 / (1 / +++6.37) + (1) / 482) / +++-+0)))) + 8.2 - 27 - -9 / +((+9 * --2 + --+-+-((-1 * + (8 - 5 - 6)) * (-(a-+(((+(4))))) - + +4) / +(-+---((5.6 - --(3 * -1.8 * + (6 * +-(((-(-6) * ---+6)) / +--(+-+- 7 * (-0 * (+(((((2)) + 8 - 3 - ++9.0 + ---(--+7 / (1 / +++6.37) + (1) / 482) / +++-+0)))) * -+5 + 7.513)))) - (+1 / ++((-84)))))))) * ++5 / +-(- -2 - -++-9.0)))) / 5 * --++090 + * - +5 + 7.513)))) - (+1 / ++((-84)))))) )) * 8.2 - 27 - -9 / +((+9 * --2 + - -+-+-((-1 * +(8 - 5 - 6)) * (-(a-+(( (+(4))))) - ++4) / +(-+---((5.6 - -- (3 * -1.8 * +(6 * +-(((-(-6) * ---+6 )) / +--(+-+-7 * (-0 * (+(((((2)) + 8 - 3 - ++9.0 + ---(--+7 / (1 / +++6 .37) + (1) / 482) / +++-+0)))) * -+5 + 7.513)))) - (+1 / ++((-84)))))))) * ++5 / +-(--2 - -++-9.0)))) / 5 * --++090 ++5 / +-(--2 - -++-9.0)))) / 5 * --++090 <start> := <expr> <expr> := <expr> '+' <expr> | <expr> '-' <expr> | <expr> '/' <expr> | <expr> '*' <expr> | '(' <expr> ')' | <number> <number> := <integer> | <integer> '.' <integer> <integer>:= <digit> <integer> | <digit> <digit> := [0-9] For Fuzzing (Hanford 1970) (Purdom 1972)
  33. 63 Grammars As effective producers Interpreter Parser ✘ ✔ 8.2

    - 27 - -9 / +((+9 * --2 + --+-+- ((-1 * +(8 - 5 - 6)) * (-(a-+(((+(4) )))) - ++4) / +(-+---((5.6 - --(3 * -1.8 * +(6 * +-(((-(-6) * ---+6)) / +--(+-+-7 * (-0 * (+(((((2)) + 8 - 3 - ++9.0 + ---(--+7 / (1 / +++6.37) + (1) / 482) / +++-+0)))) + 8.2 - 27 - -9 / +((+9 * --2 + --+-+-((-1 * + (8 - 5 - 6)) * (-(a-+(((+(4))))) - + +4) / +(-+---((5.6 - --(3 * -1.8 * + (6 * +-(((-(-6) * ---+6)) / +--(+-+- 7 * (-0 * (+(((((2)) + 8 - 3 - ++9.0 + ---(--+7 / (1 / +++6.37) + (1) / 482) / +++-+0)))) * -+5 + 7.513)))) - (+1 / ++((-84)))))))) * ++5 / +-(- -2 - -++-9.0)))) / 5 * --++090 + * - +5 + 7.513)))) - (+1 / ++((-84)))))) )) * 8.2 - 27 - -9 / +((+9 * --2 + - -+-+-((-1 * +(8 - 5 - 6)) * (-(a-+(( (+(4))))) - ++4) / +(-+---((5.6 - -- (3 * -1.8 * +(6 * +-(((-(-6) * ---+6 )) / +--(+-+-7 * (-0 * (+(((((2)) + 8 - 3 - ++9.0 + ---(--+7 / (1 / +++6 .37) + (1) / 482) / +++-+0)))) * -+5 + 7.513)))) - (+1 / ++((-84)))))))) * ++5 / +-(--2 - -++-9.0)))) / 5 * --++090 ++5 / +-(--2 - -++-9.0)))) / 5 * --++090
  34. 64 Grammars <start> := <expr> <expr> := <expr> '+' <expr>

    | <expr> '-' <expr> | <expr> '/' <expr> | <expr> '*' <expr> | '(' <expr> ')' | <number> <number> := <integer> | <integer> '.' <integer> <integer>:= <digit> <integer> | <digit> <digit> := [0-9] As efficient producers def start(): expr() def expr(): match (random() % 6): case 0: expr(); print('+'); expr() case 1: expr(); print('-'); expr() case 2: expr(); print('/'); expr() case 3: expr(); print('*'); expr() case 4: print('('); expr(); print(')') case 5: number() def number(): match (random() % 2): case 0: integer() case 1: integer(); print('.'); integer() def integer(): match (random() % 2): case 0: digit(); integer() case 1: digit() def digit(): match (random() % 10): case 0: print('0') case 1: print('1') case 2: print('2') case 3: print('3') case 4: print('4') case 5: print('5') case 6: print('6') case 7: print('7') Compiled Grammar (F1)
  35. 68 Where to Get the Grammar From? Handwritten parsers contain

    the parse structure key value key value scheme parse_scheme parse_hostpath parse_querystring parse_fragment domain TLD subdomain parse_host subdirectory parse_fslocation binary parse_binaryname parameters parse_parameters parse_url
  36. 69 Where to Get the Grammar From? Mining Grammar from

    a hand-written parser https://www.example.com/forum/questions/cgi?tag=networking&order=newwest#top key value key value split scheme parse_scheme host path parse_hostpath query string parse_querystring fragment parse_fragment domain TLD subdomain parse_host subdirectory parse_fslocation binary parse_binaryname parameters parse_parameters With Dynamic Data Flow Analysis parseurl
  37. 70 http://user:[email protected]:80/?q=path#ref urlparse:url = 'http://user:[email protected]:80/?q=path#ref' urlsplit:scheme = 'http' urlsplit:netloc =

    'user:[email protected]:80' urlsplit:fragment = 'ref' urlsplit:query = 'q=path' https://soft-eng.sydney.edu.au:80/ urlparse:url = 'https://soft-eng.sydney.edu.au:80/' urlsplit:scheme = 'https' urlsplit:netloc = 'soft-eng.sydney.edu.au:80' http://www.fuzzingbook.org/#News urlparse:url = 'http://www.fuzzingbook.org/#News' urlsplit:scheme = 'http' urlsplit:netloc = 'www.fuzzingbook.org' urlsplit:fragment = 'News' Mining with Dynamic Data Flow Analysis
  38. 71 { '<urlparse:url>': [ ['<urlsplit:scheme>', '://', '<urlsplit:netloc>', '/?', '<urlsplit:query>', '#',

    '<urlsplit:fragment>'], ['<urlsplit:scheme>', '://', '<urlsplit:netloc>', '/#','<urlsplit:fragment>'], ['<urlsplit:scheme>', '://', '<urlsplit:netloc>', '/']], '<urlsplit:scheme>' : [ ['http'], ['http']], '<urlsplit:netloc>': [ ['user:[email protected]:80'], ['www.fuzzingbook.org'], ['soft-eng.sydney.edu.au']], '<urlsplit:query>' : [ ['q=path']], '<urlsplit:fragment>' : [ ['ref'], ['News']],
 } http://user:[email protected]:80/?q=path#ref urlparse:url = 'http://user:[email protected]:80/?q=path#ref' urlsplit:scheme = 'http' urlsplit:netloc = 'user:[email protected]:80' urlsplit:fragment = 'ref' urlsplit:query = 'q=path' https://soft-eng.sydney.edu.au:80/ urlparse:url = 'https://soft-eng.sydney.edu.au:80/' urlsplit:scheme = 'https' urlsplit:netloc = 'soft-eng.sydney.edu.au:80' http://www.fuzzingbook.org/#News urlparse:url = 'http://www.fuzzingbook.org/#News' urlsplit:scheme = 'http' urlsplit:netloc = 'www.fuzzingbook.org' urlsplit:fragment = 'News' Mining with Dynamic Data Flow Analysis
  39. { '<urlparse:url>': [ ['<urlsplit:scheme>', '://', '<urlsplit:netloc>', '/?', '<urlsplit:query>', '#', '<urlsplit:fragment>'],

    ['<urlsplit:scheme>', '://', '<urlsplit:netloc>', '/#','<urlsplit:fragment>'], ['<urlsplit:scheme>', '://', '<urlsplit:netloc>', '/']], '<urlsplit:scheme>' : [ ['http'], ['http']], '<urlsplit:netloc>': [ ['user:[email protected]:80'], ['www.fuzzingbook.org'], ['soft-eng.sydney.edu.au']], '<urlsplit:query>' : [ ['q=path']], '<urlsplit:fragment>' : [ ['ref'], ['News']],
 } Limitations • Poor accuracy in most handwritten parsers • Handwritten parsers are not often well formed • Control flow is ignored Mining with Dynamic Data Flow Analysis
  40. 74 MIMID Where to Get the Grammar From? Hand-written parsers

    already encode the grammar http://localhost:8888/notebooks/x2_0_MiningGrammar.ipynb
  41. 75 Where to Get the Grammar From? 1. Extract the

    input string accesses 2. Attach control fl ow information Hand-written parsers already encode the grammar Each control- fl ow structure gets wrapped in a context-manager -If conditionals -Loops -Subroutines
  42. 78 How to Extract This Grammar? • Inputs + control

    fl ow -> Dynamic Control Dependence Trees • DCD Trees -> Parse Tree http://localhost:8888/notebooks/x2_0_MiningGrammar.ipynb#Mining-the-Traces-Generated
  43. 79 Control Dependence Graph Statement B is control dependent on

    A if A determines whether B executes. def parse_csv(s,i): while s[i:]: if is_digit(s[i]): n,j = num(s[i:]) i = i+j else: comma(s[i]) i += 1 CDG for parse_csv while: determines whether if: executes
  44. 80 def parse_csv(s,i): while s[i:]: if is_digit(s[i]): n,j = num(s[i:])

    i = i+j else: comma(s[i]) i += 1 CDG for parse_csv Dynamic Control Dependence Tree Each statement execution is represented as a separate node DCD Tree for call parse_csv()
  45. 81 def parse_csv(s,i): while s[i:]: if is_digit(s[i]): n,j = num(s[i:])

    i = i+j else: comma(s[i]) i += 1 '1' '2' ',' DCD Tree ~ Parse Tree •No tracking beyond input bu ff er •Characters are attached to nodes where they are accessed last "12," "12,"
  46. 82 •Characters are attached to nodes where they are accessed

    last http://localhost:8888/notebooks/x2_0_MiningGrammar.ipynb#reparsing-behaviour
  47. 84 def is_digit(i): return i in '0123456789' def parse_num(s,i): n

    = '' while s[i:] and is_digit(s[i]): n += s[i] i = i +1 return i,n def parse_paren(s, i): assert s[i] == '(' i, v = parse_expr(s, i+1) if s[i:] == '': raise Ex(s, i) assert s[i] == ')' return i+1, v def parse_expr(s, i = 0): expr, is_op = [], True while s[i:]: c = s[i] if isdigit(c): if not is_op: raise Ex(s,i) i,num = parse_num(s,i) expr.append(num) is_op = False elif c in ['+', '-', '*', '/']: if is_op: raise Ex(s,i) expr.append(c) is_op, i = True, i + 1 elif c == '(': if not is_op: raise Ex(s,i) i, cexpr = parse_paren(s, i) expr.append(cexpr) is_op = False elif c == ')': break else: raise Ex(s,i) if is_op: raise Ex(s,i) return i, expr 9+3/4 Parse tree for parse_expr('9+3/4')
  48. 86 9+3/4 Identifying Compatible Nodes Which nodes correspond to the

    same nonterminal http://localhost:8888/notebooks/x2_0_MiningGrammar.ipynb#Generalize-Loops
  49. <parse_expr> := <while 1:1> <while 1:0> <while 1:1> | <while

    1:1> <while 1:0> <while 1:1> <while 1:0> <while 1:1> <while 1:0> <while 1:1> | <while 1:1> <while 1:0> <while 1:1> <while 1:0> <while 1:1> | <while 1:1> <while 1:1> := <if 1:1> <if 1:1> := <parse_num> | <parse_paren> <parse_num> := <is_digit> <is_digit> := '3' | '1' <parse_paren>:= '(' <parse_expr> ')' <while 1:0> := <if 1:0> <if 1:0> := '*' 92
  50. <parse_expr> := <while_s> <while_s> := <while_1:1> <while_1:0> <while_s> | <while_1:1>

    <parse_expr> := <while 1:1> <while 1:0> <while 1:1> | <while 1:1> <while 1:0> <while 1:1> <while 1:0> <while 1:1> <while 1:0> <while 1:1> | <while 1:1> <while 1:0> <while 1:1> <while 1:0> <while 1:1> | <while 1:1> <while 1:1> := <if 1:1> <if 1:1> := <parse_num> | <parse_paren> <parse_num> := <is_digit> <is_digit> := '3' | '1' <parse_paren>:= '(' <parse_expr> ')' <while 1:0> := <if 1:0> <if 1:0> := '*' 93 Generalizing Loops with Regular Inference
  51. 95 def is_digit(i): return i in '0123456789' def parse_num(s,i): n

    = '' while s[i:] and is_digit(s[i]): n += s[i] i = i +1 return i,n def parse_paren(s, i): assert s[i] == '(' i, v = parse_expr(s, i+1) if s[i:] == '': raise Ex(s, i) assert s[i] == ')' return i+1, v def parse_expr(s, i = 0): expr, is_op = [], True while s[i:]: c = s[i] if isdigit(c): if not is_op: raise Ex(s,i) i,num = parse_num(s,i) expr.append(num) is_op = False elif c in ['+', '-', '*', '/']: if is_op: raise Ex(s,i) expr.append(c) is_op, i = True, i + 1 elif c == '(': if not is_op: raise Ex(s,i) i, cexpr = parse_paren(s, i) expr.append(cexpr) is_op = False elif c == ')': break else: raise Ex(s,i) if is_op: raise Ex(s,i) return i, expr <START> := <parse_expr.0-0-c> <parse_expr.0-0-c> := <parse_expr.0-1-s><parse_expr.0> | <parse_expr.0> <parse_expr.0-1-s> := <parse_expr.0><parse_expr.0-2> | <parse_expr.0><parse_expr.0-2><parse_expr.0-1-s> <parse_expr.0> := '(' <parse_expr.0-0-c> ')' | <parse_num.0-1-s> <parse_expr.0-2> := '*' | '+' | '-' | '/' <parse_num.0-1-s> := <is_digit.0-0-c> | <is_digit.0-0-c><parse_num.0-1-s> <is_digit.0-0-c> : [0-9] calc.py Recovered Arithmetic Grammar
  52. 96 8.2 - 27 - -9 / +((+9 * --2

    + --+-+- ((-1 * +(8 - 5 - 6)) * (-(a-+(((+(4) )))) - ++4) / +(-+---((5.6 - --(3 * -1.8 * +(6 * +-(((-(-6) * ---+6)) / +--(+-+-7 * (-0 * (+(((((2)) + 8 - 3 - ++9.0 + ---(--+7 / (1 / +++6.37) + (1) / 482) / +++-+0)))) + 8.2 - 27 - -9 / +((+9 * --2 + --+-+-((-1 * + (8 - 5 - 6)) * (-(a-+(((+(4))))) - + +4) / +(-+---((5.6 - --(3 * -1.8 * + (6 * +-(((-(-6) * ---+6)) / +--(+-+- 7 * (-0 * (+(((((2)) + 8 - 3 - ++9.0 + ---(--+7 / (1 / +++6.37) + (1) / 482) / +++-+0)))) * -+5 + 7.513)))) - (+1 / ++((-84)))))))) * ++5 / +-(- -2 - -++-9.0)))) / 5 * --++090 + * - +5 + 7.513)))) - (+1 / ++((-84)))))) )) * 8.2 - 27 - -9 / +((+9 * --2 + - -+-+-((-1 * +(8 - 5 - 6)) * (-(a-+(( (+(4))))) - ++4) / +(-+---((5.6 - -- (3 * -1.8 * +(6 * +-(((-(-6) * ---+6 )) / +--(+-+-7 * (-0 * (+(((((2)) + 8 - 3 - ++9.0 + ---(--+7 / (1 / +++6 .37) + (1) / 482) / +++-+0)))) * -+5 + 7.513)))) - (+1 / ++((-84)))))))) * ++5 / +-(--2 - -++-9.0)))) / 5 * --++090 ++5 / +-(--2 - -++-9.0)))) / 5 * --++090 <START> := <parse_expr.0-0-c> <parse_expr.0-0-c> := <parse_expr.0-1-s><parse_expr.0> | <parse_expr.0> <parse_expr.0-1-s> := <parse_expr.0><parse_expr.0-2> | <parse_expr.0><parse_expr.0-2><parse_expr.0-1-s> <parse_expr.0> := '(' <parse_expr.0-0-c> ')' | <parse_num.0-1-s> <parse_expr.0-2> := '*' | '+' | '-' | '/' <parse_num.0-1-s> := <is_digit.0-0-c> | <is_digit.0-0-c><parse_num.0-1-s> <is_digit.0-0-c> : [0-9]
  53. 97 <START> ::= <json_raw> <json_raw> ::= '"' <json_string'> | '['

    <json_list'> | '{' <json_dict'> | <json_number'> | 'true' | 'false' | 'null' <json_number'> ::= <json_number>+ | <json_number>+ 'e' <json_number>+ <json_number> ::= '+' | '-' | '.' | [0-9] | 'E' | 'e' <json_string'> ::= <json_string>* '"' <json_list'> ::= ']' | <json_raw> (','<json_raw>)* ']' | ( ',' <json_raw>)+ (',' <json_raw>)* ']' <json_dict'> ::= '}' | ( '"' <json_string'> ':' <json_raw> ',' )* '"'<json_string'> ':' <json_raw> '}' <json_string> ::= ' ' | '!' | '#' | '$' | '%' | '&' | ''' | '*' | '+' | '-' | ',' | '.' | '/' | ':' | ';' | '<' | '=' | '>' | '?' | '@' | '[' | ']' | '^' | '_', ''',| '{' | '|' | '}' | '~' | '[A-Za-z0-9]' | '\' <decode_escape> <decode_escape> ::= '"' | '/' | 'b' | 'f' | 'n' | 'r' | 't' stm.next() if expect_key: raise JSONError(E_DKEY, stm, stm.pos) if c == '}': return result expect_key = 1 continue # parse out a key/value pair elif c == '"': key = _from_json_string(stm) stm.skipspaces() c = stm.next() if c != ':': raise JSONError(E_COLON, stm, stm.pos) stm.skipspaces() val = _from_json_raw(stm) result[key] = val expect_key = 0 continue raise JSONError(E_MALF, stm, stm.pos) def _from_json_raw(stm): while True: stm.skipspaces() c = stm.peek() if c == '"': return _from_json_string(stm) elif c == '{': return _from_json_dict(stm) elif c == '[': return _from_json_list(stm) elif c == 't': return _from_json_fixed(stm, 'true', True, E_BOOL) elif c == 'f': return _from_json_fixed(stm, 'false', False, E_BOOL) elif c == 'n': return _from_json_fixed(stm, 'null', None, E_NULL) elif c in NUMSTART: return _from_json_number(stm) raise JSONError(E_MALF, stm, stm.pos) def from_json(data): stm = JSONStream(data) return _from_json_raw(stm) microjson.py Recovered JSON grammar
  54. 98

  55. 99

  56. 100

  57. 102 The Oracle Problem • You fuzz the program with

    an input. • The program runs. • It fi nishes. Was the answer correct?
  58. 103 The Oracle Problem Was the answer correct? Option 1:

    Di ff erential Testing Verify input/output against other implementations
  59. 104 The Oracle Problem Was the answer correct? Option 1:

    Di ff erential Testing Constraint: Need access to di ff erent program implementations (This is the basis for regression testing)
  60. 105 The Oracle Problem Was the answer correct? Option 1:

    Di ff erential Testing (This is the basis for regression testing) Option 2: Property Based Testing Constraint: Need access to di ff erent program implementations assert f(x) == y
  61. 106 The Oracle Problem Was the answer correct? Option 1:

    Di ff erential Testing (This is the basis for regression testing) Option 2: Property Based Testing 3: Metamorphic Testing Constraint: Need access to di ff erent program implementations assert f(x + c) == f(x) + c
  62. 107 The Oracle Problem Was the answer correct? Option 1:

    Di ff erential Testing (This is the basis for regression testing) Option 2: Property Based Testing 3: Metamorphic Testing Constraint: Need access to di ff erent program implementations Constraint: Need speci fi cations
 (or intelligence)
  63. 108 The Oracle Problem Was the answer correct? Option 1:

    Di ff erential Testing (This is the basis for regression testing) Option 2: Property Based Testing 3: Metamorphic Testing Constraint: Need access to di ff erent program implementations Constraint: Need speci fi cations
 (or intelligence) Option 4: Symbolic Execution
  64. 109 The Oracle Problem Was the answer correct? Option 1:

    Di ff erential Testing (This is the basis for regression testing) Option 2: Property Based Testing 3: Metamorphic Testing Constraint: Need access to di ff erent program implementations Constraint: Need speci fi cations
 (or intelligence) Option 4: Symbolic Execution
 5: Fuzzing
  65. 110 The Oracle Problem Was the answer correct? Option 1:

    Di ff erential Testing (This is the basis for regression testing) Option 2: Property Based Testing 3: Metamorphic Testing Constraint: Need access to di ff erent program implementations Constraint: Need speci fi cations
 (or intelligence) Option 4: Symbolic Execution
 5: Fuzzing Constraint: Limited general oracles
 (e.g. sanitizers)
  66. 112 The Oracle Problem: Solutions Normal execution establishes patterns: variables

    stay within ranges, relationships hold.
 A rare execution is one where those patterns break. That breakage is a potential bug.
  67. Option 1: Di ff erential Testing Option 2: Property Based

    Testing 3: Metamorphic Testing Constraint: Need access to di ff erent program implementations Constraint: Need speci fi cations
 (or intelligence) Option 4: Symbolic Execution
 5: Fuzzing Constraint: Limited general oracles
 (e.g. sanitizers) 113 The Oracle Problem Was the answer correct? (This is the basis for regression testing) Option 6: Approximate Oracles Daikon Runtime Invariant Miner Ernst et al. 2001
  68. 115 Daikon: Core Idea def triangle(a, b, c): if a

    == b: if b == c: return Equilateral else: return Isosceles else: if b == c: return Isosceles else: if a == c: return Isosceles else: return Scalene • Run the program on a bunch of inputs • Watch every variable at key points. • Find properties that always hold. triangle(3, 3, 3) → ENTER: a=3, b=3, c=3 EXIT: return Equilateral triangle(3, 3, 4) → ENTER: a=3, b=3, c=4 EXIT: return Isosceles triangle(2, 2, 4) → ENTER: a=3, b=3, c=4 EXIT: return Isosceles
  69. 116 Daikon: Core Idea def triangle(a, b, c): if a

    == b: if b == c: return Equilateral else: return Isosceles else: if b == c: return Isosceles else: if a == c: return Isosceles else: return Scalene • Run the program on a bunch of inputs • Watch every variable at key points. • Find properties that always hold. triangle(3, 3, 3) → ENTER: a=3, b=3, c=3 EXIT: return Equilateral triangle(3, 3, 4) → ENTER: a=3, b=3, c=4 EXIT: return Isosceles triangle(2, 2, 4) → ENTER: a=2, b=2, c=4 EXIT: return Isosceles After observing many runs: a >= 0 ✓ held every time → likely invariant a == b ✓ held every time → likely invariant
  70. 117 Daikon: Core Idea def triangle(a, b, c): if a

    == b: if b == c: return Equilateral else: return Isosceles else: if b == c: return Isosceles else: if a == c: return Isosceles else: return Scalene • Run the program on a bunch of inputs • Watch every variable at key points. • Find properties that always hold. triangle(3, 3, 3) → ENTER: a=3, b=3, c=3 EXIT: return Equilateral triangle(3, 3, 4) → ENTER: a=3, b=3, c=4 EXIT: return Isosceles triangle(2, 2, 3) → ENTER: a=2, b=2, c=3 EXIT: return Isosceles After observing many runs: a >= 0 ✓ held every time → likely invariant a == b ✘ held every time → likely invariant a + b < c ✓ held every time → likely invariant
  71. 118 Daikon: Core Idea def triangle(a, b, c): if a

    == b: if b == c: return Equilateral else: return Isosceles else: if b == c: return Isosceles else: if a == c: return Isosceles else: return Scalene • Run the program on a bunch of inputs • Watch every variable at key points. • Find properties that always hold. triangle(3, 3, 3) → ENTER: a=3, b=3, c=3 EXIT: return Equilateral triangle(3, 3, 4) → ENTER: a=3, b=3, c=4 EXIT: return Isosceles triangle(2, 2, 3) → ENTER: a=2, b=2, c=3 EXIT: return Isosceles triangle(2, 3, 4) → ENTER: a=2, b=3, c=4 EXIT: return Scalene After observing many runs: a >= 0 ✓ held every time → likely invariant a == b ✘ not held → discarded a + b < c ✓ held every time → likely invariant
  72. 119 Daikon: Core Idea def triangle(a, b, c): if a

    == b: if b == c: return Equilateral else: return Isosceles else: if b == c: return Isosceles else: if a == c: return Isosceles else: return Scalene • Run the program on a bunch of inputs • Watch every variable at key points. • Find properties that always hold. triangle(3, 3, 3) → ENTER: a=3, b=3, c=3 EXIT: return Equilateral triangle(3, 3, 4) → ENTER: a=3, b=3, c=4 EXIT: return Isosceles triangle(2, 2, 3) → ENTER: a=2, b=2, c=3 EXIT: return Isosceles triangle(2, 3, 4) → ENTER: a=2, b=3, c=4 EXIT: return Scalene After observing many runs: a >= 0 ✓ held every time → likely invariant a + b < c ✓ held every time → likely invariant
  73. 120 Daikon: Core Idea def triangle(a, b, c): if a

    == b: if b == c: return Equilateral else: return Isosceles else: if b == c: return Isosceles else: if a == c: return Isosceles else: return Scalene •These are likely invariants, not proofs.
 •They describe what the code does, not what it should do.
  74. 121 Program Points • variable values (and invariants) are anchored

    to where they were observed.
 
 triangle:::ENTER ← variable values when function is called
 triangle:::EXIT ← variable values (+ return value) when it returns • The same property at `ENTER` and at `EXIT` are two separate invariants. • A variable might be `>= 0` on the way in but not on the way out. • This is Daikon's key design decision: invariants are always located. http://localhost:8888/notebooks/x6_0_SimpleInvariantMiner.ipynb#Program-Points
  75. 122 Candidate Templates The miner generates templates for every variable

    it sees, then kills them when they fail. Unary (one variable): - x >= 0 - type(x) is int - x is not None Binary (pairs of variables): - x == y
 - x <= y
 - x >= y 
 Ternary and so on. Any template that fails even once is discarded. Survivors are your invariants. The template set is the limit of what the miner can discover. Add templates, discover more. http://localhost:8888/notebooks/x6_0_SimpleInvariantMiner.ipynb#Candidate-Invariants
  76. 123 Suppression: Cutting the Noise The suppression step removes weaker

    invariants dominated by stronger ones: x == y → 
 x <= y 
 x >= y
 x is not None
 y is not None http://localhost:8888/notebooks/x6_0_SimpleInvariantMiner.ipynb#Suppression
  77. What To Do with Invariants Invariants become assertions in your

    test suite def test_triangle_invariants(a, b, c): result = triangle(a, b, c) assert a >= 0 assert b >= 0 assert c >= 0 assert isinstance(result, str) Mined from triangle runs — auto-generated oracle
  78. 125 Leveraging Uncommon Executions An invariant violation means the program

    has entered a state it has never been in before. • Rare executions are worth investigating --- because their correctness has never been veri fi ed. • If unusual, fl ag the current input as a seed for further fuzzing • If extreme, fl ag for manual inspection
  79. 126

  80. 129 Which Fuzzer Should We Use? Exponential growth in fuzzing

    literature Cumulative publications in fuzzing Cumulative published vulnerabilities
  81. 130 Which Evaluation Metric Should I Use? • Comparable •

    Ground truth • Unbiased • Budget friendly
  82. 131 New CVEs Found? CVE, short for Common Vulnerabilities and

    Exposures, is a list of publicly disclosed computer security fl aws.
  83. 132 • Comparable • Ground truth • Unbiased • Budget

    friendly Which Fuzzer Should I Use? New CVEs ? ?
  84. 134 • Gets saturated quickly (Signal is lost) • Is

    reachability sufficient? • Limited evaluation of complex input conditions involved in expressing bugs • Are more complex coverage techniques needed? Coverage?
  85. 135 • Comparable • Ground truth • Unbiased • Budget

    friendly Which Fuzzer Should I Use? New CVEs ? ? Coverage
  86. 137 Synthetic Bugs? LAVA: Large-scale Automated Vulnerability Addition • What

    kind of bugs should be seeded? • What about unknown kinds of bugs? • Where should these bugs be seeded?
  87. 138 • Comparable • Ground truth • Unbiased • Budget

    friendly Which Fuzzer Should I Use? New CVEs ? ? Coverage Synthetic (LAVA) ? ?
  88. 141 Known Bug Benchmarks? • Gets outdated quickly (overfitting by

    fuzzers) • Significant effort in creating and maintaining • Biased as to where and what kind of bugs are present "When a measure becomes a target it ceases to be a good measure" Goodhart's law
  89. 142 • Comparable • Ground truth • Unbiased • Budget

    friendly Which Fuzzer Should I Use? New CVEs ? ? Coverage Synthetic (LAVA) ? ? Benchm arks (M AGM A) ?
  90. 144 New Bugs in Existing Benchmarks • No ground truth

    • Bug distribution is dependent on external factors • Can lead researchers to postpone publication of vulnerabilities • Feedback can't be used to decide budgeting.
  91. 145 • Comparable • Ground truth • Unbiased • Budget

    friendly Which Fuzzer Should I Use? New CVEs ? ? Coverage Synthetic (LAVA) ? ? Benchm arks (M AGM A) ? New Bugs in old Benchm arks ? Can
  92. 146

  93. 148 unsigned int len = message_length(msg); if (len < MAX_BUF_LEN)

    { copy_message(msg); } else { // Invalid length, handle error } What Is Mutation Testing? IDEA: Induce a program variation with each valid token replacement
  94. 149 unsigned int len = message_length(msg); if (len < MAX_BUF_LEN

    + 1) { copy_message(msg); } else { // Invalid length, handle error } What Is Mutation Testing? Fixes for independent bugs are almost always simple. Finite syntactic size for faults
 (aka. competent programmer hypothesis): Gopinath, Jensen, and Groce Mutations: How Close are they to Real Faults? 2014 ISSRE
  95. 150 unsigned int len = message_length(msg); if (len < MAX_BUF_LEN)

    { copy_message(msg); } else { // Invalid length, handle error } What Is Mutation Testing? (1) IDEA: Induce a program variation with each valid token replacement
  96. 151 unsigned int len = message_length(msg); if (len < >=

    MAX_BUF_LEN) { copy_message(msg); } else { // Invalid length, handle error } What Is Mutation Testing? (2) IDEA: Induce a program variation with each valid token replacement
  97. 152 unsigned int len = message_length(msg); if (len < MAX_BUF_LEN

    + 1) { copy_message(msg); } else { // Invalid length, handle error } What Is Mutation Testing? (3) IDEA: Induce a program variation with each valid token replacement
  98. 153 unsigned int len = message_length(msg); if (len < MAX_BUF_LEN

    + 16) { copy_message(msg); } else { // Invalid length, handle error } What Is Mutation Testing? Complex bugs are almost always coupled to simpler bugs. Finite semantic depth for failures
 aka. Coupling e ff ect hypothesis: Gopinath, Jensen, and Groce Mutations: How Close are they to Real Faults? 2017 ICST
  99. 155 Mutation Testing Process m4 m3 m2 m1 I1 I2

    I4 I3 I5 Generate Mutants Generate Fuzz Inputs > Detect Differences from Original > Mutation Score = Detected Mutants / Generated Mutants Original
  100. 156 M mutants (1 input) Number of Mutants Executions Total

    Campaign Effort for mutation testing = MxN program executions Mutation Testing Challenge N inputs (1 mutant) Number of Inputs Executions
  101. 157 What Is The Problem? Computation: Fuzzing -- More executions

    the better Mutation testing -- Execute each input per mutation Solution: 1) Perform coverage analysis fi rst; remove trivial mutants 2) Evaluate independent (non-interacting) mutations simultaneously 3) Remove redundancy in executions
  102. https://rahul.gopinath.org Traditional execution 158 m1 m2 m3 Setup for T1

    Setup for T2 Actual tests Mutants are executed in parallel But a majority of time spent in initialization (an average 7 times the test execution time) (Bell 2014)
  103. https://rahul.gopinath.org Split-Stream Execution 159 Setup for T1 Setup for T2

    Actual tests Execute tests in parallel Fork off mutants as they are encountered T1 T2 Gopinath, Jensen, Groce “Topsy Turvy: A faster and smarter algorithm” ICSE 2016
  104. 161 • Comparable • Ground truth • Unbiased • Budget

    friendly Mutation Testing New CVEs ? ? Coverage Synthetic (LAVA) ? ? Benchm arks (M AGM A) ? New Bugs in old Benchm arks ? M utation Testing ?
  105. 162

  106. 163 Seeded Fault Benchmarks • Easy to fine-tune a fuzzer

    to overfit • Faults are rarely similar to real faults • Based on bugs we know about! • Human bias in bug curation • Limited supply • Bug interactions requiring deduplication
  107. 164 Mutation Analysis • Easy to fine-tune a fuzzer to

    overfit • Faults are rarely similar to real faults • Based on bugs we know about! • Human bias in bug curation • Limited supply • Bug interactions requiring deduplication
  108. 165 Mutation Analysis • Easy to fine-tune a fuzzer to

    overfit Very large number of faults • Faults are rarely similar to real faults • Based on bugs we know about! • Human bias in bug curation • Limited supply • Bug interactions requiring deduplication
  109. 166 Mutation Analysis • Not easy to fine-tune • Faults

    are rarely similar to real faults • Based on bugs we know about! • Human bias in bug curation • Limited supply • Bug interactions requiring deduplication
  110. 167 Mutation Analysis • Not easy to fine-tune • Faults

    are rarely similar to real faults Evidence that mutants are similar to real faults. • Based on bugs we know about! • Human bias in bug curation • Limited supply • Bug interactions requiring deduplication
  111. 168 Mutation Analysis • Not easy to fine-tune • Evidence

    that mutants are similar to real faults. • Based on bugs we know about! • Human bias in bug curation • Limited supply • Bug interactions requiring deduplication
  112. 169 Mutation Analysis • Not easy to fine-tune • Evidence

    that mutants are similar to real faults. • Based on bugs we know about! All possible faults, including unknown ones! • Human bias in bug curation • Limited supply • Bug interactions requiring deduplication
  113. 170 Mutation Analysis • Not easy to fine-tune • Evidence

    that mutants are similar to real faults. • All possible faults, including unknown ones! • Human bias in bug curation • Limited supply • Bug interactions requiring deduplication
  114. 171 Mutation Analysis • Not easy to fine-tune • Evidence

    that mutants are similar to real faults. • All possible faults, including unknown ones! • Human bias in bug curation No human bias in introduced faults! • Limited supply • Bug interactions requiring deduplication
  115. 172 Mutation Analysis • Not easy to fine-tune • Evidence

    that mutants are similar to real faults. • All possible faults, including unknown ones! • No human bias in introduced faults! • Limited supply • Bug interactions requiring deduplication
  116. 173 Mutation Analysis • Not easy to fine-tune • Evidence

    that mutants are similar to real faults. • All possible faults, including unknown ones! • No human bias in introduced faults! • Limited supply As many as required! Including higher order ones! • Bug interactions requiring deduplication
  117. 174 Mutation Analysis • Not easy to fine-tune • Evidence

    that mutants are similar to real faults. • All possible faults, including unknown ones! • No human bias in introduced faults! • As many as required! Including higher order ones! • Bug interactions requiring deduplication
  118. 175 Mutation Analysis • Not easy to fine-tune • Evidence

    that mutants are similar to real faults. • All possible faults, including unknown ones! • No human bias in introduced faults! • As many as required! Including higher order ones! • Bug interactions requiring deduplication All mutants are evaluated independently
  119. 176 Mutation Analysis • Not easy to fine-tune • Evidence

    that mutants are similar to real faults. • All possible faults, including unknown ones! • No human bias in introduced faults! • As many as required! Including higher order ones! • All mutants are evaluated indepdently
  120. 177

  121. 178

  122. 179

  123. 180

  124. 181