From a065702de6a51fe3ef975153c54cc8aa49aec385 Mon Sep 17 00:00:00 2001 From: AdamKorcz <44787359+AdamKorcz@users.noreply.github.com> Date: Wed, 19 Oct 2022 17:34:50 +0100 Subject: [PATCH] golang: fix multipart fuzzer (#8816) Adds an updated version of [this fuzzer](https://github.com/AdamKorcz/go-fuzz-corpus/blob/master/multipart/main.go) that invokes the garbage collector manually. This prevents _some_ incorrect OOM crashes reported by OSS-Fuzz, for example https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=52536 Signed-off-by: AdamKorcz Signed-off-by: AdamKorcz --- projects/golang/Dockerfile | 1 + projects/golang/build.sh | 2 + projects/golang/multipart_fuzzer.go | 124 ++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+) create mode 100644 projects/golang/multipart_fuzzer.go diff --git a/projects/golang/Dockerfile b/projects/golang/Dockerfile index 24517e828..9083d8bdc 100644 --- a/projects/golang/Dockerfile +++ b/projects/golang/Dockerfile @@ -42,6 +42,7 @@ COPY build.sh text_fuzzer.go \ webp_fuzzer.go \ filepath_fuzzer.go \ strings_fuzzer.go \ + multipart_fuzzer.go \ glob_fuzzer.options $SRC/ WORKDIR $SRC/golang diff --git a/projects/golang/build.sh b/projects/golang/build.sh index cc7a3ccf5..b402de380 100755 --- a/projects/golang/build.sh +++ b/projects/golang/build.sh @@ -45,6 +45,8 @@ function setup_golang_fuzzers() { cp $SRC/strings_fuzzer.go $SRC/golang/strings/ + cp $SRC/multipart_fuzzer.go $SRC/golang/multipart/main.go + go mod init "github.com/dvyukov/go-fuzz-corpus" } diff --git a/projects/golang/multipart_fuzzer.go b/projects/golang/multipart_fuzzer.go new file mode 100644 index 000000000..ea26392e4 --- /dev/null +++ b/projects/golang/multipart_fuzzer.go @@ -0,0 +1,124 @@ +// Copyright 2015 go-fuzz project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +package multipart + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "mime/multipart" + "net/textproto" + "runtime" + + "github.com/dvyukov/go-fuzz-corpus/fuzz" +) + +type Part struct { + hdr textproto.MIMEHeader + data []byte +} + +func Fuzz(data []byte) int { + defer func() { + if r := recover(); r != nil { + } + runtime.GC() + }() + const boundary = "dfhjksd23f43242f43fv4b4g2g2g23vf2" + { + r := multipart.NewReader(bytes.NewReader(data), boundary) + f, err := r.ReadForm(1 << 20) + if err == nil { + f.RemoveAll() + } + } + fmt.Println("Creating multipart reader") + r := multipart.NewReader(bytes.NewReader(data), boundary) + fmt.Println("Reading") + var parts []Part + for { + p, err := r.NextPart() + if err == io.EOF { + break + } + if err != nil { + return 0 + } + p.FileName() + p.FormName() + pdata, err := ioutil.ReadAll(p) + if err != nil { + return 0 + } + p.Close() + // The parser is loose here. + // If data contains \n followed by boundary (but without \r), + // it parses it as part body. However, when it serializes it back, + // it writes \r\n followed by boundary, which becomes new part separator. + if bytes.Contains(pdata, []byte(boundary)) { + continue + } + parts = append(parts, Part{p.Header, pdata}) + } + if len(parts) == 0 { + return 0 + } + + fmt.Println("Creating new writer") + buf := new(bytes.Buffer) + w := multipart.NewWriter(buf) + w.SetBoundary(boundary) + fmt.Println("Writing data") + for _, p := range parts { + pw, err := w.CreatePart(p.hdr) + if err != nil { + panic(err) + } + n, err := pw.Write(p.data) + if err != nil { + panic(err) + } + if n != len(p.data) { + panic("partial write") + } + } + w.Close() + + fmt.Println("Time to compare") + data1 := buf.Bytes() + r1 := multipart.NewReader(buf, boundary) + var parts1 []Part + for { + p, err := r1.NextPart() + if err == io.EOF { + break + } + if err != nil { + fmt.Printf("parts0: %+v\n", parts) + fmt.Printf("data0: %q\n", data) + fmt.Printf("data1: %q\n", data1) + panic(err) + } + p.FileName() + p.FormName() + pdata, err := ioutil.ReadAll(p) + if err != nil { + panic(err) + } + p.Close() + parts1 = append(parts1, Part{p.Header, pdata}) + } + + fmt.Println("Performing deep equal") + + if !fuzz.DeepEqual(parts, parts1) { + fmt.Printf("parts0: %+v\n", parts) + fmt.Printf("parts1: %+v\n", parts1) + fmt.Printf("data0: %q\n", data) + fmt.Printf("data1: %q\n", data1) + panic("data has changed") + } + return 1 +}