cmark

My personal build of CMark ✏️

Commit
153116f7fd955bbcfee5fe80996a4619c7a343c3
Parent
00291fd1811eba348f649f74f4c727625f0be945
Author
John MacFarlane <jgm@berkeley.edu>
Date

Merge pull request #209 from philipturnbull/libFuzzer

Add libFuzzer harness for oss-fuzz

Diffstat

57 files changed, 114 insertions, 97 deletions

Status File Name N° Changes Insertions Deletions
Modified CMakeLists.txt 1 1 0
Modified Makefile 10 9 1
Modified README.md 8 8 0
Modified src/CMakeLists.txt 11 11 0
Modified src/latex.c 4 4 0
Deleted test/afl_dictionary/asterisk 2 0 2
Deleted test/afl_dictionary/attr_generic 2 0 2
Deleted test/afl_dictionary/attr_href 2 0 2
Deleted test/afl_dictionary/attr_xml_lang 2 0 2
Deleted test/afl_dictionary/attr_xmlns 2 0 2
Deleted test/afl_dictionary/backslash 2 0 2
Deleted test/afl_dictionary/backtick 2 0 2
Deleted test/afl_dictionary/colon 2 0 2
Deleted test/afl_dictionary/dashes 2 0 2
Deleted test/afl_dictionary/double_quote 2 0 2
Deleted test/afl_dictionary/entity_builtin 2 0 2
Deleted test/afl_dictionary/entity_decimal 2 0 2
Deleted test/afl_dictionary/entity_external 2 0 2
Deleted test/afl_dictionary/entity_hex 2 0 2
Deleted test/afl_dictionary/equals 2 0 2
Deleted test/afl_dictionary/exclamation 2 0 2
Deleted test/afl_dictionary/greater_than 2 0 2
Deleted test/afl_dictionary/hash 2 0 2
Deleted test/afl_dictionary/hyphen 0 0 0
Deleted test/afl_dictionary/indent 2 0 2
Deleted test/afl_dictionary/left_bracket 2 0 2
Deleted test/afl_dictionary/left_paren 2 0 2
Deleted test/afl_dictionary/less_than 2 0 2
Deleted test/afl_dictionary/plus 2 0 2
Deleted test/afl_dictionary/right_bracket 2 0 2
Deleted test/afl_dictionary/right_paren 2 0 2
Deleted test/afl_dictionary/single_quote 2 0 2
Deleted test/afl_dictionary/string_any 2 0 2
Deleted test/afl_dictionary/string_brackets 2 0 2
Deleted test/afl_dictionary/string_cdata 2 0 2
Deleted test/afl_dictionary/string_dashes 2 0 2
Deleted test/afl_dictionary/string_empty_dblquotes 2 0 2
Deleted test/afl_dictionary/string_empty_quotes 2 0 2
Deleted test/afl_dictionary/string_idrefs 2 0 2
Deleted test/afl_dictionary/string_parentheses 2 0 2
Deleted test/afl_dictionary/string_pcdata 2 0 2
Deleted test/afl_dictionary/tag_cdata 2 0 2
Deleted test/afl_dictionary/tag_close 2 0 2
Deleted test/afl_dictionary/tag_doctype 2 0 2
Deleted test/afl_dictionary/tag_element 2 0 2
Deleted test/afl_dictionary/tag_entity 2 0 2
Deleted test/afl_dictionary/tag_notation 2 0 2
Deleted test/afl_dictionary/tag_open 2 0 2
Deleted test/afl_dictionary/tag_open_close 2 0 2
Deleted test/afl_dictionary/tag_open_exclamation 2 0 2
Deleted test/afl_dictionary/tag_open_q 2 0 2
Deleted test/afl_dictionary/tag_sq2_close 2 0 2
Deleted test/afl_dictionary/tag_xml_q 2 0 2
Deleted test/afl_dictionary/underscore 2 0 2
Added test/cmark-fuzz.c 28 28 0
Added test/fuzzing_dictionary 49 49 0
Added test/run-cmark-fuzz 4 4 0
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 @@
-&lt;-
\ No newline at end of file
diff --git a/test/afl_dictionary/entity_decimal b/test/afl_dictionary/entity_decimal
@@ -1 +0,0 @@
-&#1;-
\ 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 @@
-&#x1;-
\ 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="&lt;"
+entity_decimal="&#1;"
+entity_external="&a;"
+entity_hex="&#x1;"
+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="_"
diff --git a/test/run-cmark-fuzz b/test/run-cmark-fuzz
@@ -0,0 +1,4 @@
+#!/bin/bash -eu
+CMARK_FUZZ="$1"
+shift
+ASAN_OPTIONS="quarantine_size_mb=10:detect_leaks=1" "${CMARK_FUZZ}" -max_len=256 -timeout=1 -dict=test/fuzzing_dictionary "$@"