Fix bugs found in fuzzing

This commit is contained in:
yuin 2019-07-18 18:01:01 +09:00
parent 36e42c4e73
commit 883918a85c
19 changed files with 190 additions and 83 deletions

6
.gitignore vendored
View file

@ -11,3 +11,9 @@
# Output of the go coverage tool, specifically when used with LiteIDE # Output of the go coverage tool, specifically when used with LiteIDE
*.out *.out
.DS_Store
fuzz/corpus
fuzz/crashers
fuzz/suppressions
fuzz/fuzz-fuzz.zip

View file

@ -1,7 +1,16 @@
.PHONY: test .PHONY: test fuzz
test: test:
go test -coverprofile=profile.out -coverpkg=github.com/yuin/goldmark,github.com/yuin/goldmark/ast,github.com/yuin/goldmark/extension,github.com/yuin/goldmark/extension/ast,github.com/yuin/goldmark/parser,github.com/yuin/goldmark/renderer,github.com/yuin/goldmark/renderer/html,github.com/yuin/goldmark/text,github.com/yuin/goldmark/util ./... go test -coverprofile=profile.out -coverpkg=github.com/yuin/goldmark,github.com/yuin/goldmark/ast,github.com/yuin/goldmark/extension,github.com/yuin/goldmark/extension/ast,github.com/yuin/goldmark/parser,github.com/yuin/goldmark/renderer,github.com/yuin/goldmark/renderer/html,github.com/yuin/goldmark/text,github.com/yuin/goldmark/util ./...
cov: test cov: test
go tool cover -html=profile.out go tool cover -html=profile.out
fuzz:
which go-fuzz 2>&1 > /dev/null || (GO111MODULE=off go get -u github.com/dvyukov/go-fuzz/go-fuzz github.com/dvyukov/go-fuzz/go-fuzz-build; GO111MODULE=off go get -d github.com/dvyukov/go-fuzz-corpus; true)
cd ./fuzz && go-fuzz-build
rm -rf ./fuzz/corpus
rm -rf ./fuzz/crashers
rm -rf ./fuzz/suppressions
rm -f ./fuzz/fuzz-fuzz.zip
cd ./fuzz && go-fuzz

View file

@ -348,7 +348,6 @@ func (n *BaseNode) SetAttribute(name, value []byte) {
} }
} }
n.attributes = append(n.attributes, Attribute{name, value}) n.attributes = append(n.attributes, Attribute{name, value})
return
} }
// Attribute implements Node.Attribute. // Attribute implements Node.Attribute.
@ -396,11 +395,9 @@ func DumpHelper(v Node, source []byte, level int, kv map[string]string, cb func(
fmt.Printf("\"\n") fmt.Printf("\"\n")
fmt.Printf("%sHasBlankPreviousLines: %v\n", indent2, v.HasBlankPreviousLines()) fmt.Printf("%sHasBlankPreviousLines: %v\n", indent2, v.HasBlankPreviousLines())
} }
if kv != nil {
for name, value := range kv { for name, value := range kv {
fmt.Printf("%s%s: %s\n", indent2, name, value) fmt.Printf("%s%s: %s\n", indent2, name, value)
} }
}
if cb != nil { if cb != nil {
cb(level + 1) cb(level + 1)
} }

View file

