- Commit
- 153116f7fd955bbcfee5fe80996a4619c7a343c3
- Parent
- 00291fd1811eba348f649f74f4c727625f0be945
- Author
- John MacFarlane <jgm@berkeley.edu>
- Date
Merge pull request #209 from philipturnbull/libFuzzer
Add libFuzzer harness for oss-fuzz
My personal build of CMark ✏️
Merge pull request #209 from philipturnbull/libFuzzer
Add libFuzzer harness for oss-fuzz
57 files changed, 114 insertions, 97 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -24,6 +24,7 @@ set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_ option(CMARK_TESTS "Build cmark tests and enable testing" ON) option(CMARK_STATIC "Build static libcmark library" ON) option(CMARK_SHARED "Build shared libcmark library" ON) +option(CMARK_LIB_FUZZER "Build libFuzzer fuzzing harness" OFF) add_subdirectory(src) if(CMARK_TESTS AND CMARK_SHARED)
diff --git a/Makefile b/Makefile @@ -14,6 +14,7 @@ BENCHFILE=$(BENCHDIR)/benchinput.md ALLTESTS=alltests.md NUMRUNS?=10 CMARK=$(BUILDDIR)/src/cmark +CMARK_FUZZ=$(BUILDDIR)/src/cmark-fuzz PROG?=$(CMARK) VERSION?=$(SPECVERSION) RELEASE?=CommonMark-$(VERSION) @@ -77,10 +78,17 @@ afl: $(AFL_PATH)/afl-fuzz \ -i test/afl_test_cases \ -o test/afl_results \ - -x test/afl_dictionary \ + -x test/fuzzing_dictionary \ -t 100 \ $(CMARK) $(CMARK_OPTS) +libFuzzer: + @[ -n "$(LIB_FUZZER_PATH)" ] || { echo '$$LIB_FUZZER_PATH not set'; false; } + mkdir -p $(BUILDDIR) + cd $(BUILDDIR) && cmake -DCMAKE_BUILD_TYPE=Asan -DCMARK_LIB_FUZZER=ON -DCMAKE_LIB_FUZZER_PATH=$(LIB_FUZZER_PATH) .. + $(MAKE) -j2 -C $(BUILDDIR) cmark-fuzz + test/run-cmark-fuzz $(CMARK_FUZZ) + clang-check: all ${CLANG_CHECK} -p build -analyze src/*.c
diff --git a/README.md b/README.md @@ -122,6 +122,13 @@ To do a more systematic fuzz test with [american fuzzy lop]: AFL_PATH=/path/to/afl_directory make afl +Fuzzing with [libFuzzer] is also supported but, because libFuzzer is still +under active development, may not work with your system-installed version of +clang. Assuming LLVM has been built in `$HOME/src/llvm/build` the fuzzer can be +run with: + + CC="$HOME/src/llvm/build/bin/clang" LIB_FUZZER_PATH="$HOME/src/llvm/lib/Fuzzer/libFuzzer.a" make libFuzzer + To make a release tarball and zip archive: make archive @@ -188,3 +195,4 @@ most of the C library's API and its test harness. [Build Status]: https://img.shields.io/travis/jgm/cmark/master.svg?style=flat [Windows Build Status]: https://ci.appveyor.com/api/projects/status/32r7s2skrgm9ubva?svg=true [american fuzzy lop]: http://lcamtuf.coredump.cx/afl/ +[libFuzzer]: http://llvm.org/docs/LibFuzzer.html
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt @@ -186,3 +186,14 @@ endif() if(CMAKE_BUILD_TYPE STREQUAL "Ubsan") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined") endif() + +if(CMARK_LIB_FUZZER) + set(FUZZ_HARNESS "cmark-fuzz") + add_executable(${FUZZ_HARNESS} ../test/cmark-fuzz.c ${LIBRARY_SOURCES}) + target_link_libraries(${FUZZ_HARNESS} "${CMAKE_LIB_FUZZER_PATH}") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize-coverage=trace-pc-guard") + + # cmark is written in C but the libFuzzer runtime is written in C++ which + # needs to link against the C++ runtime. Explicitly link it into cmark-fuzz + set_target_properties(${FUZZ_HARNESS} PROPERTIES LINK_FLAGS "-lstdc++") +endif()
diff --git a/src/latex.c b/src/latex.c @@ -179,6 +179,10 @@ static link_type get_link_type(cmark_node *node) { link_text = node->first_child; cmark_consolidate_text_nodes(link_text); + + if (!link_text) + return NO_LINK; + realurl = (char *)url; realurllen = (int)url_len; if (strncmp(realurl, "mailto:", 7) == 0) {
diff --git a/test/afl_dictionary/asterisk b/test/afl_dictionary/asterisk @@ -1 +0,0 @@ -*- \ No newline at end of file
diff --git a/test/afl_dictionary/attr_generic b/test/afl_dictionary/attr_generic @@ -1 +0,0 @@ - a="1"- \ No newline at end of file
diff --git a/test/afl_dictionary/attr_href b/test/afl_dictionary/attr_href @@ -1 +0,0 @@ - href="1"- \ No newline at end of file
diff --git a/test/afl_dictionary/attr_xml_lang b/test/afl_dictionary/attr_xml_lang @@ -1 +0,0 @@ - xml:lang="1"- \ No newline at end of file
diff --git a/test/afl_dictionary/attr_xmlns b/test/afl_dictionary/attr_xmlns @@ -1 +0,0 @@ - xmlns="1"- \ No newline at end of file
diff --git a/test/afl_dictionary/backslash b/test/afl_dictionary/backslash @@ -1 +0,0 @@ -\- \ No newline at end of file
diff --git a/test/afl_dictionary/backtick b/test/afl_dictionary/backtick @@ -1 +0,0 @@ -`- \ No newline at end of file
diff --git a/test/afl_dictionary/colon b/test/afl_dictionary/colon @@ -1 +0,0 @@ -:- \ No newline at end of file
diff --git a/test/afl_dictionary/dashes b/test/afl_dictionary/dashes @@ -1 +0,0 @@ ----- \ No newline at end of file
diff --git a/test/afl_dictionary/double_quote b/test/afl_dictionary/double_quote @@ -1 +0,0 @@ -"- \ No newline at end of file
diff --git a/test/afl_dictionary/entity_builtin b/test/afl_dictionary/entity_builtin @@ -1 +0,0 @@ -<- \ No newline at end of file
diff --git a/test/afl_dictionary/entity_decimal b/test/afl_dictionary/entity_decimal @@ -1 +0,0 @@ -- \ No newline at end of file
diff --git a/test/afl_dictionary/entity_external b/test/afl_dictionary/entity_external @@ -1 +0,0 @@ -&a;- \ No newline at end of file
diff --git a/test/afl_dictionary/entity_hex b/test/afl_dictionary/entity_hex @@ -1 +0,0 @@ -- \ No newline at end of file
diff --git a/test/afl_dictionary/equals b/test/afl_dictionary/equals @@ -1 +0,0 @@ -===- \ No newline at end of file
diff --git a/test/afl_dictionary/exclamation b/test/afl_dictionary/exclamation @@ -1 +0,0 @@ -!- \ No newline at end of file
diff --git a/test/afl_dictionary/greater_than b/test/afl_dictionary/greater_than @@ -1 +0,0 @@ ->- \ No newline at end of file
diff --git a/test/afl_dictionary/hash b/test/afl_dictionary/hash @@ -1 +0,0 @@ -#- \ No newline at end of file
diff --git a/test/afl_dictionary/hyphen b/test/afl_dictionary/hyphen
diff --git a/test/afl_dictionary/indent b/test/afl_dictionary/indent @@ -1 +0,0 @@ - - \ No newline at end of file
diff --git a/test/afl_dictionary/left_bracket b/test/afl_dictionary/left_bracket @@ -1 +0,0 @@ -[- \ No newline at end of file
diff --git a/test/afl_dictionary/left_paren b/test/afl_dictionary/left_paren @@ -1 +0,0 @@ -(- \ No newline at end of file
diff --git a/test/afl_dictionary/less_than b/test/afl_dictionary/less_than @@ -1 +0,0 @@ -<- \ No newline at end of file
diff --git a/test/afl_dictionary/plus b/test/afl_dictionary/plus @@ -1 +0,0 @@ -+- \ No newline at end of file
diff --git a/test/afl_dictionary/right_bracket b/test/afl_dictionary/right_bracket @@ -1 +0,0 @@ -]- \ No newline at end of file
diff --git a/test/afl_dictionary/right_paren b/test/afl_dictionary/right_paren @@ -1 +0,0 @@ -)- \ No newline at end of file
diff --git a/test/afl_dictionary/single_quote b/test/afl_dictionary/single_quote @@ -1 +0,0 @@ -'- \ No newline at end of file
diff --git a/test/afl_dictionary/string_any b/test/afl_dictionary/string_any @@ -1 +0,0 @@ -ANY- \ No newline at end of file
diff --git a/test/afl_dictionary/string_brackets b/test/afl_dictionary/string_brackets @@ -1 +0,0 @@ -[]- \ No newline at end of file
diff --git a/test/afl_dictionary/string_cdata b/test/afl_dictionary/string_cdata @@ -1 +0,0 @@ -CDATA- \ No newline at end of file
diff --git a/test/afl_dictionary/string_dashes b/test/afl_dictionary/string_dashes @@ -1 +0,0 @@ ---- \ No newline at end of file
diff --git a/test/afl_dictionary/string_empty_dblquotes b/test/afl_dictionary/string_empty_dblquotes @@ -1 +0,0 @@ -""- \ No newline at end of file
diff --git a/test/afl_dictionary/string_empty_quotes b/test/afl_dictionary/string_empty_quotes @@ -1 +0,0 @@ -''- \ No newline at end of file
diff --git a/test/afl_dictionary/string_idrefs b/test/afl_dictionary/string_idrefs @@ -1 +0,0 @@ -IDREFS- \ No newline at end of file
diff --git a/test/afl_dictionary/string_parentheses b/test/afl_dictionary/string_parentheses @@ -1 +0,0 @@ -()- \ No newline at end of file
diff --git a/test/afl_dictionary/string_pcdata b/test/afl_dictionary/string_pcdata @@ -1 +0,0 @@ -#PCDATA- \ No newline at end of file
diff --git a/test/afl_dictionary/tag_cdata b/test/afl_dictionary/tag_cdata @@ -1 +0,0 @@ -<![CDATA[- \ No newline at end of file
diff --git a/test/afl_dictionary/tag_close b/test/afl_dictionary/tag_close @@ -1 +0,0 @@ -</a>- \ No newline at end of file
diff --git a/test/afl_dictionary/tag_doctype b/test/afl_dictionary/tag_doctype @@ -1 +0,0 @@ -<!DOCTYPE- \ No newline at end of file
diff --git a/test/afl_dictionary/tag_element b/test/afl_dictionary/tag_element @@ -1 +0,0 @@ -<!ELEMENT- \ No newline at end of file
diff --git a/test/afl_dictionary/tag_entity b/test/afl_dictionary/tag_entity @@ -1 +0,0 @@ -<!ENTITY- \ No newline at end of file
diff --git a/test/afl_dictionary/tag_notation b/test/afl_dictionary/tag_notation @@ -1 +0,0 @@ -<!NOTATION- \ No newline at end of file
diff --git a/test/afl_dictionary/tag_open b/test/afl_dictionary/tag_open @@ -1 +0,0 @@ -<a>- \ No newline at end of file
diff --git a/test/afl_dictionary/tag_open_close b/test/afl_dictionary/tag_open_close @@ -1 +0,0 @@ -<a />- \ No newline at end of file
diff --git a/test/afl_dictionary/tag_open_exclamation b/test/afl_dictionary/tag_open_exclamation @@ -1 +0,0 @@ -<!- \ No newline at end of file
diff --git a/test/afl_dictionary/tag_open_q b/test/afl_dictionary/tag_open_q @@ -1 +0,0 @@ -<?- \ No newline at end of file
diff --git a/test/afl_dictionary/tag_sq2_close b/test/afl_dictionary/tag_sq2_close @@ -1 +0,0 @@ -]]>- \ No newline at end of file
diff --git a/test/afl_dictionary/tag_xml_q b/test/afl_dictionary/tag_xml_q @@ -1 +0,0 @@ -<?xml?>- \ No newline at end of file
diff --git a/test/afl_dictionary/underscore b/test/afl_dictionary/underscore @@ -1 +0,0 @@ -_- \ No newline at end of file
diff --git a/test/cmark-fuzz.c b/test/cmark-fuzz.c @@ -0,0 +1,28 @@ +#include <stdint.h> +#include <stdlib.h> +#include "cmark.h" + +int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + int options = 0; + if (size > sizeof(options)) { + /* First 4 bytes of input are treated as options */ + int options = *(const int *)data; + + /* Mask off valid option bits */ + options = options & (CMARK_OPT_SOURCEPOS | CMARK_OPT_HARDBREAKS | CMARK_OPT_SAFE | CMARK_OPT_NOBREAKS | CMARK_OPT_NORMALIZE | CMARK_OPT_VALIDATE_UTF8 | CMARK_OPT_SMART); + + /* Remainder of input is the markdown */ + const char *markdown = (const char *)(data + sizeof(options)); + const size_t markdown_size = size - sizeof(options); + cmark_node *doc = cmark_parse_document(markdown, markdown_size, options); + + free(cmark_render_commonmark(doc, options, 80)); + free(cmark_render_html(doc, options)); + free(cmark_render_latex(doc, options, 80)); + free(cmark_render_man(doc, options, 80)); + free(cmark_render_xml(doc, options)); + + cmark_node_free(doc); + } + return 0; +}
diff --git a/test/fuzzing_dictionary b/test/fuzzing_dictionary @@ -0,0 +1,49 @@ +asterisk="*" +attr_generic=" a=\"1\"" +attr_href=" href=\"1\"" +attr_xml_lang=" xml:lang=\"1\"" +attr_xmlns=" xmlns=\"1\"" +backslash="\\" +backtick="`" +colon=":" +dashes="---" +double_quote="\"" +entity_builtin="<" +entity_decimal="" +entity_external="&a;" +entity_hex="" +equals="===" +exclamation="!" +greater_than=">" +hash="#" +hyphen="-" +indent=" " +left_bracket="[" +left_paren="(" +less_than="<" +plus="+" +right_bracket="]" +right_paren=")" +single_quote="'" +string_any="ANY" +string_brackets="[]" +string_cdata="CDATA" +string_dashes="--" +string_empty_dblquotes="\"\"" +string_empty_quotes="''" +string_idrefs="IDREFS" +string_parentheses="()" +string_pcdata="#PCDATA" +tag_cdata="<![CDATA[" +tag_close="</a>" +tag_doctype="<!DOCTYPE" +tag_element="<!ELEMENT" +tag_entity="<!ENTITY" +tag_notation="<!NOTATION" +tag_open="<a>" +tag_open_close="<a />" +tag_open_exclamation="<!" +tag_open_q="<?" +tag_sq2_close="]]>" +tag_xml_q="<?xml?>" +underscore="_"