package proc_test import ( "encoding/binary" "encoding/gob" "flag" "os" "os/exec" "sort" "strings" "testing" "github.com/go-delve/delve/pkg/dwarf/op" "github.com/go-delve/delve/pkg/proc" "github.com/go-delve/delve/pkg/proc/core" protest "github.com/go-delve/delve/pkg/proc/test" ) var fuzzEvalExpressionSetup = flag.Bool("fuzzevalexpressionsetup", false, "Performs setup for FuzzEvalExpression") const ( fuzzExecutable = "testdata/fuzzexe" fuzzCoredump = "testdata/fuzzcoredump" fuzzInfoPath = "testdata/fuzzinfo" ) type fuzzInfo struct { Loc *proc.Location Memchunks []memchunk Regs op.DwarfRegisters Fuzzbuf []byte } // FuzzEvalExpression fuzzes the variables loader and expression evaluator of Delve. // To run it, execute the setup first: // // go test -run FuzzEvalExpression -fuzzevalexpressionsetup // // this will create some required files in testdata, the fuzzer can then be run with: // // go test -run NONE -fuzz FuzzEvalExpression -v -fuzzminimizetime=0 func FuzzEvalExpression(f *testing.F) { if *fuzzEvalExpressionSetup { doFuzzEvalExpressionSetup(f) } _, err := os.Stat(fuzzExecutable) if os.IsNotExist(err) { f.Skip("not setup") } bi := proc.NewBinaryInfo("linux", "amd64") assertNoError(bi.LoadBinaryInfo(fuzzExecutable, 0, nil), f, "LoadBinaryInfo") fh, err := os.Open(fuzzInfoPath) assertNoError(err, f, "Open fuzzInfoPath") defer fh.Close() var fi fuzzInfo gob.NewDecoder(fh).Decode(&fi) fi.Regs.ByteOrder = binary.LittleEndian fns, err := bi.FindFunction("main.main") assertNoError(err, f, "FindFunction main.main") fi.Loc.Fn = fns[0] f.Add(fi.Fuzzbuf) f.Fuzz(func(t *testing.T, fuzzbuf []byte) { t.Log("fuzzbuf len", len(fuzzbuf)) mem := &core.SplicedMemory{} // can't work with shrunk input fuzzbufs provided by the fuzzer, resize it // so it is always at least the size we want. lastMemchunk := fi.Memchunks[len(fi.Memchunks)-1] fuzzbufsz := lastMemchunk.Idx + int(lastMemchunk.Sz) if fuzzbufsz > len(fuzzbuf) { newfuzzbuf := make([]byte, fuzzbufsz) copy(newfuzzbuf, fuzzbuf) fuzzbuf = newfuzzbuf } end := uint64(0) for _, memchunk := range fi.Memchunks { if end != memchunk.Addr { mem.Add(&zeroReader{}, end, memchunk.Addr-end) } mem.Add(&offsetReader{fuzzbuf[memchunk.Idx:][:memchunk.Sz], memchunk.Addr}, memchunk.Addr, memchunk.Sz) end = memchunk.Addr + memchunk.Sz } scope := &proc.EvalScope{Location: *fi.Loc, Regs: fi.Regs, Mem: memoryReaderWithFailingWrites{mem}, BinInfo: bi} for _, tc := range getEvalExpressionTestCases() { _, err := scope.EvalExpression(tc.name, pnormalLoadConfig) if err != nil { if strings.Contains(err.Error(), "internal debugger error") { panic(err) } } } }) } func doFuzzEvalExpressionSetup(f *testing.F) { os.Mkdir("testdata", 0700) withTestProcess("testvariables2", f, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) { exePath := fixture.Path assertNoError(grp.Continue(), f, "Continue") fh, err := os.Create(fuzzCoredump) assertNoError(err, f, "Creating coredump") var state proc.DumpState p.Dump(fh, 0, &state) assertNoError(state.Err, f, "Dump()") out, err := exec.Command("cp", exePath, fuzzExecutable).CombinedOutput() f.Log(string(out)) assertNoError(err, f, "cp") }) // 2. Open the core file and search for the correct goroutine cgrp, err := core.OpenCore(fuzzCoredump, fuzzExecutable, nil) c := cgrp.Selected assertNoError(err, f, "OpenCore") gs, _, err := proc.GoroutinesInfo(c, 0, 0) assertNoError(err, f, "GoroutinesInfo") found := false for _, g := range gs { if strings.Contains(g.UserCurrent().File, "testvariables2") { c.SwitchGoroutine(g) found = true break } } if !found { f.Errorf("could not find testvariables2 goroutine") } // 3. Run all the test cases on the core file, register which memory addresses are read frames, err := proc.GoroutineStacktrace(c, c.SelectedGoroutine(), 2, 0) assertNoError(err, f, "Stacktrace") mem := c.Memory() loc, _ := c.CurrentThread().Location() tmem := &tracingMem{make(map[uint64]int), mem} scope := &proc.EvalScope{Location: *loc, Regs: frames[0].Regs, Mem: tmem, BinInfo: c.BinInfo()} for _, tc := range getEvalExpressionTestCases() { scope.EvalExpression(tc.name, pnormalLoadConfig) } // 4. Copy all the memory we read into a buffer to use as fuzz example (if // we try to use the whole core dump as fuzz example the Go fuzzer crashes) memchunks, fuzzbuf := tmem.memoryReadsCondensed() fh, err := os.Create(fuzzInfoPath) assertNoError(err, f, "os.Create") frames[0].Regs.ByteOrder = nil loc.Fn = nil assertNoError(gob.NewEncoder(fh).Encode(fuzzInfo{ Loc: loc, Memchunks: memchunks, Regs: frames[0].Regs, Fuzzbuf: fuzzbuf, }), f, "Encode") assertNoError(fh.Close(), f, "Close") } type tracingMem struct { read map[uint64]int mem proc.MemoryReadWriter } func (tmem *tracingMem) ReadMemory(b []byte, n uint64) (int, error) { if len(b) > tmem.read[n] { tmem.read[n] = len(b) } return tmem.mem.ReadMemory(b, n) } func (tmem *tracingMem) WriteMemory(uint64, []byte) (int, error) { panic("should not be called") } type memchunk struct { Addr, Sz uint64 Idx int } func (tmem *tracingMem) memoryReadsCondensed() ([]memchunk, []byte) { memoryReads := make([]memchunk, 0, len(tmem.read)) for addr, sz := range tmem.read { memoryReads = append(memoryReads, memchunk{addr, uint64(sz), 0}) } sort.Slice(memoryReads, func(i, j int) bool { return memoryReads[i].Addr < memoryReads[j].Addr }) memoryReadsCondensed := make([]memchunk, 0, len(memoryReads)) for _, v := range memoryReads { if len(memoryReadsCondensed) != 0 { last := &memoryReadsCondensed[len(memoryReadsCondensed)-1] if last.Addr+last.Sz >= v.Addr { end := v.Addr + v.Sz sz2 := end - last.Addr if sz2 > last.Sz { last.Sz = sz2 } continue } } memoryReadsCondensed = append(memoryReadsCondensed, v) } fuzzbuf := []byte{} for i := range memoryReadsCondensed { buf := make([]byte, memoryReadsCondensed[i].Sz) tmem.mem.ReadMemory(buf, memoryReadsCondensed[i].Addr) memoryReadsCondensed[i].Idx = len(fuzzbuf) fuzzbuf = append(fuzzbuf, buf...) } return memoryReadsCondensed, fuzzbuf } type offsetReader struct { buf []byte addr uint64 } func (or *offsetReader) ReadMemory(buf []byte, addr uint64) (int, error) { copy(buf, or.buf[addr-or.addr:][:len(buf)]) return len(buf), nil } type memoryReaderWithFailingWrites struct { proc.MemoryReader } func (w memoryReaderWithFailingWrites) WriteMemory(uint64, []byte) (int, error) { panic("should not be called") } type zeroReader struct{} func (*zeroReader) ReadMemory(b []byte, addr uint64) (int, error) { for i := range b { b[i] = 0 } return len(b), nil } func (*zeroReader) WriteMemory(b []byte, addr uint64) (int, error) { panic("should not be called") }