Fuzzing a Markdown parser written in Go

Fuzzing a Markdown parser written in Go

part of Go Cookbook
After working on the gomarkdown Markdown parser, I wanted to check that unexpected input would not crash it or make it hang. Fuzzing found bugs that my hand-written tests had missed.
The original investigation used go-fuzz. Since Go 1.18, fuzzing is built into go test, so no separate fuzzing tool or build tag is needed.

What is fuzzing?

A fuzzer mutates inputs and uses coverage feedback to explore paths through a program. A test can report an error or panic when it finds a problem. For a parser, even the simple property “arbitrary input must not panic” is useful. Fuzzing can uncover bugs; it does not prove that a parser is correct or secure.

Write a fuzz test

In an existing Go module, add the parser dependency:
go get github.com/gomarkdown/markdown
Save this as markdown_fuzz_test.go in a directory containing package markdowncheck (or change the package name to match your code):
package markdowncheck

import (
	"testing"

	"github.com/gomarkdown/markdown"
)

func FuzzMarkdown(f *testing.F) {
	f.Add([]byte("# Heading\n\nA paragraph with *emphasis*.\n"))
	f.Add([]byte("[link](https://example.com)\n"))
	f.Add([]byte{})
	f.Fuzz(func(t *testing.T, data []byte) {
		markdown.Parse(data, nil)
	})
}
Each call gets a fresh parser. Keep fuzz targets deterministic and avoid sharing mutable state across inputs. If you also want to exercise HTML rendering, call markdown.ToHTML(data, nil, nil) in the target.

Run it

From the package directory:
go test -run=^$ -fuzz=^FuzzMarkdown$ -fuzztime=30s
Omit -fuzztime to keep searching until you stop the process. Run one fuzz target at a time; -fuzz must match exactly one target in one package.
Go saves a failing input under testdata/fuzz/FuzzMarkdown/ and prints a command to reproduce it. Commit that file with the fix: ordinary go test runs the seed corpus, including saved regression cases. Coverage-expanding inputs are also retained in the build cache for later fuzzing runs.
Seed files in that directory use Go’s fuzz corpus encoding. Do not copy raw Markdown files there. To reuse existing fixtures, read them before f.Fuzz and pass their bytes to f.Add.
The Go fuzzing guide explains corpus handling and supported argument types. The fuzzing tutorial walks through finding and fixing a failure.

The original results

My parser descended from the widely used Blackfriday library, which itself descended from a C library. Even so, the original go-fuzz run found three separate issues that caused a crash or an infinite loop.
Those bugs were fixed in this change, this change, and this change, with regression tests. These are historical results, not bugs found by a new run of the example above.
#go #programming
Sep 5 2026

Related articles

Feedback about page:

Feedback:
Optional: your email if you want me to get back to you: