Skip to content

Commit baa8658

Browse files
DavidKorczynskipre-commit-ci[bot]chrisjsewell
authored
🧪 Add OSS-Fuzz set up (#255)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Chris Sewell <[email protected]>
1 parent d1852a5 commit baa8658

File tree

3 files changed

+117
-0
lines changed

3 files changed

+117
-0
lines changed

tests/fuzz/README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# OSS-Fuzz integration
2+
3+
In principle, core Markdown parsing is designed to never except/crash on any input,
4+
and so [fuzzing](https://en.wikipedia.org/wiki/Fuzzing) can be used to test this conformance.
5+
This folder contains fuzzers which are principally run downstream as part of the <https://github.com/google/oss-fuzz> infrastructure.
6+
7+
Any file that matches `fuzz_*.py` in this repository will be built and run on OSS-Fuzz
8+
(see <https://github.com/google/oss-fuzz/blob/master/projects/markdown-it-py/build.sh>).
9+
10+
See <https://google.github.io/oss-fuzz/advanced-topics/ideal-integration> for full details.
11+
12+
## CI integration
13+
14+
Fuzzing essentially runs forever, or until a crash is found, therefore it cannot be fully integrated into local continous integration testing.
15+
The workflow in `.github/workflows/fuzz.yml` though runs a brief fuzzing on code changed in a PR,
16+
which can be used to provide early warning on code changes.
17+
18+
## Reproducing crash failures
19+
20+
If OSS-Fuzz (or the CI workflow) identifies a crash, it will produce a "minimized testcase" file
21+
(e.g. <https://oss-fuzz.com/testcase-detail/5424112454729728>).
22+
23+
To reproduce this crash locally, the easiest way is to run the [tox](https://tox.wiki/) environment, provided in this repository, against the test file (see `tox.ini`):
24+
25+
```
26+
tox -e fuzz path/to/testcase
27+
```
28+
29+
This idempotently sets up a local python environment with markdown-it-py (local dev) and [Atheris](https://pypi.org/project/atheris/) installed,
30+
clones <https://github.com/google/oss-fuzz> into it,
31+
and builds the fuzzers.
32+
Then the testcase is run within this environment.
33+
34+
If you wish to simply run the full fuzzing process,
35+
you can activate this environment, then run e.g.:
36+
37+
```
38+
python .tox/fuzz/oss-fuzz/infra/helper.py run_fuzzer markdown-it-py fuzz_markdown
39+
```
40+
41+
For a more thorough guide on reproducing, see: https://google.github.io/oss-fuzz/advanced-topics/reproducing/

tests/fuzz/fuzz_markdown.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import sys
2+
3+
import atheris
4+
5+
from markdown_it import MarkdownIt
6+
7+
8+
def TestOneInput(data):
9+
fdp = atheris.FuzzedDataProvider(data)
10+
md = MarkdownIt()
11+
raw_markdown = fdp.ConsumeUnicodeNoSurrogates(sys.maxsize)
12+
md.parse(raw_markdown)
13+
md.render(raw_markdown)
14+
15+
16+
def main():
17+
atheris.instrument_all()
18+
atheris.Setup(sys.argv, TestOneInput)
19+
atheris.Fuzz()
20+
21+
22+
if __name__ == "__main__":
23+
main()

tests/fuzz/fuzz_markdown_extended.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import sys
2+
3+
import atheris
4+
5+
# Beautified from auto-generated fuzzer at:
6+
# https://github.com/ossf/fuzz-introspector/pull/872#issuecomment-1450847118
7+
# Auto-fuzz heuristics used: py-autofuzz-heuristics-4.1
8+
# Imports by the generated code
9+
import markdown_it
10+
11+
12+
def TestOneInput(data):
13+
fdp = atheris.FuzzedDataProvider(data)
14+
val_1 = fdp.ConsumeUnicodeNoSurrogates(fdp.ConsumeIntInRange(0, 1024))
15+
val_2 = fdp.ConsumeUnicodeNoSurrogates(fdp.ConsumeIntInRange(0, 1024))
16+
val_3 = fdp.ConsumeUnicodeNoSurrogates(fdp.ConsumeIntInRange(0, 256))
17+
val_4 = fdp.ConsumeUnicodeNoSurrogates(fdp.ConsumeIntInRange(0, 256))
18+
val_5 = fdp.ConsumeUnicodeNoSurrogates(fdp.ConsumeIntInRange(0, 256))
19+
val_6 = fdp.ConsumeUnicodeNoSurrogates(fdp.ConsumeIntInRange(0, 256))
20+
val_7 = fdp.ConsumeUnicodeNoSurrogates(fdp.ConsumeIntInRange(0, 256))
21+
val_8 = fdp.ConsumeUnicodeNoSurrogates(fdp.ConsumeIntInRange(0, 256))
22+
val_9 = fdp.ConsumeUnicodeNoSurrogates(fdp.ConsumeIntInRange(0, 256))
23+
val_10 = fdp.ConsumeUnicodeNoSurrogates(fdp.ConsumeIntInRange(0, 256))
24+
25+
try:
26+
c1 = markdown_it.main.MarkdownIt()
27+
c1.render(val_1)
28+
c1.parse(val_2)
29+
c1.renderInline(val_3)
30+
c1.parseInline(val_4)
31+
c1.normalizeLink(val_5)
32+
c1.normalizeLinkText(val_6)
33+
c1.disable(val_7)
34+
c1.enable(val_8)
35+
c1.validateLink(val_9)
36+
c1.configure(val_10)
37+
except (
38+
ValueError,
39+
KeyError,
40+
TypeError,
41+
):
42+
# Exceptions thrown by the hit code.
43+
pass
44+
45+
46+
def main():
47+
atheris.instrument_all()
48+
atheris.Setup(sys.argv, TestOneInput)
49+
atheris.Fuzz()
50+
51+
52+
if __name__ == "__main__":
53+
main()

0 commit comments

Comments
 (0)