Skip to content

cmd/go, testing: go fuzz testing get's stuck somewhere else other than the code to be fuzzed #74571

Open
@personnumber3377

Description

@personnumber3377

Go version

go version go1.24.4 linux/amd64

Output of go env in your module/workspace:

AR='ar'
CC='gcc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='g++'
GCCGO='gccgo'
GO111MODULE=''
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/home/sontapaa_jokulainen/.cache/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/home/sontapaa_jokulainen/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build1537132889=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMOD='/home/sontapaa_jokulainen/fuzz/archive_fuzzer/go.mod'
GOMODCACHE='/home/sontapaa_jokulainen/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/sontapaa_jokulainen/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/home/sontapaa_jokulainen/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.24.4'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

Hi!

I am trying to fuzz this program here:

// archive_test.go
package archivedifffuzz

import (
        "archive/zip"
        "bytes"
        "crypto/sha1"
        "encoding/hex"
        "fmt"
        "io"
        "os"
        "os/exec"
        "path/filepath"
        "strings"
        "testing"
        "runtime"
        "context"
        "time"
        "strconv"
)

type FileEntry struct {
        Name string
        Size int
}

func getLocalUnzipPath() string {
        _, filename, _, ok := runtime.Caller(0)
        if !ok {
         panic("unable to get caller info")
        }
        dir := filepath.Dir(filename)
        return filepath.Join(dir, "unzip")
}

func ExtractWithUnzipAndStatFiles(t *testing.T, zipData []byte) (map[string]int, string, error) {
        tmpDir, err := os.MkdirTemp("", "unzipped-*")
        if err != nil {
         return nil, "", err
        }

        tmpZip, err := os.CreateTemp("", "fuzz-*.zip")
        if err != nil {
         os.RemoveAll(tmpDir)
         return nil, "", err
        }
        defer os.Remove(tmpZip.Name())
        defer tmpZip.Close()

        if _, err := tmpZip.Write(zipData); err != nil {
         os.RemoveAll(tmpDir)
         return nil, "", err
        }

        cmd := exec.Command(getLocalUnzipPath(), "-o", tmpZip.Name(), "-d", tmpDir)


        var outBuf bytes.Buffer
        cmd.Stdout = &outBuf
        cmd.Stderr = &outBuf

        if err := cmd.Run(); err != nil {
         os.RemoveAll(tmpDir)
         return nil, "", fmt.Errorf("unzip failed!!!")
        }

        output := outBuf.String()
        if strings.Contains(output, "mismatch") {
         os.RemoveAll(tmpDir)
         return nil, "", fmt.Errorf("filename mismatch detected")
        }

        fileMap := make(map[string]int)
        err = filepath.Walk(tmpDir, func(path string, info os.FileInfo, err error) error {
         if err != nil {
          return err
         }
         if info.IsDir() {
          return nil
         }
         rel, err := filepath.Rel(tmpDir, path)
         if err != nil {
          return err
         }
         fileMap[rel] = int(info.Size())
         return nil
        })
        if err != nil {
         os.RemoveAll(tmpDir)
         return nil, "", err
        }

        
        return fileMap, tmpDir, nil
}
func LoadCorpus(f *testing.F) {
        files, _ := os.ReadDir("corpus/")
        for _, file := range files {
         if data, err := os.ReadFile("corpus/" + file.Name()); err == nil {
          f.Add(data)
         }
        }
}

func FuzzZipDifferential(f *testing.F) {
        f.Add([]byte("PK\x03\x04..."))
        LoadCorpus(f)

        f.Fuzz(func(t *testing.T, data []byte) {
         if len(data) > 100_000 {
          return
         }

         // runDifferentialTest(t, data)


         ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
         defer cancel()

         done := make(chan struct{})
         go func() {
          defer func() {

                if r := recover(); r != nil {
                  panic(r)
                }

                return
          }()

          runDifferentialTest(t, data)
          close(done)
         }()

         select {
         case <-done:
         case <-ctx.Done():
          return
          // panic("timeout")
         }




        })
}

func mapsEqual(a, b map[string]int) bool {
        if len(a) != len(b) {
         return false
        }
        for k, v := range a {
         if b[k] != v {
          return false
         }
        }
        return true
}

func isValidFilename(s string) bool {
        for _, r := range s {
         if r < 32 || r > 127 || r == '\n' || r == '\r' {
          return false
         }
        }
        return true
}

func findFileInZip(reader *zip.Reader, name string) *zip.File {
        for _, f := range reader.File {
         if f.Name == name {
          return f
         }
        }
        return nil
}

func IncrementCounterInFile(filename string) error {
        // Read existing file contents (if any)
        data, err := os.ReadFile(filename)
        count := 0
        if err == nil {
         trimmed := strings.TrimSpace(string(data))
         if trimmed != "" {
          count, err = strconv.Atoi(trimmed)
          if err != nil {
                return fmt.Errorf("invalid number in file: %v", err)
          }
         }
        } else if !os.IsNotExist(err) {
         return err
        }

        // Increment and write back
        count++
        return os.WriteFile(filename, []byte(fmt.Sprintf("%d\n", count)), 0644)
}

