[{"data":1,"prerenderedAt":1382},["ShallowReactive",2],{"page-\u002Fproperty-based-fuzz-testing-strategies\u002Fcoverage-guided-fuzzing-with-atheris\u002F":3},{"id":4,"title":5,"body":6,"description":1346,"extension":1347,"meta":1348,"navigation":108,"path":1378,"seo":1379,"stem":1380,"__hash__":1381},"content\u002Fproperty-based-fuzz-testing-strategies\u002Fcoverage-guided-fuzzing-with-atheris\u002Findex.md","Coverage-Guided Fuzzing with Atheris",{"type":7,"value":8,"toc":1330},"minimark",[9,13,18,62,66,74,174,193,322,326,340,350,392,398,415,421,527,531,534,540,596,603,649,655,724,728,731,761,786,790,793,800,820,826,835,841,844,914,918,921,935,943,946,950,1054,1058,1065,1076,1096,1103,1118,1121,1124,1127,1195,1200,1203,1209,1218,1231,1237,1240,1244,1250,1256,1262,1268,1272,1275,1278,1281,1284,1287,1291,1320,1326],[10,11,12],"p",{},"Property-based testing explores the inputs you described. Fuzzing explores the inputs you did not — malformed UTF-8, truncated frames, deeply nested structures, byte sequences no strategy would generate. For a parser, a deserializer or anything that reads untrusted input, that difference is the difference between a suite that proves your assumptions and one that finds where they end.",[14,15,17],"h2",{"id":16},"prerequisites","Prerequisites",[19,20,21,38,53],"ul",{},[22,23,24,28,29,32,33,37],"li",{},[25,26,27],"strong",{},"Python 3.8+"," and ",[25,30,31],{},"atheris 2.3+"," (",[34,35,36],"code",{},"pip install atheris","), on Linux or macOS.",[22,39,40,41,44,45,48,49,52],{},"Optional: ",[25,42,43],{},"hypothesis 6.90+"," for structure-aware fuzzing via ",[34,46,47],{},"atheris.instrument_all"," plus ",[34,50,51],{},"hypothesis","-built inputs.",[22,54,55,56,61],{},"Familiarity with property design from ",[57,58,60],"a",{"href":59},"\u002Fproperty-based-fuzz-testing-strategies\u002Fadvanced-property-based-testing\u002F","advanced property-based testing",".",[14,63,65],{"id":64},"core-concept-coverage-as-the-search-signal","Core concept: coverage as the search signal",[10,67,68,69,73],{},"A random-input fuzzer is a slot machine. A ",[70,71,72],"em",{},"coverage-guided"," fuzzer keeps the inputs that reached new code and mutates those, so the search walks deeper into the program with every discovery. Atheris provides that loop for Python by instrumenting bytecode at import time and reporting edge coverage to libFuzzer, which owns the mutation strategy and the corpus.",[75,76,81],"pre",{"className":77,"code":78,"language":79,"meta":80,"style":80},"language-python shiki shiki-themes github-light github-dark","#!\u002Fusr\u002Fbin\u002Fenv python3\nimport atheris\nimport sys\n\nwith atheris.instrument_imports():        # instrument everything imported here\n    from myapp.wire import decode_frame\n\ndef TestOneInput(data: bytes) -> None:\n    try:\n        decode_frame(data)                # the only oracle: it must not crash\n    except (ValueError, UnicodeDecodeError):\n        return                            # expected, documented rejections\n\natheris.Setup(sys.argv, TestOneInput)\natheris.Fuzz()\n","python","",[34,82,83,91,97,103,110,116,122,127,133,139,145,151,157,162,168],{"__ignoreMap":80},[84,85,88],"span",{"class":86,"line":87},"line",1,[84,89,90],{},"#!\u002Fusr\u002Fbin\u002Fenv python3\n",[84,92,94],{"class":86,"line":93},2,[84,95,96],{},"import atheris\n",[84,98,100],{"class":86,"line":99},3,[84,101,102],{},"import sys\n",[84,104,106],{"class":86,"line":105},4,[84,107,109],{"emptyLinePlaceholder":108},true,"\n",[84,111,113],{"class":86,"line":112},5,[84,114,115],{},"with atheris.instrument_imports():        # instrument everything imported here\n",[84,117,119],{"class":86,"line":118},6,[84,120,121],{},"    from myapp.wire import decode_frame\n",[84,123,125],{"class":86,"line":124},7,[84,126,109],{"emptyLinePlaceholder":108},[84,128,130],{"class":86,"line":129},8,[84,131,132],{},"def TestOneInput(data: bytes) -> None:\n",[84,134,136],{"class":86,"line":135},9,[84,137,138],{},"    try:\n",[84,140,142],{"class":86,"line":141},10,[84,143,144],{},"        decode_frame(data)                # the only oracle: it must not crash\n",[84,146,148],{"class":86,"line":147},11,[84,149,150],{},"    except (ValueError, UnicodeDecodeError):\n",[84,152,154],{"class":86,"line":153},12,[84,155,156],{},"        return                            # expected, documented rejections\n",[84,158,160],{"class":86,"line":159},13,[84,161,109],{"emptyLinePlaceholder":108},[84,163,165],{"class":86,"line":164},14,[84,166,167],{},"atheris.Setup(sys.argv, TestOneInput)\n",[84,169,171],{"class":86,"line":170},15,[84,172,173],{},"atheris.Fuzz()\n",[10,175,176,177,180,181,184,185,188,189,192],{},"Two decisions define a fuzz target. What counts as ",[70,178,179],{},"input"," — here, raw bytes handed to a decoder — and what counts as a ",[70,182,183],{},"bug",". The default oracle is \"no unhandled exception\", and the ",[34,186,187],{},"except"," clause is where you declare which exceptions are the documented contract rather than defects. A target that catches ",[34,190,191],{},"Exception"," broadly finds nothing.",[194,195,198,318],"figure",{"className":196},[197],"diagram",[199,200,207,208,207,212,207,216,207,224,207,233,207,242,207,248,207,252,207,256,207,261,207,266,207,269,207,272,207,275,207,279,207,282,207,285,207,288,207,292,207,295,207,298,207,301,207,305,207,308,207,311,207,315],"svg",{"viewBox":201,"role":202,"ariaLabelledBy":203,"xmlns":206},"0 0 760 270","img",[204,205],"fuzzvspbt-t","fuzzvspbt-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[209,210,211],"title",{"id":204},"Fuzzing and property-based testing compared",[213,214,215],"desc",{"id":205},"A table comparing coverage-guided fuzzing and property-based testing across how inputs are generated, what guides the search, what each is best at finding, and how a failure is reduced.",[217,218],"rect",{"x":219,"y":219,"width":220,"height":221,"rx":222,"fill":223},"0","760","270","14","#fffdf8",[225,226,211],"text",{"x":227,"y":228,"textAnchor":229,"fontSize":230,"fontWeight":231,"fill":232},"380","30","middle","15","700","#3d405b",[217,234],{"x":235,"y":236,"width":237,"height":238,"rx":239,"fill":240,"stroke":232,"strokeWidth":241},"24","52","712","40","10","#f2cc8f","1.5",[225,243,247],{"x":244,"y":245,"fontSize":246,"fontWeight":231,"fill":232},"38","76","12","Criterion",[225,249,251],{"x":250,"y":245,"textAnchor":229,"fontSize":246,"fontWeight":231,"fill":232},"390","Atheris",[225,253,255],{"x":254,"y":245,"textAnchor":229,"fontSize":246,"fontWeight":231,"fill":232},"620","Hypothesis",[217,257],{"x":235,"y":258,"width":237,"height":238,"fill":259,"stroke":232,"strokeWidth":260},"92","#f4f1de","1",[225,262,265],{"x":244,"y":263,"fontSize":264,"fill":232},"116","11.5","Input generation",[225,267,268],{"x":250,"y":263,"textAnchor":229,"fontSize":264,"fill":232},"byte mutation",[225,270,271],{"x":254,"y":263,"textAnchor":229,"fontSize":264,"fill":232},"strategies",[217,273],{"x":235,"y":274,"width":237,"height":238,"fill":223,"stroke":232,"strokeWidth":260},"132",[225,276,278],{"x":244,"y":277,"fontSize":264,"fill":232},"156","Search signal",[225,280,281],{"x":250,"y":277,"textAnchor":229,"fontSize":264,"fill":232},"code coverage",[225,283,284],{"x":254,"y":277,"textAnchor":229,"fontSize":264,"fill":232},"random + targeting",[217,286],{"x":235,"y":287,"width":237,"height":238,"fill":259,"stroke":232,"strokeWidth":260},"172",[225,289,291],{"x":244,"y":290,"fontSize":264,"fill":232},"196","Best at finding",[225,293,294],{"x":250,"y":290,"textAnchor":229,"fontSize":264,"fill":232},"parser and decoder crashes",[225,296,297],{"x":254,"y":290,"textAnchor":229,"fontSize":264,"fill":232},"logic and invariant bugs",[217,299],{"x":235,"y":300,"width":237,"height":238,"fill":223,"stroke":232,"strokeWidth":260},"212",[225,302,304],{"x":244,"y":303,"fontSize":264,"fill":232},"236","Failure reduction",[225,306,307],{"x":250,"y":303,"textAnchor":229,"fontSize":264,"fill":232},"libFuzzer minimisation",[225,309,310],{"x":254,"y":303,"textAnchor":229,"fontSize":264,"fill":232},"shrinking",[86,312],{"x1":313,"y1":236,"x2":313,"y2":314,"stroke":232,"strokeWidth":260},"274","252",[86,316],{"x1":317,"y1":236,"x2":317,"y2":314,"stroke":232,"strokeWidth":260},"505",[319,320,321],"figcaption",{},"They overlap least where they matter most: fuzzing owns untrusted-input handling, property testing owns specified behaviour.",[14,323,325],{"id":324},"step-by-step-implementation","Step-by-step implementation",[10,327,328,331,332,335,336,339],{},[25,329,330],{},"1. Instrument before importing."," Coverage feedback only exists for modules imported inside ",[34,333,334],{},"atheris.instrument_imports()",", or instrumented explicitly with ",[34,337,338],{},"atheris.instrument_func",". A target that imports the code at the top of the file, outside the context manager, runs as a blind random fuzzer.",[10,341,342,345,346,349],{},[25,343,344],{},"2. Convert bytes into the shape the code expects."," ",[34,347,348],{},"FuzzedDataProvider"," turns the raw buffer into typed values without wasting mutations on structure the parser would reject immediately:",[75,351,353],{"className":77,"code":352,"language":79,"meta":80,"style":80},"import atheris\n\ndef TestOneInput(data: bytes) -> None:\n    fdp = atheris.FuzzedDataProvider(data)\n    count = fdp.ConsumeIntInRange(0, 32)\n    name = fdp.ConsumeUnicodeNoSurrogates(64)\n    payload = fdp.ConsumeBytes(fdp.remaining_bytes())\n    build_message(count=count, name=name, payload=payload)\n",[34,354,355,359,363,367,372,377,382,387],{"__ignoreMap":80},[84,356,357],{"class":86,"line":87},[84,358,96],{},[84,360,361],{"class":86,"line":93},[84,362,109],{"emptyLinePlaceholder":108},[84,364,365],{"class":86,"line":99},[84,366,132],{},[84,368,369],{"class":86,"line":105},[84,370,371],{},"    fdp = atheris.FuzzedDataProvider(data)\n",[84,373,374],{"class":86,"line":112},[84,375,376],{},"    count = fdp.ConsumeIntInRange(0, 32)\n",[84,378,379],{"class":86,"line":118},[84,380,381],{},"    name = fdp.ConsumeUnicodeNoSurrogates(64)\n",[84,383,384],{"class":86,"line":124},[84,385,386],{},"    payload = fdp.ConsumeBytes(fdp.remaining_bytes())\n",[84,388,389],{"class":86,"line":129},[84,390,391],{},"    build_message(count=count, name=name, payload=payload)\n",[10,393,394,397],{},[25,395,396],{},"3. Seed the corpus."," A directory of real inputs — captured frames, sample files, protocol messages — gives the fuzzer valid structures to mutate from, which is worth orders of magnitude in reaching depth:",[75,399,403],{"className":400,"code":401,"language":402,"meta":80,"style":80},"language-console shiki shiki-themes github-light github-dark","$ mkdir -p corpus && cp tests\u002Fdata\u002F*.bin corpus\u002F\n$ python fuzz_decode.py corpus\u002F -max_total_time=60\n","console",[34,404,405,410],{"__ignoreMap":80},[84,406,407],{"class":86,"line":87},[84,408,409],{},"$ mkdir -p corpus && cp tests\u002Fdata\u002F*.bin corpus\u002F\n",[84,411,412],{"class":86,"line":93},[84,413,414],{},"$ python fuzz_decode.py corpus\u002F -max_total_time=60\n",[10,416,417,420],{},[25,418,419],{},"4. Keep the crashes."," libFuzzer writes each crashing input to a file. Those files are the artefact: check them into the repository as regression fixtures and run them as ordinary tests, which is how a fuzzing finding becomes a permanent test.",[194,422,424,524],{"className":423},[197],[199,425,207,430,207,433,207,436,207,454,207,456,207,458,207,464,207,470,207,475,207,481,207,484,207,488,207,491,207,495,207,498,207,502,207,505,207,509,207,512,207,516,207,519],{"viewBox":426,"role":202,"ariaLabelledBy":427,"xmlns":206},"0 0 760 172",[428,429],"fuzzloop-t","fuzzloop-d",[209,431,432],{"id":428},"The coverage-guided loop",[213,434,435],{"id":429},"A left-to-right loop: pick an input from the corpus, mutate it, run the target while recording coverage, and keep the mutant in the corpus when it reached a new edge.",[437,438,439,440,207],"defs",{},"\n    ",[441,442,449],"marker",{"id":443,"viewBox":444,"refX":445,"refY":446,"markerWidth":447,"markerHeight":447,"orient":448},"fuzzloop-a","0 0 10 10","9","5","7","auto-start-reverse",[450,451],"path",{"d":452,"fill":453},"M0 0 L10 5 L0 10 z","#e07a5f",[217,455],{"x":219,"y":219,"width":220,"height":287,"rx":222,"fill":223},[225,457,432],{"x":227,"y":228,"textAnchor":229,"fontSize":230,"fontWeight":231,"fill":232},[217,459],{"x":460,"y":461,"width":462,"height":245,"rx":246,"fill":259,"stroke":453,"strokeWidth":463},"26","62","142","1.8",[225,465,469],{"x":466,"y":467,"textAnchor":229,"fontSize":468,"fontWeight":231,"fill":232},"97","96","12.5","pick from corpus",[225,471,474],{"x":466,"y":472,"textAnchor":229,"fontSize":473,"fill":232},"113","11","a known input",[86,476],{"x1":477,"y1":478,"x2":479,"y2":478,"stroke":453,"strokeWidth":463,"markerEnd":480},"176","100","208","url(#fuzzloop-a)",[217,482],{"x":483,"y":461,"width":462,"height":245,"rx":246,"fill":223,"stroke":453,"strokeWidth":463},"214",[225,485,487],{"x":486,"y":467,"textAnchor":229,"fontSize":468,"fontWeight":231,"fill":232},"286","mutate",[225,489,490],{"x":486,"y":472,"textAnchor":229,"fontSize":473,"fill":232},"bit flips, splices",[86,492],{"x1":493,"y1":478,"x2":494,"y2":478,"stroke":453,"strokeWidth":463,"markerEnd":480},"364","396",[217,496],{"x":497,"y":461,"width":462,"height":245,"rx":246,"fill":259,"stroke":453,"strokeWidth":463},"403",[225,499,501],{"x":500,"y":467,"textAnchor":229,"fontSize":468,"fontWeight":231,"fill":232},"474","run + measure",[225,503,504],{"x":500,"y":472,"textAnchor":229,"fontSize":473,"fill":232},"edge coverage",[86,506],{"x1":507,"y1":478,"x2":508,"y2":478,"stroke":453,"strokeWidth":463,"markerEnd":480},"552","584",[217,510],{"x":511,"y":461,"width":462,"height":245,"rx":246,"fill":223,"stroke":453,"strokeWidth":463},"592",[225,513,515],{"x":514,"y":467,"textAnchor":229,"fontSize":468,"fontWeight":231,"fill":232},"663","keep if new",[225,517,518],{"x":514,"y":472,"textAnchor":229,"fontSize":473,"fill":232},"corpus grows",[225,520,523],{"x":227,"y":521,"textAnchor":229,"fontSize":264,"fontStyle":522,"fill":232},"164","italic","A seeded corpus starts the loop deep inside the parser rather than at its first length check.",[319,525,526],{},"Keeping only the inputs that reached new code is what turns random mutation into a directed search.",[14,528,530],{"id":529},"structure-aware-fuzzing","Structure-aware fuzzing",[10,532,533],{},"Raw byte mutation is efficient for a binary parser and wasteful for anything that expects structured input. A JSON API fuzzed with random bytes spends almost every execution being rejected by the first parse, and coverage never reaches the code that matters.",[10,535,536,537,539],{},"Two techniques fix that. The first is ",[34,538,348],{},", which converts the mutation buffer into typed values, so mutations change a field rather than corrupting the frame:",[75,541,543],{"className":77,"code":542,"language":79,"meta":80,"style":80},"import atheris\n\ndef TestOneInput(data: bytes) -> None:\n    fdp = atheris.FuzzedDataProvider(data)\n    doc = {\n        \"id\": fdp.ConsumeIntInRange(0, 10 ** 6),\n        \"name\": fdp.ConsumeUnicodeNoSurrogates(48),\n        \"tags\": [fdp.ConsumeUnicodeNoSurrogates(12)\n                 for _ in range(fdp.ConsumeIntInRange(0, 6))],\n    }\n    validate_document(doc)          # the validator, not the JSON parser, is the target\n",[34,544,545,549,553,557,561,566,571,576,581,586,591],{"__ignoreMap":80},[84,546,547],{"class":86,"line":87},[84,548,96],{},[84,550,551],{"class":86,"line":93},[84,552,109],{"emptyLinePlaceholder":108},[84,554,555],{"class":86,"line":99},[84,556,132],{},[84,558,559],{"class":86,"line":105},[84,560,371],{},[84,562,563],{"class":86,"line":112},[84,564,565],{},"    doc = {\n",[84,567,568],{"class":86,"line":118},[84,569,570],{},"        \"id\": fdp.ConsumeIntInRange(0, 10 ** 6),\n",[84,572,573],{"class":86,"line":124},[84,574,575],{},"        \"name\": fdp.ConsumeUnicodeNoSurrogates(48),\n",[84,577,578],{"class":86,"line":129},[84,579,580],{},"        \"tags\": [fdp.ConsumeUnicodeNoSurrogates(12)\n",[84,582,583],{"class":86,"line":135},[84,584,585],{},"                 for _ in range(fdp.ConsumeIntInRange(0, 6))],\n",[84,587,588],{"class":86,"line":141},[84,589,590],{},"    }\n",[84,592,593],{"class":86,"line":147},[84,594,595],{},"    validate_document(doc)          # the validator, not the JSON parser, is the target\n",[10,597,598,599,602],{},"The second is to reuse Hypothesis strategies as the decoder, which gives fuzzing the domain knowledge that already exists in the test suite. ",[34,600,601],{},"atheris.instrument_all()"," plus a strategy-driven target lets libFuzzer's coverage feedback guide Hypothesis's generation:",[75,604,606],{"className":77,"code":605,"language":79,"meta":80,"style":80},"import atheris\nfrom hypothesis import given, strategies as st\n\n@given(st.builds(Order, quantity=st.integers(0, 1000), price=st.integers(0, 10 ** 6)))\ndef property_total_is_non_negative(order):\n    assert compute_total(order) >= 0\n\natheris.Setup(sys.argv, atheris.instrument_func(property_total_is_non_negative.hypothesis.fuzz_one_input))\natheris.Fuzz()\n",[34,607,608,612,617,621,626,631,636,640,645],{"__ignoreMap":80},[84,609,610],{"class":86,"line":87},[84,611,96],{},[84,613,614],{"class":86,"line":93},[84,615,616],{},"from hypothesis import given, strategies as st\n",[84,618,619],{"class":86,"line":99},[84,620,109],{"emptyLinePlaceholder":108},[84,622,623],{"class":86,"line":105},[84,624,625],{},"@given(st.builds(Order, quantity=st.integers(0, 1000), price=st.integers(0, 10 ** 6)))\n",[84,627,628],{"class":86,"line":112},[84,629,630],{},"def property_total_is_non_negative(order):\n",[84,632,633],{"class":86,"line":118},[84,634,635],{},"    assert compute_total(order) >= 0\n",[84,637,638],{"class":86,"line":124},[84,639,109],{"emptyLinePlaceholder":108},[84,641,642],{"class":86,"line":129},[84,643,644],{},"atheris.Setup(sys.argv, atheris.instrument_func(property_total_is_non_negative.hypothesis.fuzz_one_input))\n",[84,646,647],{"class":86,"line":135},[84,648,173],{},[10,650,651,654],{},[34,652,653],{},"fuzz_one_input"," is the bridge: Hypothesis exposes it precisely so an external fuzzer can drive a property with its own byte buffer. The property keeps its strategies, its shrinking and its assertions, and gains a coverage-guided search in place of random sampling. For code that already has property tests, this is the cheapest possible entry into fuzzing — no new target, no new oracle.",[194,656,658,721],{"className":657},[197],[199,659,207,664,207,667,207,670,207,673,207,675,207,677,207,679,207,682,207,685,207,687,207,690,207,693,207,696,207,698,207,700,207,703,207,706,207,708,207,711,207,714,207,717,207,719],{"viewBox":660,"role":202,"ariaLabelledBy":661,"xmlns":206},"0 0 760 230",[662,663],"fuzzinput-t","fuzzinput-d",[209,665,666],{"id":662},"Three ways to feed a fuzz target",[213,668,669],{"id":663},"A table of three input strategies for a fuzz target - raw bytes, FuzzedDataProvider typed values, and a Hypothesis strategy driven through fuzz_one_input - describing what each suits and the cost of each.",[217,671],{"x":219,"y":219,"width":220,"height":672,"rx":222,"fill":223},"230",[225,674,666],{"x":227,"y":228,"textAnchor":229,"fontSize":230,"fontWeight":231,"fill":232},[217,676],{"x":235,"y":236,"width":237,"height":238,"rx":239,"fill":240,"stroke":232,"strokeWidth":241},[225,678,247],{"x":244,"y":245,"fontSize":246,"fontWeight":231,"fill":232},[225,680,681],{"x":250,"y":245,"textAnchor":229,"fontSize":246,"fontWeight":231,"fill":232},"Suits",[225,683,684],{"x":254,"y":245,"textAnchor":229,"fontSize":246,"fontWeight":231,"fill":232},"Cost",[217,686],{"x":235,"y":258,"width":237,"height":238,"fill":259,"stroke":232,"strokeWidth":260},[225,688,689],{"x":244,"y":263,"fontSize":264,"fill":232},"raw bytes",[225,691,692],{"x":250,"y":263,"textAnchor":229,"fontSize":264,"fill":232},"binary parsers, decoders",[225,694,695],{"x":254,"y":263,"textAnchor":229,"fontSize":264,"fill":232},"wasteful on structured input",[217,697],{"x":235,"y":274,"width":237,"height":238,"fill":223,"stroke":232,"strokeWidth":260},[225,699,348],{"x":244,"y":277,"fontSize":264,"fill":232},[225,701,702],{"x":250,"y":277,"textAnchor":229,"fontSize":264,"fill":232},"APIs with typed fields",[225,704,705],{"x":254,"y":277,"textAnchor":229,"fontSize":264,"fill":232},"you write the decoding",[217,707],{"x":235,"y":287,"width":237,"height":238,"fill":259,"stroke":232,"strokeWidth":260},[225,709,710],{"x":244,"y":290,"fontSize":264,"fill":232},"Hypothesis fuzz_one_input",[225,712,713],{"x":250,"y":290,"textAnchor":229,"fontSize":264,"fill":232},"code with existing properties",[225,715,716],{"x":254,"y":290,"textAnchor":229,"fontSize":264,"fill":232},"strategy overhead per run",[86,718],{"x1":313,"y1":236,"x2":313,"y2":300,"stroke":232,"strokeWidth":260},[86,720],{"x1":317,"y1":236,"x2":317,"y2":300,"stroke":232,"strokeWidth":260},[319,722,723],{},"Match the decoder to the input the code expects: mutations spent on structure the parser rejects immediately are mutations wasted.",[14,725,727],{"id":726},"verification","Verification",[10,729,730],{},"Confirm three things before trusting a fuzzing setup, because all three fail silently.",[75,732,734],{"className":400,"code":733,"language":402,"meta":80,"style":80},"$ python fuzz_decode.py corpus\u002F -max_total_time=30 -print_final_stats=1\n#2048   NEW    cov: 412 ft: 690 corp: 37\u002F1204b exec\u002Fs: 8100\n...\nstat::number_of_executed_units: 243000\nstat::average_exec_per_sec:     8100\n",[34,735,736,741,746,751,756],{"__ignoreMap":80},[84,737,738],{"class":86,"line":87},[84,739,740],{},"$ python fuzz_decode.py corpus\u002F -max_total_time=30 -print_final_stats=1\n",[84,742,743],{"class":86,"line":93},[84,744,745],{},"#2048   NEW    cov: 412 ft: 690 corp: 37\u002F1204b exec\u002Fs: 8100\n",[84,747,748],{"class":86,"line":99},[84,749,750],{},"...\n",[84,752,753],{"class":86,"line":105},[84,754,755],{},"stat::number_of_executed_units: 243000\n",[84,757,758],{"class":86,"line":112},[84,759,760],{},"stat::average_exec_per_sec:     8100\n",[10,762,763,766,767,770,771,774,775,778,779,766,782,785],{},[25,764,765],{},"Coverage must grow."," A ",[34,768,769],{},"cov:"," value that never moves means instrumentation is not applied — usually an import outside the instrumented block. ",[25,772,773],{},"Execution rate must be reasonable."," A few thousand executions per second is normal for Python; a rate in the tens means the target is doing I\u002FO or setup per input, and that work belongs outside ",[34,776,777],{},"TestOneInput",". ",[25,780,781],{},"The corpus must grow.",[34,783,784],{},"corp:"," count stuck at the seed size means every mutation is being rejected early, which points at a length or magic-byte check the fuzzer cannot guess — give it a better seed.",[14,787,789],{"id":788},"corpus-management","Corpus management",[10,791,792],{},"The corpus is the durable asset of a fuzzing programme. It is a set of inputs that collectively reach every code path the fuzzer has discovered, and it is what makes the next run start where the last one stopped.",[10,794,795,796,799],{},"Three operations keep it healthy. ",[25,797,798],{},"Merging"," folds new inputs into the corpus while discarding any that add no coverage, which stops it growing without bound:",[75,801,803],{"className":400,"code":802,"language":402,"meta":80,"style":80},"$ mkdir -p corpus_new\n$ python fuzz_decode.py -merge=1 corpus\u002F artifacts\u002F corpus_new\u002F\nINFO: Merge: 1204 files, 37 new\n",[34,804,805,810,815],{"__ignoreMap":80},[84,806,807],{"class":86,"line":87},[84,808,809],{},"$ mkdir -p corpus_new\n",[84,811,812],{"class":86,"line":93},[84,813,814],{},"$ python fuzz_decode.py -merge=1 corpus\u002F artifacts\u002F corpus_new\u002F\n",[84,816,817],{"class":86,"line":99},[84,818,819],{},"INFO: Merge: 1204 files, 37 new\n",[10,821,822,825],{},[25,823,824],{},"Minimising"," shrinks each input to the smallest version reaching the same coverage, which makes runs faster and crash reproductions readable:",[75,827,829],{"className":400,"code":828,"language":402,"meta":80,"style":80},"$ python fuzz_decode.py -minimize_crash=1 -runs=100000 artifacts\u002Fcrash-8f14e45f\n",[34,830,831],{"__ignoreMap":80},[84,832,833],{"class":86,"line":87},[84,834,828],{},[10,836,837,840],{},[25,838,839],{},"Seeding"," matters most at the start. A corpus of realistic inputs — real protocol frames, real config files, real payloads captured from staging — lets the fuzzer begin deep inside the parser rather than at its first length check. The difference between an empty corpus and a seeded one is frequently the difference between finding nothing in an hour and finding three crashes in ten minutes.",[10,842,843],{},"Store the seed corpus in the repository, small and curated. Store the working corpus in CI cache, where it can grow. And when the format changes, rebuild the seeds rather than carrying forward inputs the parser now rejects at byte one — a stale corpus is worse than none, because it consumes budget on inputs that cannot reach anything.",[194,845,847,911],{"className":846},[197],[199,848,207,852,207,855,207,858,207,865,207,867,207,869,207,871,207,874,207,877,207,880,207,882,207,885,207,888,207,890,207,892,207,895,207,898,207,900,207,902,207,905,207,908],{"viewBox":426,"role":202,"ariaLabelledBy":849,"xmlns":206},[850,851],"corpuslife-t","corpuslife-d",[209,853,854],{"id":850},"The life of a corpus",[213,856,857],{"id":851},"A left-to-right cycle: curated seeds enter the corpus, a fuzzing run adds inputs that reach new coverage, a merge discards redundant entries, and the reduced corpus seeds the next run.",[437,859,439,860,207],{},[441,861,863],{"id":862,"viewBox":444,"refX":445,"refY":446,"markerWidth":447,"markerHeight":447,"orient":448},"corpuslife-a",[450,864],{"d":452,"fill":240},[217,866],{"x":219,"y":219,"width":220,"height":287,"rx":222,"fill":223},[225,868,854],{"x":227,"y":228,"textAnchor":229,"fontSize":230,"fontWeight":231,"fill":232},[217,870],{"x":460,"y":461,"width":462,"height":245,"rx":246,"fill":259,"stroke":240,"strokeWidth":463},[225,872,873],{"x":466,"y":467,"textAnchor":229,"fontSize":468,"fontWeight":231,"fill":232},"curated seeds",[225,875,876],{"x":466,"y":472,"textAnchor":229,"fontSize":473,"fill":232},"in the repository",[86,878],{"x1":477,"y1":478,"x2":479,"y2":478,"stroke":240,"strokeWidth":463,"markerEnd":879},"url(#corpuslife-a)",[217,881],{"x":483,"y":461,"width":462,"height":245,"rx":246,"fill":223,"stroke":240,"strokeWidth":463},[225,883,884],{"x":486,"y":467,"textAnchor":229,"fontSize":468,"fontWeight":231,"fill":232},"run adds inputs",[225,886,887],{"x":486,"y":472,"textAnchor":229,"fontSize":473,"fill":232},"new coverage only",[86,889],{"x1":493,"y1":478,"x2":494,"y2":478,"stroke":240,"strokeWidth":463,"markerEnd":879},[217,891],{"x":497,"y":461,"width":462,"height":245,"rx":246,"fill":259,"stroke":240,"strokeWidth":463},[225,893,894],{"x":500,"y":467,"textAnchor":229,"fontSize":468,"fontWeight":231,"fill":232},"merge",[225,896,897],{"x":500,"y":472,"textAnchor":229,"fontSize":473,"fill":232},"drop the redundant",[86,899],{"x1":507,"y1":478,"x2":508,"y2":478,"stroke":240,"strokeWidth":463,"markerEnd":879},[217,901],{"x":511,"y":461,"width":462,"height":245,"rx":246,"fill":223,"stroke":240,"strokeWidth":463},[225,903,904],{"x":514,"y":467,"textAnchor":229,"fontSize":468,"fontWeight":231,"fill":232},"next run",[225,906,907],{"x":514,"y":472,"textAnchor":229,"fontSize":473,"fill":232},"starts deeper",[225,909,910],{"x":227,"y":521,"textAnchor":229,"fontSize":264,"fontStyle":522,"fill":232},"A corpus that only grows becomes a slow replay of thousands of equivalent inputs.",[319,912,913],{},"Every stage exists to keep one property true: each corpus entry reaches coverage no other entry does.",[14,915,917],{"id":916},"what-fuzzing-finds-that-tests-do-not","What fuzzing finds that tests do not",[10,919,920],{},"It is worth being concrete about the return, because fuzzing is an investment and the case for it is often made vaguely.",[10,922,923,924,28,927,930,931,934],{},"The bug classes a coverage-guided fuzzer finds reliably are: unhandled exceptions on malformed input, including the ",[34,925,926],{},"TypeError",[34,928,929],{},"AttributeError"," that a parser raises when it reaches a branch nobody considered; unbounded recursion on nested structures, which surfaces as ",[34,932,933],{},"RecursionError"," and is a denial-of-service vector in a service; quadratic behaviour on adversarial inputs, visible as a timeout rather than a crash; and memory exhaustion from a length field the parser trusts.",[10,936,937,938,942],{},"The classes it does not find are equally clear. Wrong answers on valid input — the property tests' domain. Logic errors in code with no untrusted input. Concurrency bugs, unless the target explicitly exercises them. And anything requiring a sequence of operations, which is where ",[57,939,941],{"href":940},"\u002Fproperty-based-fuzz-testing-strategies\u002Fstateful-and-model-based-testing\u002F","stateful testing"," applies instead.",[10,944,945],{},"That division is the reason to run both. A parser with property tests and no fuzzing is untested against the inputs an attacker will send; a service with fuzzing and no property tests is untested against the inputs its users will send.\nA final word on expectations. A fuzzing programme's first week is usually productive — the easy crashes in code that never saw malformed input are found quickly — and its second month is usually quiet. That is the normal shape, not a failure: the corpus has reached the edges of the code it can explore, and the next findings arrive when the code changes. Judge the programme by whether it catches regressions in new parsing code, not by a steady stream of discoveries, and keep the budget modest enough that a quiet month is not a cost worth cutting.",[14,947,949],{"id":948},"troubleshooting","Troubleshooting",[951,952,953,969],"table",{},[954,955,956],"thead",{},[957,958,959,963,966],"tr",{},[960,961,962],"th",{},"Symptom",[960,964,965],{},"Root cause",[960,967,968],{},"Fix",[970,971,972,987,998,1012,1025,1043],"tbody",{},[957,973,974,978,981],{},[975,976,977],"td",{},"Coverage never increases",[975,979,980],{},"modules imported outside the instrumented block",[975,982,983,984],{},"move imports inside ",[34,985,986],{},"instrument_imports()",[957,988,989,992,995],{},[975,990,991],{},"A handful of executions per second",[975,993,994],{},"per-input setup, file or network I\u002FO",[975,996,997],{},"hoist setup to module scope, stub I\u002FO",[957,999,1000,1003,1006],{},[975,1001,1002],{},"Crashes only in the fuzzer, never in tests",[975,1004,1005],{},"the target catches too little; real contract violations",[975,1007,1008,1009,1011],{},"narrow the ",[34,1010,187],{}," clause to documented exceptions",[957,1013,1014,1017,1020],{},[975,1015,1016],{},"Corpus stays at seed size",[975,1018,1019],{},"early rejection on magic bytes or length",[975,1021,1022,1023],{},"seed with realistic inputs, use ",[34,1024,348],{},[957,1026,1027,1030,1033],{},[975,1028,1029],{},"Runs forever in CI",[975,1031,1032],{},"no budget set",[975,1034,1035,1036,1039,1040],{},"pass ",[34,1037,1038],{},"-max_total_time"," or ",[34,1041,1042],{},"-runs",[957,1044,1045,1048,1051],{},[975,1046,1047],{},"Native crashes with no Python traceback",[975,1049,1050],{},"C extension without sanitizer flags",[975,1052,1053],{},"rebuild the extension with ASan and matching flags",[14,1055,1057],{"id":1056},"fuzzing-native-extensions","Fuzzing native extensions",[10,1059,1060,1061,1064],{},"Pure-Python fuzzing finds Python-level defects. A project whose parser is a C extension — ",[34,1062,1063],{},"lxml",", a protobuf runtime, a compression codec, a hand-written accelerator — has its most dangerous code below the level Atheris instruments by default, and reaching it takes two additional pieces.",[10,1066,1067,1068,1071,1072,1075],{},"The first is ",[25,1069,1070],{},"sanitizer-instrumented builds",". Atheris ships a ",[34,1073,1074],{},"libFuzzer"," compatible with AddressSanitizer and UndefinedBehaviorSanitizer, and the extension must be compiled with matching flags so memory errors are detected rather than producing undefined behaviour:",[75,1077,1079],{"className":400,"code":1078,"language":402,"meta":80,"style":80},"$ CC=\"clang\" CFLAGS=\"-fsanitize=address,fuzzer-no-link -g -O1\" \\\n  LDSHARED=\"clang -shared\" \\\n  pip install --no-binary :all: --force-reinstall .\u002Fmy_extension\n",[34,1080,1081,1086,1091],{"__ignoreMap":80},[84,1082,1083],{"class":86,"line":87},[84,1084,1085],{},"$ CC=\"clang\" CFLAGS=\"-fsanitize=address,fuzzer-no-link -g -O1\" \\\n",[84,1087,1088],{"class":86,"line":93},[84,1089,1090],{},"  LDSHARED=\"clang -shared\" \\\n",[84,1092,1093],{"class":86,"line":99},[84,1094,1095],{},"  pip install --no-binary :all: --force-reinstall .\u002Fmy_extension\n",[10,1097,1098,1099,1102],{},"The second is ",[25,1100,1101],{},"preloading the sanitizer runtime",", because Python itself was not built with it:",[75,1104,1106],{"className":400,"code":1105,"language":402,"meta":80,"style":80},"$ LD_PRELOAD=$(python -c \"import atheris; print(atheris.path())\")\u002Fasan_with_fuzzer.so \\\n  python fuzz_parser.py corpus\u002F -max_total_time=600\n",[34,1107,1108,1113],{"__ignoreMap":80},[84,1109,1110],{"class":86,"line":87},[84,1111,1112],{},"$ LD_PRELOAD=$(python -c \"import atheris; print(atheris.path())\")\u002Fasan_with_fuzzer.so \\\n",[84,1114,1115],{"class":86,"line":93},[84,1116,1117],{},"  python fuzz_parser.py corpus\u002F -max_total_time=600\n",[10,1119,1120],{},"With both in place, a buffer overflow inside the extension aborts with an ASan report naming the offending line, rather than corrupting memory and crashing somewhere unrelated ten minutes later. That difference is the entire reason to do it: a native memory bug found by a fuzzer is a stack trace, and the same bug found in production is a mystery.",[10,1122,1123],{},"Two operational cautions. Sanitized builds are three to five times slower and use substantially more memory, so a run needs a longer budget and a larger container. And the sanitized extension must not leak into normal test runs — keep the build in a dedicated image or a separate virtualenv, or ordinary CI inherits both the slowdown and the false failures from an interpreter that was not built for it.",[10,1125,1126],{},"Where a native extension cannot be rebuilt — a vendored binary wheel, a closed-source SDK — fuzz the Python wrapper anyway. Coverage feedback stops at the boundary, so the search is blind below it, but crashes and hangs are still detected, and a segfault from a wheel you cannot rebuild is a vendor bug report rather than a debugging session.",[194,1128,1130,1192],{"className":1129},[197],[199,1131,207,1135,207,1138,207,1141,207,1143,207,1145,207,1147,207,1149,207,1152,207,1155,207,1157,207,1160,207,1163,207,1166,207,1168,207,1171,207,1174,207,1177,207,1179,207,1182,207,1185,207,1188,207,1190],{"viewBox":660,"role":202,"ariaLabelledBy":1132,"xmlns":206},[1133,1134],"nativefuzz-t","nativefuzz-d",[209,1136,1137],{"id":1133},"What each build gives the fuzzer",[213,1139,1140],{"id":1134},"A table of three build configurations - plain Python with a stock wheel, an instrumented Python-level build, and a sanitizer-built extension - describing the coverage each provides and the bug classes each can detect.",[217,1142],{"x":219,"y":219,"width":220,"height":672,"rx":222,"fill":223},[225,1144,1137],{"x":227,"y":228,"textAnchor":229,"fontSize":230,"fontWeight":231,"fill":232},[217,1146],{"x":235,"y":236,"width":237,"height":238,"rx":239,"fill":240,"stroke":232,"strokeWidth":241},[225,1148,247],{"x":244,"y":245,"fontSize":246,"fontWeight":231,"fill":232},[225,1150,1151],{"x":250,"y":245,"textAnchor":229,"fontSize":246,"fontWeight":231,"fill":232},"Coverage reaches",[225,1153,1154],{"x":254,"y":245,"textAnchor":229,"fontSize":246,"fontWeight":231,"fill":232},"Detects",[217,1156],{"x":235,"y":258,"width":237,"height":238,"fill":259,"stroke":232,"strokeWidth":260},[225,1158,1159],{"x":244,"y":263,"fontSize":264,"fill":232},"stock wheel",[225,1161,1162],{"x":250,"y":263,"textAnchor":229,"fontSize":264,"fill":232},"Python frames only",[225,1164,1165],{"x":254,"y":263,"textAnchor":229,"fontSize":264,"fill":232},"exceptions and hangs",[217,1167],{"x":235,"y":274,"width":237,"height":238,"fill":223,"stroke":232,"strokeWidth":260},[225,1169,1170],{"x":244,"y":277,"fontSize":264,"fill":232},"instrument_all()",[225,1172,1173],{"x":250,"y":277,"textAnchor":229,"fontSize":264,"fill":232},"all Python, incl. deps",[225,1175,1176],{"x":254,"y":277,"textAnchor":229,"fontSize":264,"fill":232},"deeper Python paths",[217,1178],{"x":235,"y":287,"width":237,"height":238,"fill":259,"stroke":232,"strokeWidth":260},[225,1180,1181],{"x":244,"y":290,"fontSize":264,"fill":232},"ASan-built extension",[225,1183,1184],{"x":250,"y":290,"textAnchor":229,"fontSize":264,"fill":232},"native code too",[225,1186,1187],{"x":254,"y":290,"textAnchor":229,"fontSize":264,"fill":232},"memory errors, UB",[86,1189],{"x1":313,"y1":236,"x2":313,"y2":300,"stroke":232,"strokeWidth":260},[86,1191],{"x1":317,"y1":236,"x2":317,"y2":300,"stroke":232,"strokeWidth":260},[319,1193,1194],{},"Each row costs more setup and more runtime; the bottom row is the only one that finds memory corruption before a user does.",[1196,1197,1199],"h3",{"id":1198},"triaging-a-crash","Triaging a crash",[10,1201,1202],{},"A crash artefact needs a decision within a day, or the corpus accumulates findings nobody has judged. Four questions settle it.",[10,1204,1205,1208],{},[25,1206,1207],{},"Is it reachable from real input?"," A crash from bytes that the network layer would reject before reaching the parser is lower priority than one reachable from a valid-looking request — but check rather than assume, because \"the framework validates that\" is frequently untrue for the field in question.",[10,1210,1211,1214,1215,1217],{},[25,1212,1213],{},"What is the impact?"," An unhandled exception in a request handler is a 500; a ",[34,1216,933],{}," or an unbounded allocation is a denial of service; a native memory error is a potential remote code execution. The three are not the same bug.",[10,1219,1220,1223,1224,1227,1228,1230],{},[25,1221,1222],{},"Is it a contract violation or a missing case?"," If the documented contract says malformed input raises ",[34,1225,1226],{},"FrameError",", a ",[34,1229,926],{}," is a violation and the fix is to catch and re-raise. If the input is arguably valid, the fix is in the logic.",[10,1232,1233,1236],{},[25,1234,1235],{},"Does it reproduce outside the fuzzer?"," Feed the artefact to the function in a plain test. One that does not reproduce points at state leaking between fuzz iterations — a real defect in the target, not in the code.",[10,1238,1239],{},"Record the answers with the committed artefact. A crash fixture whose reason nobody remembers gets deleted in a cleanup, and the bug returns.\nA note on team ownership. Fuzzing produces findings at unpredictable intervals, which fits badly with sprint planning and well with a rotating owner: one person per month reads the summaries, triages any crashes, and reports whether coverage is still growing. That role takes under an hour a month once the reporting is in place, and it is the difference between a fuzzing programme and a fuzzing job that runs unattended until someone notices it has been failing to start for six weeks.",[14,1241,1243],{"id":1242},"frequently-asked-questions","Frequently Asked Questions",[10,1245,1246,1249],{},[25,1247,1248],{},"How is fuzzing different from property-based testing?","\nHypothesis generates structured values from strategies and shrinks failures; Atheris mutates raw bytes and is guided by code coverage, so it discovers paths nobody described. They are complementary: Hypothesis for specified behaviour, fuzzing for the parser and decoder edges.",[10,1251,1252,1255],{},[25,1253,1254],{},"Does Atheris need a special Python build?","\nNo for Python-level coverage — Atheris instruments bytecode at import. Native extensions need to be compiled with the matching sanitizer and coverage flags to be fuzzed effectively.",[10,1257,1258,1261],{},[25,1259,1260],{},"What should a fuzz target assert?","\nAs little as possible beyond not crashing. The default oracle is an unhandled exception; add targeted assertions such as round-trip equality only where the property is cheap to check.",[10,1263,1264,1267],{},[25,1265,1266],{},"How long should a fuzzing run last?","\nMinutes in CI as a regression check against the stored corpus, and hours or days on a schedule for discovery. A short CI run proves no known input crashes; only a long run finds new ones.",[14,1269,1271],{"id":1270},"where-fuzzing-fits-in-a-testing-strategy","Where fuzzing fits in a testing strategy",[10,1273,1274],{},"Fuzzing is the last layer to add and the first to be under-resourced, so it helps to state plainly what it is for and what it replaces.",[10,1276,1277],{},"It replaces nothing. Unit tests specify behaviour, property tests generalise that specification across inputs you described, and fuzzing explores the inputs nobody described. A project with fuzzing and no property tests has a fast search over a space whose correctness criteria are only \"does not crash\" — which finds robustness bugs and no logic bugs at all.",[10,1279,1280],{},"The natural sequence is therefore: unit tests for the specified cases, properties for the invariants, fuzzing for the untrusted boundary. Each layer's findings feed the one below it — a fuzzer's crash becomes a pinned regression test, and a property's counterexample becomes a corpus seed.",[10,1282,1283],{},"Three signals suggest a project is ready for the last layer. It parses input it did not create: a wire format, a user upload, a third-party webhook. It has had at least one production incident traced to malformed input. And its parsing code is stable enough that a corpus will remain valid for months rather than weeks — fuzzing a format that changes every sprint spends its budget re-learning the structure.",[10,1285,1286],{},"Where those signals are absent, the same effort is better spent on properties. Where they are present, a single fuzz target on the main entry point, running nightly with a cached corpus, is a small standing cost that keeps paying out for years: the crashes it finds are the ones that would otherwise be found by whoever is sending you the malformed input.",[14,1288,1290],{"id":1289},"related-guides","Related guides",[19,1292,1293,1300,1307,1313],{},[22,1294,1295,1299],{},[57,1296,1298],{"href":1297},"\u002Fproperty-based-fuzz-testing-strategies\u002Fcoverage-guided-fuzzing-with-atheris\u002Fwriting-your-first-atheris-fuzz-target\u002F","Writing your first Atheris fuzz target"," — the target, the data provider and the first crash, step by step.",[22,1301,1302,1306],{},[57,1303,1305],{"href":1304},"\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"," — corpus caching, budgets and turning crashes into tests.",[22,1308,1309,1312],{},[57,1310,1311],{"href":59},"Advanced property-based testing with Hypothesis"," — the structured half of generated-input testing.",[22,1314,1315,1319],{},[57,1316,1318],{"href":1317},"\u002Fproperty-based-fuzz-testing-strategies\u002F","Property-based & fuzz testing strategies"," — where both techniques sit in a testing strategy.",[10,1321,1322,1323],{},"← Back to ",[57,1324,1325],{"href":1317},"Property-Based & Fuzz Testing Strategies",[1327,1328,1329],"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":80,"searchDepth":93,"depth":93,"links":1331},[1332,1333,1334,1335,1336,1337,1338,1339,1340,1343,1344,1345],{"id":16,"depth":93,"text":17},{"id":64,"depth":93,"text":65},{"id":324,"depth":93,"text":325},{"id":529,"depth":93,"text":530},{"id":726,"depth":93,"text":727},{"id":788,"depth":93,"text":789},{"id":916,"depth":93,"text":917},{"id":948,"depth":93,"text":949},{"id":1056,"depth":93,"text":1057,"children":1341},[1342],{"id":1198,"depth":99,"text":1199},{"id":1242,"depth":93,"text":1243},{"id":1270,"depth":93,"text":1271},{"id":1289,"depth":93,"text":1290},"Fuzz Python with Atheris: libFuzzer instrumentation, writing fuzz targets, structure-aware inputs with Hypothesis, corpus management, and running fuzzers under a CI budget.","md",{"slug":1349,"type":1350,"breadcrumb":1351,"datePublished":1352,"dateModified":1352,"faq":1353,"howto":1362},"coverage-guided-fuzzing-with-atheris","topic","Atheris Fuzzing","2026-08-01",[1354,1356,1358,1360],{"q":1248,"a":1355},"Hypothesis generates structured values from strategies and shrinks failures; Atheris mutates raw bytes and is guided by code coverage, so it discovers paths nobody described. They are complementary: Hypothesis for specified behaviour, fuzzing for the parser and decoder edges.",{"q":1254,"a":1357},"No for Python-level coverage — Atheris instruments bytecode at import. Native extensions need to be compiled with the matching sanitizer and coverage flags to be fuzzed effectively.",{"q":1260,"a":1359},"As little as possible beyond not crashing. The default oracle is an unhandled exception; add targeted assertions such as round-trip equality only where the property is cheap to check.",{"q":1266,"a":1361},"Minutes in CI as a regression check against the stored corpus, and hours or days on a schedule for discovery. A short CI run proves no known input crashes; only a long run finds new ones.",{"name":1363,"description":1364,"steps":1365},"How to fuzz Python code with Atheris","Write a coverage-guided fuzz target, build a corpus, and run it under a time budget.",[1366,1369,1372,1375],{"name":1367,"text":1368},"Write a fuzz target that consumes bytes","Define TestOneInput to feed the input into the code under test and return.",{"name":1370,"text":1371},"Instrument the modules under test","Enable Atheris instrumentation before importing them so coverage feedback works.",{"name":1373,"text":1374},"Seed a corpus with realistic inputs","Provide example files so the fuzzer starts from valid structures rather than noise.",{"name":1376,"text":1377},"Run under a time or iteration budget","Bound the run in CI and keep long discovery runs on a schedule.","\u002Fproperty-based-fuzz-testing-strategies\u002Fcoverage-guided-fuzzing-with-atheris",{"title":5,"description":1346},"property-based-fuzz-testing-strategies\u002Fcoverage-guided-fuzzing-with-atheris\u002Findex","dZqDVnjOwatJMNuXp8YqE43dqnhAqeapvNLKhoAER4s",1785613403106]