@ -28,7 +28,7 @@ func (b *definitionListParser) Open(parent gast.Node, reader text.Reader, pc par
} }
line, _ := reader.PeekLine() line, _ := reader.PeekLine()
pos := pc.BlockOffset() pos := pc.BlockOffset()
if line[pos] != ':' { if pos < 0 || line[pos] != ':' {
return nil, parser.NoChildren return nil, parser.NoChildren
} }
@ -105,10 +105,13 @@ func NewDefinitionDescriptionParser() parser.BlockParser {
func (b *definitionDescriptionParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) { func (b *definitionDescriptionParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
line, _ := reader.PeekLine() line, _ := reader.PeekLine()
pos := pc.BlockOffset() pos := pc.BlockOffset()
if line[pos] != ':' { if pos < 0 || line[pos] != ':' {
return nil, parser.NoChildren return nil, parser.NoChildren
} }
list, _ := parent.(*ast.DefinitionList) list, _ := parent.(*ast.DefinitionList)
if list == nil {
return nil, parser.NoChildren
}
para := list.TemporaryParagraph para := list.TemporaryParagraph
list.TemporaryParagraph = nil list.TemporaryParagraph = nil
if para != nil { if para != nil {
@ -183,18 +186,18 @@ func (r *DefinitionListHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFunc
func (r *DefinitionListHTMLRenderer) renderDefinitionList(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) { func (r *DefinitionListHTMLRenderer) renderDefinitionList(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
if entering { if entering {
w.WriteString("<dl>\n") _, _ = w.WriteString("<dl>\n")
} else { } else {
w.WriteString("</dl>\n") _, _ = w.WriteString("</dl>\n")
} }
return gast.WalkContinue, nil return gast.WalkContinue, nil
} }
func (r *DefinitionListHTMLRenderer) renderDefinitionTerm(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) { func (r *DefinitionListHTMLRenderer) renderDefinitionTerm(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
if entering { if entering {
w.WriteString("<dt>") _, _ = w.WriteString("<dt>")
} else { } else {
w.WriteString("</dt>\n") _, _ = w.WriteString("</dt>\n")
} }
return gast.WalkContinue, nil return gast.WalkContinue, nil
} }
@ -203,12 +206,12 @@ func (r *DefinitionListHTMLRenderer) renderDefinitionDescription(w util.BufWrite
if entering { if entering {
n := node.(*ast.DefinitionDescription) n := node.(*ast.DefinitionDescription)
if n.IsTight { if n.IsTight {
w.WriteString("<dd>") _, _ = w.WriteString("<dd>")
} else { } else {
w.WriteString("<dd>\n") _, _ = w.WriteString("<dd>\n")
} }
} else { } else {
w.WriteString("</dd>\n") _, _ = w.WriteString("</dd>\n")
} }
return gast.WalkContinue, nil return gast.WalkContinue, nil
} }

View file

@ -29,7 +29,7 @@ func NewFootnoteBlockParser() parser.BlockParser {
func (b *footnoteBlockParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) { func (b *footnoteBlockParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
line, segment := reader.PeekLine() line, segment := reader.PeekLine()
pos := pc.BlockOffset() pos := pc.BlockOffset()
if line[pos] != '[' { if pos < 0 || line[pos] != '[' {
return nil, parser.NoChildren return nil, parser.NoChildren
} }
pos++ pos++
@ -37,7 +37,7 @@ func (b *footnoteBlockParser) Open(parent gast.Node, reader text.Reader, pc pars
return nil, parser.NoChildren return nil, parser.NoChildren
} }
open := pos + 1 open := pos + 1
closes := -1 closes := 0
closure := util.FindClosure(line[pos+1:], '[', ']', false, false) closure := util.FindClosure(line[pos+1:], '[', ']', false, false)
if closure > -1 { if closure > -1 {
closes = pos + 1 + closure closes = pos + 1 + closure
@ -52,10 +52,15 @@ func (b *footnoteBlockParser) Open(parent gast.Node, reader text.Reader, pc pars
if util.IsBlank(label) { if util.IsBlank(label) {
return nil, parser.NoChildren return nil, parser.NoChildren
} }
item := ast.NewFootnote(label)
pos = pos + 2 + closes - open + 2 pos = pos + 2 + closes - open + 2
if pos >= len(line) {
reader.Advance(pos)
return item, parser.NoChildren
}
childpos, padding := util.IndentPosition(line[pos:], pos, 1) childpos, padding := util.IndentPosition(line[pos:], pos, 1)
reader.AdvanceAndSetPadding(pos+childpos, padding) reader.AdvanceAndSetPadding(pos+childpos, padding)
item := ast.NewFootnote(label)
return item, parser.HasChildren return item, parser.HasChildren
} }
@ -207,13 +212,13 @@ func (r *FootnoteHTMLRenderer) renderFootnoteLink(w util.BufWriter, source []byt
if entering { if entering {
n := node.(*ast.FootnoteLink) n := node.(*ast.FootnoteLink)
is := strconv.Itoa(n.Index) is := strconv.Itoa(n.Index)
w.WriteString(`<sup id="fnref:`) _, _ = w.WriteString(`<sup id="fnref:`)
w.WriteString(is) _, _ = w.WriteString(is)
w.WriteString(`"><a href="#fn:`) _, _ = w.WriteString(`"><a href="#fn:`)
w.WriteString(is) _, _ = w.WriteString(is)
w.WriteString(`" class="footnote-ref" role="doc-noteref">`) _, _ = w.WriteString(`" class="footnote-ref" role="doc-noteref">`)
w.WriteString(is) _, _ = w.WriteString(is)
w.WriteString(`</a></sup>`) _, _ = w.WriteString(`</a></sup>`)
} }
return gast.WalkContinue, nil return gast.WalkContinue, nil
} }
@ -222,12 +227,12 @@ func (r *FootnoteHTMLRenderer) renderFootnote(w util.BufWriter, source []byte, n
n := node.(*ast.Footnote) n := node.(*ast.Footnote)
is := strconv.Itoa(n.Index) is := strconv.Itoa(n.Index)
if entering { if entering {
w.WriteString(`<li id="fn:`) _, _ = w.WriteString(`<li id="fn:`)
w.WriteString(is) _, _ = w.WriteString(is)
w.WriteString(`" role="doc-endnote">`) _, _ = w.WriteString(`" role="doc-endnote">`)
w.WriteString("\n") _, _ = w.WriteString("\n")
} else { } else {
w.WriteString("</li>\n") _, _ = w.WriteString("</li>\n")
} }
return gast.WalkContinue, nil return gast.WalkContinue, nil
} }
@ -238,20 +243,20 @@ func (r *FootnoteHTMLRenderer) renderFootnoteList(w util.BufWriter, source []byt
tag = "div" tag = "div"
} }
if entering { if entering {
w.WriteString("<") _, _ = w.WriteString("<")
w.WriteString(tag) _, _ = w.WriteString(tag)
w.WriteString(` class="footnotes" role="doc-endnotes">`) _, _ = w.WriteString(` class="footnotes" role="doc-endnotes">`)
if r.Config.XHTML { if r.Config.XHTML {
w.WriteString("\n<hr />\n") _, _ = w.WriteString("\n<hr />\n")
} else { } else {
w.WriteString("\n<hr>\n") _, _ = w.WriteString("\n<hr>\n")
} }
w.WriteString("<ol>\n") _, _ = w.WriteString("<ol>\n")
} else { } else {
w.WriteString("</ol>\n") _, _ = w.WriteString("</ol>\n")
w.WriteString("<") _, _ = w.WriteString("<")
w.WriteString(tag) _, _ = w.WriteString(tag)
w.WriteString(">\n") _, _ = w.WriteString(">\n")
} }
return gast.WalkContinue, nil return gast.WalkContinue, nil
} }

View file

@ -169,22 +169,22 @@ func (r *TableHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegistere
func (r *TableHTMLRenderer) renderTable(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) { func (r *TableHTMLRenderer) renderTable(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
if entering { if entering {
w.WriteString("<table>\n") _, _ = w.WriteString("<table>\n")
} else { } else {
w.WriteString("</table>\n") _, _ = w.WriteString("</table>\n")
} }
return gast.WalkContinue, nil return gast.WalkContinue, nil
} }
func (r *TableHTMLRenderer) renderTableHeader(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) { func (r *TableHTMLRenderer) renderTableHeader(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
if entering { if entering {
w.WriteString("<thead>\n") _, _ = w.WriteString("<thead>\n")
w.WriteString("<tr>\n") _, _ = w.WriteString("<tr>\n")
} else { } else {
w.WriteString("</tr>\n") _, _ = w.WriteString("</tr>\n")
w.WriteString("</thead>\n") _, _ = w.WriteString("</thead>\n")
if n.NextSibling() != nil { if n.NextSibling() != nil {
w.WriteString("<tbody>\n") _, _ = w.WriteString("<tbody>\n")
} }
} }
return gast.WalkContinue, nil return gast.WalkContinue, nil
@ -192,11 +192,11 @@ func (r *TableHTMLRenderer) renderTableHeader(w util.BufWriter, source []byte, n
func (r *TableHTMLRenderer) renderTableRow(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) { func (r *TableHTMLRenderer) renderTableRow(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
if entering { if entering {
w.WriteString("<tr>\n") _, _ = w.WriteString("<tr>\n")
} else { } else {
w.WriteString("</tr>\n") _, _ = w.WriteString("</tr>\n")
if n.Parent().LastChild() == n { if n.Parent().LastChild() == n {
w.WriteString("</tbody>\n") _, _ = w.WriteString("</tbody>\n")
} }
} }
return gast.WalkContinue, nil return gast.WalkContinue, nil

28
fuzz/fuzz.go Normal file
View file

@ -0,0 +1,28 @@
package fuzz
import (
"bytes"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/renderer/html"
)
func Fuzz(data []byte) int {
markdown := goldmark.New(
goldmark.WithRendererOptions(
html.WithUnsafe(),
),
goldmark.WithExtensions(
extension.DefinitionList,
extension.Footnote,
extension.GFM,
extension.Typographer,
),
)
var b bytes.Buffer
if err := markdown.Convert(data, &b); err != nil {
return 0
}
return 1
}

41
fuzz/fuzz_test.go Normal file
View file

@ -0,0 +1,41 @@
package fuzz
import (
"bytes"
"fmt"
"io/ioutil"
"testing"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/renderer/html"
"github.com/yuin/goldmark/util"
)
var _ = fmt.Printf
func TestFuzz(t *testing.T) {
crasher := "6dff3d03167cb144d4e2891edac76ee740a77bc7"
data, err := ioutil.ReadFile("crashers/" + crasher)
if err != nil {
return
}
fmt.Printf("%s\n", util.VisualizeSpaces(data))
fmt.Println("||||||||||||||||||||||")
markdown := goldmark.New(
goldmark.WithRendererOptions(
html.WithUnsafe(),
),
goldmark.WithExtensions(
extension.DefinitionList,
extension.Footnote,
extension.GFM,
extension.Typographer,
),
)
var b bytes.Buffer
if err := markdown.Convert(data, &b); err != nil {
panic(err)
}
fmt.Println(b.String())
}

View file

@ -77,6 +77,9 @@ func NewATXHeadingParser(opts ...HeadingOption) BlockParser {
func (b *atxHeadingParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) { func (b *atxHeadingParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) {
line, segment := reader.PeekLine() line, segment := reader.PeekLine()
pos := pc.BlockOffset() pos := pc.BlockOffset()
if pos < 0 {
return nil, NoChildren
}
i := pos i := pos
for ; i < len(line) && line[i] == '#'; i++ { for ; i < len(line) && line[i] == '#'; i++ {
} }

View file

@ -55,7 +55,7 @@ func (b *codeBlockParser) Close(node ast.Node, reader text.Reader, pc Context) {
lines := node.Lines() lines := node.Lines()
length := lines.Len() - 1 length := lines.Len() - 1
source := reader.Source() source := reader.Source()
for { for length >= 0 {
line := lines.At(length) line := lines.At(length)
if util.IsBlank(line.Value(source)) { if util.IsBlank(line.Value(source)) {
length-- length--

View file

@ -30,7 +30,7 @@ var fencedCodeBlockInfoKey = NewContextKey()
func (b *fencedCodeBlockParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) { func (b *fencedCodeBlockParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) {
line, segment := reader.PeekLine() line, segment := reader.PeekLine()
pos := pc.BlockOffset() pos := pc.BlockOffset()
if line[pos] != '`' && line[pos] != '~' { if pos < 0 || (line[pos] != '`' && line[pos] != '~') {
return nil, NoChildren return nil, NoChildren
} }
findent := pos findent := pos

View file

@ -109,7 +109,7 @@ func (b *htmlBlockParser) Open(parent ast.Node, reader text.Reader, pc Context)
var node *ast.HTMLBlock var node *ast.HTMLBlock
line, segment := reader.PeekLine() line, segment := reader.PeekLine()
last := pc.LastOpenedBlock().Node last := pc.LastOpenedBlock().Node
if pos := pc.BlockOffset(); line[pos] != '<' { if pos := pc.BlockOffset(); pos < 0 || line[pos] != '<' {
return nil, NoChildren return nil, NoChildren
} }

View file

@ -35,7 +35,7 @@ func parseListItem(line []byte) ([6]int, listItemType) {
ret[1] = i ret[1] = i
ret[2] = i ret[2] = i
var typ listItemType var typ listItemType
if i < l && line[i] == '-' || line[i] == '*' || line[i] == '+' { if i < l && (line[i] == '-' || line[i] == '*' || line[i] == '+') {
i++ i++
ret[3] = i ret[3] = i
typ = bulletList typ = bulletList
@ -46,7 +46,7 @@ func parseListItem(line []byte) ([6]int, listItemType) {
if ret[3] == ret[2] || ret[3]-ret[2] > 9 { if ret[3] == ret[2] || ret[3]-ret[2] > 9 {
return ret, notList return ret, notList
} }
if i < l && line[i] == '.' || line[i] == ')' { if i < l && (line[i] == '.' || line[i] == ')') {
i++ i++
ret[3] = i ret[3] = i
} else { } else {
@ -56,12 +56,17 @@ func parseListItem(line []byte) ([6]int, listItemType) {
} else { } else {
return ret, notList return ret, notList
} }
if line[i] != '\n' { if i < l && line[i] != '\n' {
w, _ := util.IndentWidth(line[i:], 0) w, _ := util.IndentWidth(line[i:], 0)
if w == 0 { if w == 0 {
return ret, notList return ret, notList
} }
} }
if i >= l {
ret[4] = -1
ret[5] = -1
return ret, typ
}
ret[4] = i ret[4] = i
ret[5] = len(line) ret[5] = len(line)
if line[ret[5]-1] == '\n' && line[i] != '\n' { if line[ret[5]-1] == '\n' && line[i] != '\n' {
@ -80,7 +85,7 @@ func matchesListItem(source []byte, strict bool) ([6]int, listItemType) {
func calcListOffset(source []byte, match [6]int) int { func calcListOffset(source []byte, match [6]int) int {
offset := 0 offset := 0
if util.IsBlank(source[match[4]:]) { // list item starts with a blank line if match[4] < 0 || util.IsBlank(source[match[4]:]) { // list item starts with a blank line
offset = 1 offset = 1
} else { } else {
offset, _ = util.IndentWidth(source[match[4]:], match[4]) offset, _ = util.IndentWidth(source[match[4]:], match[4])

View file

@ -36,7 +36,7 @@ func (b *listItemParser) Open(parent ast.Node, reader text.Reader, pc Context) (
} }
itemOffset := calcListOffset(line, match) itemOffset := calcListOffset(line, match)
node := ast.NewListItem(match[3] + itemOffset) node := ast.NewListItem(match[3] + itemOffset)
if match[5]-match[4] == 1 { if match[4] < 0 || match[5]-match[4] == 1 {
return node, NoChildren return node, NoChildren
} }

View file

@ -156,6 +156,7 @@ type Context interface {
// BlockOffset returns a first non-space character position on current line. // BlockOffset returns a first non-space character position on current line.
// This value is valid only for BlockParser.Open. // This value is valid only for BlockParser.Open.
// BlockOffset returns -1 if current line is blank.
BlockOffset() int BlockOffset() int
// BlockOffset sets a first non-space character position on current line. // BlockOffset sets a first non-space character position on current line.
@ -833,7 +834,11 @@ retry:
//currentLineNum, _ = reader.Position() //currentLineNum, _ = reader.Position()
line, _ = reader.PeekLine() line, _ = reader.PeekLine()
w, pos = util.IndentWidth(line, 0) w, pos = util.IndentWidth(line, 0)
if w >= len(line) {
pc.SetBlockOffset(-1)
} else {
pc.SetBlockOffset(pos) pc.SetBlockOffset(pos)
}
shouldPeek = false shouldPeek = false
if line == nil || line[0] == '\n' { if line == nil || line[0] == '\n' {
break break

View file

@ -65,7 +65,7 @@ func (b *setextHeadingParser) Open(parent ast.Node, reader text.Reader, pc Conte
} }
node := ast.NewHeading(level) node := ast.NewHeading(level)
node.Lines().Append(segment) node.Lines().Append(segment)
pc.Set(temporaryParagraphKey, paragraph) pc.Set(temporaryParagraphKey, last)
return node, NoChildren return node, NoChildren
} }

View file

@ -577,6 +577,7 @@ func (d *defaultWriter) Write(writer util.BufWriter, source []byte) {
next := i + 1 next := i + 1
if next < limit && source[next] == '#' { if next < limit && source[next] == '#' {
nnext := next + 1 nnext := next + 1
if nnext < limit {
nc := source[nnext] nc := source[nnext]
// code point like #x22; // code point like #x22;
if nnext < limit && nc == 'x' || nc == 'X' { if nnext < limit && nc == 'x' || nc == 'X' {
@ -601,6 +602,7 @@ func (d *defaultWriter) Write(writer util.BufWriter, source []byte) {
continue continue
} }
} }
}
} else { } else {
start := next start := next
i, ok = util.ReadWhile(source, [2]int{start, limit}, util.IsAlphaNumeric) i, ok = util.ReadWhile(source, [2]int{start, limit}, util.IsAlphaNumeric)

View file

@ -326,6 +326,9 @@ func (r *blockReader) PrecendingCharacter() rune {
break break
} }
} }
if i < 0 {
return rune('\n')
}
rn, _ := utf8.DecodeRune(r.source[i:]) rn, _ := utf8.DecodeRune(r.source[i:])
return rn return rn
} }

View file

@ -567,7 +567,7 @@ func URLEscape(v []byte, resolveReference bool) []byte {
i += int(u8len) i += int(u8len)
n = i n = i
} }
if cob.IsCopied() { if cob.IsCopied() && n < limit {
cob.Write(v[n:]) cob.Write(v[n:])
} }
return cob.Bytes() return cob.Bytes()