[{"data":1,"prerenderedAt":1089},["ShallowReactive",2],{"page-\u002Fproperty-based-fuzz-testing-strategies\u002Fcoverage-guided-fuzzing-with-atheris\u002Fwriting-your-first-atheris-fuzz-target\u002F":3},{"id":4,"title":5,"body":6,"description":1055,"extension":1056,"meta":1057,"navigation":97,"path":1085,"seo":1086,"stem":1087,"__hash__":1088},"content\u002Fproperty-based-fuzz-testing-strategies\u002Fcoverage-guided-fuzzing-with-atheris\u002Fwriting-your-first-atheris-fuzz-target\u002Findex.md","Writing Your First Atheris Fuzz Target",{"type":7,"value":8,"toc":1044},"minimark",[9,18,23,50,54,57,193,220,223,270,273,438,442,448,550,554,557,565,568,629,632,636,639,654,669,672,717,730,733,812,816,870,874,877,883,892,902,937,948,952,966,979,996,1002,1006,1034,1040],[10,11,12,13,17],"p",{},"The first fuzz target usually fails in one of two ways: it reports zero coverage growth because instrumentation was not applied, or it reports a thousand crashes because the oracle treats a documented ",[14,15,16],"code",{},"ValueError"," as a bug. Both are one-line problems, and getting them right the first time is most of the work of adopting fuzzing.",[19,20,22],"h2",{"id":21},"prerequisites","Prerequisites",[24,25,26,38,41],"ul",{},[27,28,29,33,34,37],"li",{},[30,31,32],"strong",{},"atheris 2.3+",", ",[30,35,36],{},"Python 3.8+",", Linux or macOS.",[27,39,40],{},"A function that consumes untrusted input — a parser, decoder, or validator.",[27,42,43,44,49],{},"The loop model in ",[45,46,48],"a",{"href":47},"\u002Fproperty-based-fuzz-testing-strategies\u002Fcoverage-guided-fuzzing-with-atheris\u002F","coverage-guided fuzzing with Atheris",".",[19,51,53],{"id":52},"solution","Solution",[10,55,56],{},"A complete target is about fifteen lines. Every one of them matters.",[58,59,64],"pre",{"className":60,"code":61,"language":62,"meta":63,"style":63},"language-python shiki shiki-themes github-light github-dark","#!\u002Fusr\u002Fbin\u002Fenv python3\n\"\"\"Fuzz the wire-format decoder. Run: python fuzz_decode.py corpus\u002F\"\"\"\nimport sys\nimport atheris\n\n# Instrumentation must wrap the import: this is what produces coverage feedback.\nwith atheris.instrument_imports():\n    from myapp.wire import decode_frame, FrameError\n\ndef TestOneInput(data: bytes) -> None:\n    fdp = atheris.FuzzedDataProvider(data)\n    version = fdp.ConsumeIntInRange(0, 3)          # a small, meaningful field\n    body = fdp.ConsumeBytes(fdp.remaining_bytes())  # the rest is payload\n    try:\n        decode_frame(version, body)\n    except FrameError:\n        return                                      # documented rejection: not a bug\n\nif __name__ == \"__main__\":\n    atheris.Setup(sys.argv, TestOneInput)\n    atheris.Fuzz()\n","python","",[14,65,66,74,80,86,92,99,105,111,117,122,128,134,140,146,152,158,164,170,175,181,187],{"__ignoreMap":63},[67,68,71],"span",{"class":69,"line":70},"line",1,[67,72,73],{},"#!\u002Fusr\u002Fbin\u002Fenv python3\n",[67,75,77],{"class":69,"line":76},2,[67,78,79],{},"\"\"\"Fuzz the wire-format decoder. Run: python fuzz_decode.py corpus\u002F\"\"\"\n",[67,81,83],{"class":69,"line":82},3,[67,84,85],{},"import sys\n",[67,87,89],{"class":69,"line":88},4,[67,90,91],{},"import atheris\n",[67,93,95],{"class":69,"line":94},5,[67,96,98],{"emptyLinePlaceholder":97},true,"\n",[67,100,102],{"class":69,"line":101},6,[67,103,104],{},"# Instrumentation must wrap the import: this is what produces coverage feedback.\n",[67,106,108],{"class":69,"line":107},7,[67,109,110],{},"with atheris.instrument_imports():\n",[67,112,114],{"class":69,"line":113},8,[67,115,116],{},"    from myapp.wire import decode_frame, FrameError\n",[67,118,120],{"class":69,"line":119},9,[67,121,98],{"emptyLinePlaceholder":97},[67,123,125],{"class":69,"line":124},10,[67,126,127],{},"def TestOneInput(data: bytes) -> None:\n",[67,129,131],{"class":69,"line":130},11,[67,132,133],{},"    fdp = atheris.FuzzedDataProvider(data)\n",[67,135,137],{"class":69,"line":136},12,[67,138,139],{},"    version = fdp.ConsumeIntInRange(0, 3)          # a small, meaningful field\n",[67,141,143],{"class":69,"line":142},13,[67,144,145],{},"    body = fdp.ConsumeBytes(fdp.remaining_bytes())  # the rest is payload\n",[67,147,149],{"class":69,"line":148},14,[67,150,151],{},"    try:\n",[67,153,155],{"class":69,"line":154},15,[67,156,157],{},"        decode_frame(version, body)\n",[67,159,161],{"class":69,"line":160},16,[67,162,163],{},"    except FrameError:\n",[67,165,167],{"class":69,"line":166},17,[67,168,169],{},"        return                                      # documented rejection: not a bug\n",[67,171,173],{"class":69,"line":172},18,[67,174,98],{"emptyLinePlaceholder":97},[67,176,178],{"class":69,"line":177},19,[67,179,180],{},"if __name__ == \"__main__\":\n",[67,182,184],{"class":69,"line":183},20,[67,185,186],{},"    atheris.Setup(sys.argv, TestOneInput)\n",[67,188,190],{"class":69,"line":189},21,[67,191,192],{},"    atheris.Fuzz()\n",[10,194,195,196,199,200,203,204,207,208,211,212,215,216,219],{},"The ",[14,197,198],{},"except FrameError"," line is the oracle, stated precisely: malformed input must raise ",[14,201,202],{},"FrameError"," and nothing else. A ",[14,205,206],{},"TypeError",", an ",[14,209,210],{},"IndexError",", a ",[14,213,214],{},"RecursionError"," or a hang is a finding. Broadening that clause to ",[14,217,218],{},"except Exception"," would make the target incapable of failing.",[10,221,222],{},"Run it against a seeded corpus and a time budget:",[58,224,228],{"className":225,"code":226,"language":227,"meta":63,"style":63},"language-console shiki shiki-themes github-light github-dark","$ mkdir -p corpus && cp tests\u002Fdata\u002Fframes\u002F*.bin corpus\u002F\n$ python fuzz_decode.py corpus\u002F -max_total_time=120 -print_final_stats=1\nINFO: Running with entropic power schedule (0xFF, 100).\n#512    NEW    cov: 188 ft: 291 corp: 12\u002F842b exec\u002Fs: 6300\n#2048   NEW    cov: 241 ft: 402 corp: 23\u002F1.9Kb exec\u002Fs: 6100\n==12345== ERROR: libFuzzer: deadly signal\n   ... Python traceback ...\nartifact_prefix='.\u002F'; Test unit written to .\u002Fcrash-8f14e45fceea167a\n","console",[14,229,230,235,240,245,250,255,260,265],{"__ignoreMap":63},[67,231,232],{"class":69,"line":70},[67,233,234],{},"$ mkdir -p corpus && cp tests\u002Fdata\u002Fframes\u002F*.bin corpus\u002F\n",[67,236,237],{"class":69,"line":76},[67,238,239],{},"$ python fuzz_decode.py corpus\u002F -max_total_time=120 -print_final_stats=1\n",[67,241,242],{"class":69,"line":82},[67,243,244],{},"INFO: Running with entropic power schedule (0xFF, 100).\n",[67,246,247],{"class":69,"line":88},[67,248,249],{},"#512    NEW    cov: 188 ft: 291 corp: 12\u002F842b exec\u002Fs: 6300\n",[67,251,252],{"class":69,"line":94},[67,253,254],{},"#2048   NEW    cov: 241 ft: 402 corp: 23\u002F1.9Kb exec\u002Fs: 6100\n",[67,256,257],{"class":69,"line":101},[67,258,259],{},"==12345== ERROR: libFuzzer: deadly signal\n",[67,261,262],{"class":69,"line":107},[67,263,264],{},"   ... Python traceback ...\n",[67,266,267],{"class":69,"line":113},[67,268,269],{},"artifact_prefix='.\u002F'; Test unit written to .\u002Fcrash-8f14e45fceea167a\n",[10,271,272],{},"The crash artefact is the payoff: a file containing the exact bytes that broke the decoder, replayable forever.",[274,275,278,434],"figure",{"className":276},[277],"diagram",[279,280,287,288,287,292,287,296,287,314,287,322,287,331,287,339,287,345,287,350,287,355,287,364,287,369,287,373,287,377,287,381,287,385,287,389,287,393,287,396,287,400,287,404,287,408,287,412,287,416,287,419,287,422,287,426,287,430],"svg",{"viewBox":281,"role":282,"ariaLabelledBy":283,"xmlns":286},"0 0 760 434","img",[284,285],"fuzztarget-t","fuzztarget-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[289,290,291],"title",{"id":284},"The four parts of a fuzz target",[293,294,295],"desc",{"id":285},"A vertical breakdown of a fuzz target: instrumented imports that enable coverage feedback, an input decoder that turns bytes into typed arguments, the call into the code under test, and the oracle that decides which exceptions are findings.",[297,298,299,300,287],"defs",{},"\n    ",[301,302,309],"marker",{"id":303,"viewBox":304,"refX":305,"refY":306,"markerWidth":307,"markerHeight":307,"orient":308},"fuzztarget-a","0 0 10 10","9","5","7","auto-start-reverse",[310,311],"path",{"d":312,"fill":313},"M0 0 L10 5 L0 10 z","#81b29a",[315,316],"rect",{"x":317,"y":317,"width":318,"height":319,"rx":320,"fill":321},"0","760","434","14","#fffdf8",[323,324,291],"text",{"x":325,"y":326,"textAnchor":327,"fontSize":328,"fontWeight":329,"fill":330},"380","30","middle","15","700","#3d405b",[315,332],{"x":333,"y":334,"width":335,"height":336,"rx":337,"fill":321,"stroke":313,"strokeWidth":338},"34","58","320","64","12","1.8",[323,340,344],{"x":341,"y":342,"textAnchor":327,"fontSize":343,"fontWeight":329,"fill":330},"194","86","12.5","instrumented import",[323,346,349],{"x":341,"y":347,"textAnchor":327,"fontSize":348,"fill":330},"103","11","coverage feedback exists",[69,351],{"x1":341,"y1":352,"x2":341,"y2":353,"stroke":313,"strokeWidth":338,"markerEnd":354},"128","149","url(#fuzztarget-a)",[315,356],{"x":357,"y":336,"width":358,"height":359,"rx":360,"fill":321,"stroke":313,"strokeWidth":361,"strokeDashArray":362},"384","346","52","10","1.5",[306,363],"4",[323,365,368],{"x":366,"y":367,"textAnchor":327,"fontSize":348,"fill":330},"557","93","Outside the block means no coverage",[315,370],{"x":333,"y":371,"width":335,"height":336,"rx":337,"fill":372,"stroke":313,"strokeWidth":338},"156","#f4f1de",[323,374,376],{"x":341,"y":375,"textAnchor":327,"fontSize":343,"fontWeight":329,"fill":330},"184","FuzzedDataProvider",[323,378,380],{"x":341,"y":379,"textAnchor":327,"fontSize":348,"fill":330},"201","bytes become typed values",[69,382],{"x1":341,"y1":383,"x2":341,"y2":384,"stroke":313,"strokeWidth":338,"markerEnd":354},"226","247",[315,386],{"x":357,"y":387,"width":358,"height":359,"rx":360,"fill":372,"stroke":313,"strokeWidth":361,"strokeDashArray":388},"162",[306,363],[323,390,392],{"x":366,"y":391,"textAnchor":327,"fontSize":348,"fill":330},"191","Keeps mutations meaningful",[315,394],{"x":333,"y":395,"width":335,"height":336,"rx":337,"fill":321,"stroke":313,"strokeWidth":338},"254",[323,397,399],{"x":341,"y":398,"textAnchor":327,"fontSize":343,"fontWeight":329,"fill":330},"282","call the code",[323,401,403],{"x":341,"y":402,"textAnchor":327,"fontSize":348,"fill":330},"299","one input, one call",[69,405],{"x1":341,"y1":406,"x2":341,"y2":407,"stroke":313,"strokeWidth":338,"markerEnd":354},"324","345",[315,409],{"x":357,"y":410,"width":358,"height":359,"rx":360,"fill":321,"stroke":313,"strokeWidth":361,"strokeDashArray":411},"260",[306,363],[323,413,415],{"x":366,"y":414,"textAnchor":327,"fontSize":348,"fill":330},"289","No I\u002FO, no setup, no logging",[315,417],{"x":333,"y":418,"width":335,"height":336,"rx":337,"fill":372,"stroke":313,"strokeWidth":338},"352",[323,420,421],{"x":341,"y":325,"textAnchor":327,"fontSize":343,"fontWeight":329,"fill":330},"the oracle",[323,423,425],{"x":341,"y":424,"textAnchor":327,"fontSize":348,"fill":330},"397","which exceptions are bugs",[315,427],{"x":357,"y":428,"width":358,"height":359,"rx":360,"fill":372,"stroke":313,"strokeWidth":361,"strokeDashArray":429},"358",[306,363],[323,431,433],{"x":366,"y":432,"textAnchor":327,"fontSize":348,"fill":330},"387","Narrow, documented exceptions only",[435,436,437],"figcaption",{},"Getting the first and last parts right is the difference between a fuzzer that searches and one that spins.",[19,439,441],{"id":440},"why-this-works","Why this works",[10,443,444,445,447],{},"Atheris rewrites the bytecode of instrumented modules to record which edges execute, and hands that trace to libFuzzer after every input. libFuzzer keeps any input that reached a previously unseen edge, and mutates the corpus preferentially toward inputs that increase coverage. ",[14,446,376],{}," matters because mutation happens on the raw buffer: consuming a bounded integer from the front means a single bit flip changes a version field rather than corrupting a length prefix that causes immediate rejection, so more mutations reach interesting code.",[274,449,451,547],{"className":450},[277],[279,452,287,457,287,460,287,463,287,466,287,468,287,474,287,479,287,483,287,487,287,491,287,496,287,499,287,502,287,505,287,508,287,511,287,514,287,517,287,521,287,524,287,527,287,530,287,534,287,537,287,540,287,544],{"viewBox":453,"role":282,"ariaLabelledBy":454,"xmlns":286},"0 0 760 270",[455,456],"fdpmethods-t","fdpmethods-d",[289,458,459],{"id":455},"FuzzedDataProvider methods worth knowing",[293,461,462],{"id":456},"A table of four FuzzedDataProvider methods - ConsumeIntInRange, ConsumeUnicodeNoSurrogates, ConsumeBytes and ConsumeProbability - describing what each yields and the typical use.",[315,464],{"x":317,"y":317,"width":318,"height":465,"rx":320,"fill":321},"270",[323,467,459],{"x":325,"y":326,"textAnchor":327,"fontSize":328,"fontWeight":329,"fill":330},[315,469],{"x":470,"y":359,"width":471,"height":472,"rx":360,"fill":473,"stroke":330,"strokeWidth":361},"24","712","40","#f2cc8f",[323,475,478],{"x":476,"y":477,"fontSize":337,"fontWeight":329,"fill":330},"38","76","Criterion",[323,480,482],{"x":481,"y":477,"textAnchor":327,"fontSize":337,"fontWeight":329,"fill":330},"390","Yields",[323,484,486],{"x":485,"y":477,"textAnchor":327,"fontSize":337,"fontWeight":329,"fill":330},"620","Use for",[315,488],{"x":470,"y":489,"width":471,"height":472,"fill":372,"stroke":330,"strokeWidth":490},"92","1",[323,492,495],{"x":476,"y":493,"fontSize":494,"fill":330},"116","11.5","ConsumeIntInRange(a, b)",[323,497,498],{"x":481,"y":493,"textAnchor":327,"fontSize":494,"fill":330},"a bounded int",[323,500,501],{"x":485,"y":493,"textAnchor":327,"fontSize":494,"fill":330},"enums, versions, counts",[315,503],{"x":470,"y":504,"width":471,"height":472,"fill":321,"stroke":330,"strokeWidth":490},"132",[323,506,507],{"x":476,"y":371,"fontSize":494,"fill":330},"ConsumeUnicodeNoSurrogates(n)",[323,509,510],{"x":481,"y":371,"textAnchor":327,"fontSize":494,"fill":330},"valid text",[323,512,513],{"x":485,"y":371,"textAnchor":327,"fontSize":494,"fill":330},"names, keys, headers",[315,515],{"x":470,"y":516,"width":471,"height":472,"fill":372,"stroke":330,"strokeWidth":490},"172",[323,518,520],{"x":476,"y":519,"fontSize":494,"fill":330},"196","ConsumeBytes(n)",[323,522,523],{"x":481,"y":519,"textAnchor":327,"fontSize":494,"fill":330},"a raw slice",[323,525,526],{"x":485,"y":519,"textAnchor":327,"fontSize":494,"fill":330},"payload bodies",[315,528],{"x":470,"y":529,"width":471,"height":472,"fill":321,"stroke":330,"strokeWidth":490},"212",[323,531,533],{"x":476,"y":532,"fontSize":494,"fill":330},"236","ConsumeProbability()",[323,535,536],{"x":481,"y":532,"textAnchor":327,"fontSize":494,"fill":330},"a float in 0..1",[323,538,539],{"x":485,"y":532,"textAnchor":327,"fontSize":494,"fill":330},"branch selection",[69,541],{"x1":542,"y1":359,"x2":542,"y2":543,"stroke":330,"strokeWidth":490},"274","252",[69,545],{"x1":546,"y1":359,"x2":546,"y2":543,"stroke":330,"strokeWidth":490},"505",[435,548,549],{},"Consume the structured fields first and the free-form payload last, so mutations to the tail cannot shift the meaning of the head.",[19,551,553],{"id":552},"choosing-what-to-fuzz-first","Choosing what to fuzz first",[10,555,556],{},"Not every function benefits. Three properties make a good first target: it takes untrusted or externally supplied input, it is pure enough to run thousands of times per second, and it has a clearly documented set of exceptions it is allowed to raise.",[10,558,559,560,564],{},"Parsers, deserializers, decompressors, template renderers, URL and path handlers, and anything reading a binary format score on all three. Business logic operating on already-validated objects scores on none — its inputs are structured, its failure mode is a wrong answer rather than a crash, and ",[45,561,563],{"href":562},"\u002Fproperty-based-fuzz-testing-strategies\u002Fadvanced-property-based-testing\u002F","property-based testing"," is the better tool for it.",[10,566,567],{},"Where a function performs I\u002FO, fuzz the pure core and leave the shell alone:",[58,569,571],{"className":60,"code":570,"language":62,"meta":63,"style":63},"# Bad target: opens a file per input, at maybe 40 executions per second.\ndef TestOneInput(data: bytes) -> None:\n    with open(\"\u002Ftmp\u002Ff\", \"wb\") as fh:\n        fh.write(data)\n    load_config_file(\"\u002Ftmp\u002Ff\")\n\n# Good target: the parsing core, called directly, thousands per second.\ndef TestOneInput(data: bytes) -> None:\n    try:\n        parse_config(data.decode(\"utf-8\", errors=\"replace\"))\n    except ConfigError:\n        return\n",[14,572,573,578,582,587,592,597,601,606,610,614,619,624],{"__ignoreMap":63},[67,574,575],{"class":69,"line":70},[67,576,577],{},"# Bad target: opens a file per input, at maybe 40 executions per second.\n",[67,579,580],{"class":69,"line":76},[67,581,127],{},[67,583,584],{"class":69,"line":82},[67,585,586],{},"    with open(\"\u002Ftmp\u002Ff\", \"wb\") as fh:\n",[67,588,589],{"class":69,"line":88},[67,590,591],{},"        fh.write(data)\n",[67,593,594],{"class":69,"line":94},[67,595,596],{},"    load_config_file(\"\u002Ftmp\u002Ff\")\n",[67,598,599],{"class":69,"line":101},[67,600,98],{"emptyLinePlaceholder":97},[67,602,603],{"class":69,"line":107},[67,604,605],{},"# Good target: the parsing core, called directly, thousands per second.\n",[67,607,608],{"class":69,"line":113},[67,609,127],{},[67,611,612],{"class":69,"line":119},[67,613,151],{},[67,615,616],{"class":69,"line":124},[67,617,618],{},"        parse_config(data.decode(\"utf-8\", errors=\"replace\"))\n",[67,620,621],{"class":69,"line":130},[67,622,623],{},"    except ConfigError:\n",[67,625,626],{"class":69,"line":136},[67,627,628],{},"        return\n",[10,630,631],{},"Refactoring a function into \"read bytes\" and \"parse bytes\" purely so it can be fuzzed is usually a design improvement anyway — the same seam makes it testable without temporary files.",[19,633,635],{"id":634},"debugging-a-target-that-finds-nothing","Debugging a target that finds nothing",[10,637,638],{},"A target that runs for an hour with no crashes is either exercising well-tested code or not exercising it at all, and the statistics distinguish the two in seconds.",[58,640,642],{"className":225,"code":641,"language":227,"meta":63,"style":63},"$ python fuzz_decode.py corpus\u002F -max_total_time=60 -print_final_stats=1\n#1048576  pulse  cov: 34 ft: 41 corp: 3\u002F21b exec\u002Fs: 17000\n",[14,643,644,649],{"__ignoreMap":63},[67,645,646],{"class":69,"line":70},[67,647,648],{},"$ python fuzz_decode.py corpus\u002F -max_total_time=60 -print_final_stats=1\n",[67,650,651],{"class":69,"line":76},[67,652,653],{},"#1048576  pulse  cov: 34 ft: 41 corp: 3\u002F21b exec\u002Fs: 17000\n",[10,655,656,657,660,661,664,665,668],{},"Three numbers are wrong here. ",[14,658,659],{},"cov: 34"," is far too low for anything but a trivial function — real parsers reach hundreds of edges. ",[14,662,663],{},"corp: 3"," means the corpus never grew past its seeds. And ",[14,666,667],{},"exec\u002Fs: 17000"," is suspiciously high for Python, which usually means the input is being rejected before any real work happens.",[10,670,671],{},"The diagnosis follows. Add a print inside the target to confirm the code is reached at all, then check the rejection path:",[58,673,675],{"className":60,"code":674,"language":62,"meta":63,"style":63},"import sys\n\ndef TestOneInput(data: bytes) -> None:\n    try:\n        decode_frame(data)\n    except FrameError as exc:\n        # Temporarily: which rejection is swallowing every input?\n        print(exc, file=sys.stderr)\n        return\n",[14,676,677,681,685,689,693,698,703,708,713],{"__ignoreMap":63},[67,678,679],{"class":69,"line":70},[67,680,85],{},[67,682,683],{"class":69,"line":76},[67,684,98],{"emptyLinePlaceholder":97},[67,686,687],{"class":69,"line":82},[67,688,127],{},[67,690,691],{"class":69,"line":88},[67,692,151],{},[67,694,695],{"class":69,"line":94},[67,696,697],{},"        decode_frame(data)\n",[67,699,700],{"class":69,"line":101},[67,701,702],{},"    except FrameError as exc:\n",[67,704,705],{"class":69,"line":107},[67,706,707],{},"        # Temporarily: which rejection is swallowing every input?\n",[67,709,710],{"class":69,"line":113},[67,711,712],{},"        print(exc, file=sys.stderr)\n",[67,714,715],{"class":69,"line":119},[67,716,628],{},[10,718,719,720,33,723,33,726,729],{},"Running that for a few seconds usually shows the same message repeatedly — ",[14,721,722],{},"bad magic bytes",[14,724,725],{},"length too short",[14,727,728],{},"unsupported version"," — which names the check the fuzzer cannot get past. The fix is a seed input that satisfies it, so mutation starts on the other side of the gate.",[10,731,732],{},"Where the gate is a checksum, no amount of mutation will pass it. Either compute the checksum inside the target after mutating the body, or add a build flag that disables verification for fuzzing. Both are standard practice; leaving the checksum in place means fuzzing the checksum function and nothing else.",[274,734,736,809],{"className":735},[277],[279,737,287,741,287,744,287,747,287,749,287,751,287,753,287,755,287,758,287,761,287,763,287,766,287,769,287,772,287,774,287,777,287,780,287,783,287,785,287,788,287,791,287,794,287,796,287,799,287,802,287,805,287,807],{"viewBox":453,"role":282,"ariaLabelledBy":738,"xmlns":286},[739,740],"fuzzstats-t","fuzzstats-d",[289,742,743],{"id":739},"Reading the libFuzzer status line",[293,745,746],{"id":740},"A table of four libFuzzer status fields - cov, ft, corp and exec\u002Fs - explaining what each measures and what an unhealthy value indicates.",[315,748],{"x":317,"y":317,"width":318,"height":465,"rx":320,"fill":321},[323,750,743],{"x":325,"y":326,"textAnchor":327,"fontSize":328,"fontWeight":329,"fill":330},[315,752],{"x":470,"y":359,"width":471,"height":472,"rx":360,"fill":473,"stroke":330,"strokeWidth":361},[323,754,478],{"x":476,"y":477,"fontSize":337,"fontWeight":329,"fill":330},[323,756,757],{"x":481,"y":477,"textAnchor":327,"fontSize":337,"fontWeight":329,"fill":330},"Measures",[323,759,760],{"x":485,"y":477,"textAnchor":327,"fontSize":337,"fontWeight":329,"fill":330},"Unhealthy when",[315,762],{"x":470,"y":489,"width":471,"height":472,"fill":372,"stroke":330,"strokeWidth":490},[323,764,765],{"x":476,"y":493,"fontSize":494,"fill":330},"cov",[323,767,768],{"x":481,"y":493,"textAnchor":327,"fontSize":494,"fill":330},"edges covered",[323,770,771],{"x":485,"y":493,"textAnchor":327,"fontSize":494,"fill":330},"flat, or implausibly low",[315,773],{"x":470,"y":504,"width":471,"height":472,"fill":321,"stroke":330,"strokeWidth":490},[323,775,776],{"x":476,"y":371,"fontSize":494,"fill":330},"ft",[323,778,779],{"x":481,"y":371,"textAnchor":327,"fontSize":494,"fill":330},"coverage features",[323,781,782],{"x":485,"y":371,"textAnchor":327,"fontSize":494,"fill":330},"not growing with cov",[315,784],{"x":470,"y":516,"width":471,"height":472,"fill":372,"stroke":330,"strokeWidth":490},[323,786,787],{"x":476,"y":519,"fontSize":494,"fill":330},"corp",[323,789,790],{"x":481,"y":519,"textAnchor":327,"fontSize":494,"fill":330},"corpus size and bytes",[323,792,793],{"x":485,"y":519,"textAnchor":327,"fontSize":494,"fill":330},"stuck at the seed count",[315,795],{"x":470,"y":529,"width":471,"height":472,"fill":321,"stroke":330,"strokeWidth":490},[323,797,798],{"x":476,"y":532,"fontSize":494,"fill":330},"exec\u002Fs",[323,800,801],{"x":481,"y":532,"textAnchor":327,"fontSize":494,"fill":330},"executions per second",[323,803,804],{"x":485,"y":532,"textAnchor":327,"fontSize":494,"fill":330},"very high: early rejection",[69,806],{"x1":542,"y1":359,"x2":542,"y2":543,"stroke":330,"strokeWidth":490},[69,808],{"x1":546,"y1":359,"x2":546,"y2":543,"stroke":330,"strokeWidth":490},[435,810,811],{},"A high execution rate with flat coverage is the classic signature of inputs being rejected before they reach anything interesting.",[19,813,815],{"id":814},"edge-cases-and-failure-modes","Edge cases and failure modes",[24,817,818,831,837,845,854,864],{},[27,819,820,826,827,830],{},[30,821,822,825],{},[14,823,824],{},"instrument_all()"," instruments everything, including dependencies."," It finds deeper bugs and slows execution substantially; start with ",[14,828,829],{},"instrument_imports()"," on your own modules.",[27,832,833,836],{},[30,834,835],{},"A target that mutates global state accumulates it across inputs."," Reset per call, or the crash will not reproduce from the artefact alone.",[27,838,839,844],{},[30,840,841,843],{},[14,842,214],{}," from deeply nested input is usually a real finding"," for a parser, and usually not for a general library; decide deliberately which side of the oracle it falls on.",[27,846,847,853],{},[30,848,849,850,49],{},"Timeouts need ",[14,851,852],{},"-timeout=N"," An input that hangs is a denial-of-service bug, and without the flag the fuzzer waits indefinitely instead of reporting it.",[27,855,856,859,860,863],{},[30,857,858],{},"Crashes are written to the current directory"," unless ",[14,861,862],{},"-artifact_prefix="," is set, which in CI means they land somewhere that is not collected.",[27,865,866,869],{},[30,867,868],{},"Python's own exceptions can mask native ones."," When fuzzing a C extension, build it with AddressSanitizer or the crash arrives as an unexplained signal.",[19,871,873],{"id":872},"keeping-the-target-maintainable","Keeping the target maintainable",[10,875,876],{},"A fuzz target is code that nobody reads until it fails, so it benefits from the same discipline as a test — and from three conventions specific to fuzzing.",[10,878,879,882],{},[30,880,881],{},"One target per entry point."," A target that fuzzes three functions makes attribution ambiguous when it crashes and dilutes the coverage signal. Separate files, separate corpora, separate budgets.",[10,884,885,891],{},[30,886,887,888,49],{},"Keep setup out of ",[14,889,890],{},"TestOneInput"," Anything constructed per input is paid thousands of times per second. Build clients, load schemas and compile patterns at module scope, where the cost is paid once.",[10,893,894,897,898,901],{},[30,895,896],{},"Document the oracle in a comment."," The ",[14,899,900],{},"except"," clause encodes a contract — \"malformed input raises FrameError and nothing else\" — and that sentence belongs above it in words, because the next person will otherwise widen the clause to silence a crash rather than fixing it.",[58,903,905],{"className":60,"code":904,"language":62,"meta":63,"style":63},"# Contract: decode_frame must raise FrameError for any malformed input.\n# Any other exception, or a hang, is a defect. Do NOT widen this clause.\ntry:\n    decode_frame(version, body)\nexcept FrameError:\n    return\n",[14,906,907,912,917,922,927,932],{"__ignoreMap":63},[67,908,909],{"class":69,"line":70},[67,910,911],{},"# Contract: decode_frame must raise FrameError for any malformed input.\n",[67,913,914],{"class":69,"line":76},[67,915,916],{},"# Any other exception, or a hang, is a defect. Do NOT widen this clause.\n",[67,918,919],{"class":69,"line":82},[67,920,921],{},"try:\n",[67,923,924],{"class":69,"line":88},[67,925,926],{},"    decode_frame(version, body)\n",[67,928,929],{"class":69,"line":94},[67,930,931],{},"except FrameError:\n",[67,933,934],{"class":69,"line":101},[67,935,936],{},"    return\n",[10,938,939,940,943,944,947],{},"Finally, run the targets in the normal test suite as a smoke check: calling ",[14,941,942],{},"TestOneInput(b\"\")"," and ",[14,945,946],{},"TestOneInput(b\"\\x00\" * 64)"," from an ordinary pytest case proves the target still imports and runs after a refactor. A fuzz target that silently stopped working is indistinguishable from one that is finding nothing.",[19,949,951],{"id":950},"frequently-asked-questions","Frequently Asked Questions",[10,953,954,957,958,961,962,965],{},[30,955,956],{},"Why does my fuzzer report no coverage growth?","\nBecause the code under test was imported outside ",[14,959,960],{},"atheris.instrument_imports()",". Only modules imported inside that context manager, or wrapped with ",[14,963,964],{},"instrument_func",", report edges to libFuzzer.",[10,967,968,971,972,974,975,978],{},[30,969,970],{},"Should TestOneInput catch exceptions?","\nOnly the ones that are part of the documented contract, such as ",[14,973,16],{}," for malformed input. Catching ",[14,976,977],{},"Exception"," broadly hides every bug the fuzzer would otherwise find.",[10,980,981,984,985,988,989,992,993,995],{},[30,982,983],{},"How do I turn raw bytes into typed arguments?","\nUse ",[14,986,987],{},"atheris.FuzzedDataProvider",", which consumes the buffer into ints, strings and byte slices. It keeps mutations meaningful instead of being spent on structure the parser rejects immediately.\n",[30,990,991],{},"How do I fuzz a function that takes several arguments?","\nDecode them all from one buffer with ",[14,994,376],{},", in a fixed order. The order matters: consume the small structured fields first and the free-form payload last, so a mutation near the start changes a field rather than shifting the meaning of everything after it.",[10,997,998,1001],{},[30,999,1000],{},"Can a fuzz target use pytest fixtures?","\nNot directly — the target runs under libFuzzer, not pytest. Build whatever the target needs at module scope, and if the setup is shared with the test suite, factor it into a plain function that both call. That factoring is usually an improvement to the tests as well.",[19,1003,1005],{"id":1004},"related","Related",[24,1007,1008,1014,1021,1027],{},[27,1009,1010,1013],{},[45,1011,1012],{"href":47},"Coverage-guided fuzzing with Atheris"," — the search loop and how coverage guides it.",[27,1015,1016,1020],{},[45,1017,1019],{"href":1018},"\u002Fproperty-based-fuzz-testing-strategies\u002Fcoverage-guided-fuzzing-with-atheris\u002Frunning-fuzz-targets-in-ci-with-time-budgets\u002F","Running fuzz targets in CI with time budgets"," — budgets, corpus caching and crash regression tests.",[27,1022,1023,1026],{},[45,1024,1025],{"href":562},"Advanced property-based testing with Hypothesis"," — the right tool for validated, structured inputs.",[27,1028,1029,1033],{},[45,1030,1032],{"href":1031},"\u002Fsystematic-debugging-performance-profiling\u002Fdebugging-async-code-and-event-loops\u002F","Debugging async code and event loops"," — when the crash artefact only reproduces inside a running service.",[10,1035,1036,1037],{},"← Back to ",[45,1038,1039],{"href":47},"Coverage-Guided Fuzzing with Atheris",[1041,1042,1043],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":63,"searchDepth":76,"depth":76,"links":1045},[1046,1047,1048,1049,1050,1051,1052,1053,1054],{"id":21,"depth":76,"text":22},{"id":52,"depth":76,"text":53},{"id":440,"depth":76,"text":441},{"id":552,"depth":76,"text":553},{"id":634,"depth":76,"text":635},{"id":814,"depth":76,"text":815},{"id":872,"depth":76,"text":873},{"id":950,"depth":76,"text":951},{"id":1004,"depth":76,"text":1005},"Write a working Atheris fuzz target: instrumented imports, FuzzedDataProvider, choosing the oracle, seeding a corpus, and reading the first crash artefact.","md",{"slug":1058,"type":1059,"breadcrumb":1060,"datePublished":1061,"dateModified":1061,"faq":1062,"howto":1069},"writing-your-first-atheris-fuzz-target","article","First Fuzz Target","2026-08-01",[1063,1065,1067],{"q":956,"a":1064},"Because the code under test was imported outside atheris.instrument_imports(). Only modules imported inside that context manager, or wrapped with instrument_func, report edges to libFuzzer.",{"q":970,"a":1066},"Only the ones that are part of the documented contract, such as ValueError for malformed input. Catching Exception broadly hides every bug the fuzzer would otherwise find.",{"q":983,"a":1068},"Use atheris.FuzzedDataProvider, which consumes the buffer into ints, strings and byte slices. It keeps mutations meaningful instead of being spent on structure the parser rejects immediately.",{"name":1070,"description":1071,"steps":1072},"How to write an Atheris fuzz target","Build a runnable fuzz target with instrumentation, an input decoder and a clear oracle.",[1073,1076,1079,1082],{"name":1074,"text":1075},"Import the code inside instrument_imports","Instrumentation must wrap the import so coverage feedback exists.",{"name":1077,"text":1078},"Decode the input with FuzzedDataProvider","Turn the byte buffer into the typed arguments the function expects.",{"name":1080,"text":1081},"Define the oracle narrowly","Return on documented exceptions and let everything else propagate as a finding.",{"name":1083,"text":1084},"Seed a corpus and run with a budget","Point the fuzzer at real example inputs and bound the run.","\u002Fproperty-based-fuzz-testing-strategies\u002Fcoverage-guided-fuzzing-with-atheris\u002Fwriting-your-first-atheris-fuzz-target",{"title":5,"description":1055},"property-based-fuzz-testing-strategies\u002Fcoverage-guided-fuzzing-with-atheris\u002Fwriting-your-first-atheris-fuzz-target\u002Findex","LIsqdn4hQu0UHQnbQGTdezZsiJ2OkQr0JdugFimLFO8",1785613404467]