func runDifferentialTest(t *testing.T, data []byte) {

        /*
        unzipFiles, unzipDir, err := ExtractWithUnzipAndStatFiles(t, data)
        if err != nil || len(unzipFiles) == 0 {
         return
        }
        defer os.RemoveAll(unzipDir)
        */

        reader, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
        if err != nil {
         return
        }

        goFiles := make(map[string]int)
        for _, f := range reader.File {
         if !isValidFilename(f.Name) {
          return
         }
         rc, err := f.Open()
         if err != nil {
          return
         }
         n, err := io.Copy(io.Discard, rc)
         rc.Close()
         if err != nil {
          return
         }
         goFiles[f.Name] = int(n)
        }

        // IncrementCounterInFile("/home/YOURHOMEDIRECTORY/integer.txt")
        unzipFiles, unzipDir, err := ExtractWithUnzipAndStatFiles(t, data)
        if err != nil || len(unzipFiles) == 0 {
                // defer os.RemoveAll(unzipDir)
                // t.Errorf("Here is the error: %s", err.Error())
                // panic("fuck")
         return
        }
        // defer os.RemoveAll(unzipDir)

        common := 0
        for name, unzipSize := range unzipFiles {
         if goSize, ok := goFiles[name]; ok {
          common++
          if goSize != unzipSize {
                sum := sha1.Sum(data)
                t.Errorf("Size mismatch for file %s (Go: %d, Unzip: %d)", name, goSize, unzipSize)
                t.Errorf("SHA1: %x", sum)
                if entry := findFileInZip(reader, name); entry != nil {
                  rc, err := entry.Open()
                  if err == nil {
                   goData, _ := io.ReadAll(rc)
                   rc.Close()
                   t.Logf("=== Go version of %s ===\n%s", name, hex.Dump(goData))
                  }
                }
                unzipPath := filepath.Join(unzipDir, name)
                unzipData, err := os.ReadFile(unzipPath)
                if err == nil {
                  t.Logf("=== unzip version of %s ===\n%s", name, hex.Dump(unzipData))
                } else {
                  t.Logf("Failed to read %s from unzip dir: %v", name, err)
                }
                panic("size mismatch")
          }
         }
        }
        // panic("fuck")
        if !mapsEqual(unzipFiles, goFiles) {
         sum := sha1.Sum(data)
         t.Errorf("File mismatch (common: %d, Go: %d, Unzip: %d)", common, len(goFiles), len(unzipFiles))
         t.Errorf("SHA1: %x", sum)
         goOnly := []string{}
         unzipOnly := []string{}
         for name := range goFiles {
          if _, ok := unzipFiles[name]; !ok {
                goOnly = append(goOnly, name)
          }
         }
         for name := range unzipFiles {
          if _, ok := goFiles[name]; !ok {
                unzipOnly = append(unzipOnly, name)
          }
         }
         if len(goOnly) > 0 {
          t.Errorf("Files only in Go archive:\n%s", strings.Join(goOnly, "\n"))
         }
         if len(unzipOnly) > 0 {
          t.Errorf("Files only in Unzip archive:\n%s", strings.Join(unzipOnly, "\n"))
         }
         t.Logf("=== RAW FILENAME DUMP (Go) ===")
         for name := range goFiles {
          t.Logf("%q | bytes: % x", name, []byte(name))
         }
         t.Logf("=== RAW FILENAME DUMP (Unzip) ===")
         for name := range unzipFiles {
          t.Logf("%q | bytes: % x", name, []byte(name))
         }
         panic("filename mismatch")
        }
}

I run it with go test -fuzz=FuzzZipDifferential and then after a while I am getting this output here:

fuzz: elapsed: 38m37s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 38m40s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 38m43s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 38m46s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 38m49s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 38m52s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 38m55s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 38m58s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 39m1s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 39m4s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 39m7s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 39m10s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 39m13s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 39m16s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 39m19s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 39m22s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 39m25s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 39m28s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 39m31s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 39m34s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 39m37s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 39m40s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 39m43s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 39m46s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 39m49s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 39m52s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 39m55s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 39m58s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 40m1s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)
fuzz: elapsed: 40m4s, execs: 1548489 (0/sec), new interesting: 4 (total: 577)

in the output log. It claims that no executions are done, even though I added the timeout to the code. This leads me to believe that execution gets stuck somewhere else other than the fuzz target code. I have verified that I have disk space on my machine. I also should have adequate memory and processing power.

What did you see happen?

Fuzzing grinds to a halt for some odd reason.

What did you expect to see?

Fuzzing continues normally.

Metadata

Metadata

Assignees

No one assigned

    Labels

    WaitingForInfoIssue is not actionable because of missing required information, which needs to be provided.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions