gitlab.com/Raven-IO/raven-delve@v1.22.4/pkg/proc/variables_fuzz_test.go (about)

     1  package proc_test
     2  
     3  import (
     4  	"encoding/binary"
     5  	"encoding/gob"
     6  	"flag"
     7  	"os"
     8  	"os/exec"
     9  	"sort"
    10  	"strings"
    11  	"testing"
    12  
    13  	"gitlab.com/Raven-IO/raven-delve/pkg/dwarf/op"
    14  	"gitlab.com/Raven-IO/raven-delve/pkg/proc"
    15  	"gitlab.com/Raven-IO/raven-delve/pkg/proc/core"
    16  
    17  	protest "gitlab.com/Raven-IO/raven-delve/pkg/proc/test"
    18  )
    19  
    20  var fuzzEvalExpressionSetup = flag.Bool("fuzzevalexpressionsetup", false, "Performs setup for FuzzEvalExpression")
    21  
    22  const (
    23  	fuzzExecutable = "testdata/fuzzexe"
    24  	fuzzCoredump   = "testdata/fuzzcoredump"
    25  	fuzzInfoPath   = "testdata/fuzzinfo"
    26  )
    27  
    28  type fuzzInfo struct {
    29  	Loc       *proc.Location
    30  	Memchunks []memchunk
    31  	Regs      op.DwarfRegisters
    32  	Fuzzbuf   []byte
    33  }
    34  
    35  // FuzzEvalExpression fuzzes the variables loader and expression evaluator of Delve.
    36  // To run it, execute the setup first:
    37  //
    38  //	go test -run FuzzEvalExpression -fuzzevalexpressionsetup
    39  //
    40  // this will create some required files in testdata, the fuzzer can then be run with:
    41  //
    42  //	go test -run NONE -fuzz FuzzEvalExpression -v -fuzzminimizetime=0
    43  func FuzzEvalExpression(f *testing.F) {
    44  	if *fuzzEvalExpressionSetup {
    45  		doFuzzEvalExpressionSetup(f)
    46  	}
    47  	_, err := os.Stat(fuzzExecutable)
    48  	if os.IsNotExist(err) {
    49  		f.Skip("not setup")
    50  	}
    51  	bi := proc.NewBinaryInfo("linux", "amd64")
    52  	assertNoError(bi.LoadBinaryInfo(fuzzExecutable, 0, nil), f, "LoadBinaryInfo")
    53  	fh, err := os.Open(fuzzInfoPath)
    54  	assertNoError(err, f, "Open fuzzInfoPath")
    55  	defer fh.Close()
    56  	var fi fuzzInfo
    57  	gob.NewDecoder(fh).Decode(&fi)
    58  	fi.Regs.ByteOrder = binary.LittleEndian
    59  	fns, err := bi.FindFunction("main.main")
    60  	assertNoError(err, f, "FindFunction main.main")
    61  	fi.Loc.Fn = fns[0]
    62  	f.Add(fi.Fuzzbuf)
    63  	f.Fuzz(func(t *testing.T, fuzzbuf []byte) {
    64  		t.Log("fuzzbuf len", len(fuzzbuf))
    65  		mem := &core.SplicedMemory{}
    66  
    67  		// can't work with shrunk input fuzzbufs provided by the fuzzer, resize it
    68  		// so it is always at least the size we want.
    69  		lastMemchunk := fi.Memchunks[len(fi.Memchunks)-1]
    70  		fuzzbufsz := lastMemchunk.Idx + int(lastMemchunk.Sz)
    71  		if fuzzbufsz > len(fuzzbuf) {
    72  			newfuzzbuf := make([]byte, fuzzbufsz)
    73  			copy(newfuzzbuf, fuzzbuf)
    74  			fuzzbuf = newfuzzbuf
    75  		}
    76  
    77  		end := uint64(0)
    78  
    79  		for _, memchunk := range fi.Memchunks {
    80  			if end != memchunk.Addr {
    81  				mem.Add(&zeroReader{}, end, memchunk.Addr-end)
    82  			}
    83  			mem.Add(&offsetReader{fuzzbuf[memchunk.Idx:][:memchunk.Sz], memchunk.Addr}, memchunk.Addr, memchunk.Sz)
    84  			end = memchunk.Addr + memchunk.Sz
    85  		}
    86  
    87  		scope := &proc.EvalScope{Location: *fi.Loc, Regs: fi.Regs, Mem: memoryReaderWithFailingWrites{mem}, BinInfo: bi}
    88  		for _, tc := range getEvalExpressionTestCases() {
    89  			_, err := scope.EvalExpression(tc.name, pnormalLoadConfig)
    90  			if err != nil {
    91  				if strings.Contains(err.Error(), "internal debugger error") {
    92  					panic(err)
    93  				}
    94  			}
    95  		}
    96  	})
    97  }
    98  
    99  func doFuzzEvalExpressionSetup(f *testing.F) {
   100  	os.Mkdir("testdata", 0700)
   101  	withTestProcess("testvariables2", f, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
   102  		exePath := fixture.Path
   103  		assertNoError(grp.Continue(), f, "Continue")
   104  		fh, err := os.Create(fuzzCoredump)
   105  		assertNoError(err, f, "Creating coredump")
   106  		var state proc.DumpState
   107  		p.Dump(fh, 0, &state)
   108  		assertNoError(state.Err, f, "Dump()")
   109  		out, err := exec.Command("cp", exePath, fuzzExecutable).CombinedOutput()
   110  		f.Log(string(out))
   111  		assertNoError(err, f, "cp")
   112  	})
   113  
   114  	// 2. Open the core file and search for the correct goroutine
   115  
   116  	cgrp, err := core.OpenCore(fuzzCoredump, fuzzExecutable, nil)
   117  	c := cgrp.Selected
   118  	assertNoError(err, f, "OpenCore")
   119  	gs, _, err := proc.GoroutinesInfo(c, 0, 0)
   120  	assertNoError(err, f, "GoroutinesInfo")
   121  	found := false
   122  	for _, g := range gs {
   123  		if strings.Contains(g.UserCurrent().File, "testvariables2") {
   124  			c.SwitchGoroutine(g)
   125  			found = true
   126  			break
   127  		}
   128  	}
   129  	if !found {
   130  		f.Errorf("could not find testvariables2 goroutine")
   131  	}
   132  
   133  	// 3. Run all the test cases on the core file, register which memory addresses are read
   134  
   135  	frames, err := proc.GoroutineStacktrace(c, c.SelectedGoroutine(), 2, 0)
   136  	assertNoError(err, f, "Stacktrace")
   137  
   138  	mem := c.Memory()
   139  	loc, _ := c.CurrentThread().Location()
   140  	tmem := &tracingMem{make(map[uint64]int), mem}
   141  
   142  	scope := &proc.EvalScope{Location: *loc, Regs: frames[0].Regs, Mem: tmem, BinInfo: c.BinInfo()}
   143  
   144  	for _, tc := range getEvalExpressionTestCases() {
   145  		scope.EvalExpression(tc.name, pnormalLoadConfig)
   146  	}
   147  
   148  	// 4. Copy all the memory we read into a buffer to use as fuzz example (if
   149  	// we try to use the whole core dump as fuzz example the Go fuzzer crashes)
   150  
   151  	memchunks, fuzzbuf := tmem.memoryReadsCondensed()
   152  
   153  	fh, err := os.Create(fuzzInfoPath)
   154  	assertNoError(err, f, "os.Create")
   155  	frames[0].Regs.ByteOrder = nil
   156  	loc.Fn = nil
   157  	assertNoError(gob.NewEncoder(fh).Encode(fuzzInfo{
   158  		Loc:       loc,
   159  		Memchunks: memchunks,
   160  		Regs:      frames[0].Regs,
   161  		Fuzzbuf:   fuzzbuf,
   162  	}), f, "Encode")
   163  	assertNoError(fh.Close(), f, "Close")
   164  }
   165  
   166  type tracingMem struct {
   167  	read map[uint64]int
   168  	mem  proc.MemoryReadWriter
   169  }
   170  
   171  func (tmem *tracingMem) ReadMemory(b []byte, n uint64) (int, error) {
   172  	if len(b) > tmem.read[n] {
   173  		tmem.read[n] = len(b)
   174  	}
   175  	return tmem.mem.ReadMemory(b, n)
   176  }
   177  
   178  func (tmem *tracingMem) WriteMemory(uint64, []byte) (int, error) {
   179  	panic("should not be called")
   180  }
   181  
   182  type memchunk struct {
   183  	Addr, Sz uint64
   184  	Idx      int
   185  }
   186  
   187  func (tmem *tracingMem) memoryReadsCondensed() ([]memchunk, []byte) {
   188  	memoryReads := make([]memchunk, 0, len(tmem.read))
   189  	for addr, sz := range tmem.read {
   190  		memoryReads = append(memoryReads, memchunk{addr, uint64(sz), 0})
   191  	}
   192  	sort.Slice(memoryReads, func(i, j int) bool { return memoryReads[i].Addr < memoryReads[j].Addr })
   193  
   194  	memoryReadsCondensed := make([]memchunk, 0, len(memoryReads))
   195  	for _, v := range memoryReads {
   196  		if len(memoryReadsCondensed) != 0 {
   197  			last := &memoryReadsCondensed[len(memoryReadsCondensed)-1]
   198  			if last.Addr+last.Sz >= v.Addr {
   199  				end := v.Addr + v.Sz
   200  				sz2 := end - last.Addr
   201  				if sz2 > last.Sz {
   202  					last.Sz = sz2
   203  				}
   204  				continue
   205  			}
   206  		}
   207  		memoryReadsCondensed = append(memoryReadsCondensed, v)
   208  	}
   209  
   210  	fuzzbuf := []byte{}
   211  	for i := range memoryReadsCondensed {
   212  		buf := make([]byte, memoryReadsCondensed[i].Sz)
   213  		tmem.mem.ReadMemory(buf, memoryReadsCondensed[i].Addr)
   214  		memoryReadsCondensed[i].Idx = len(fuzzbuf)
   215  		fuzzbuf = append(fuzzbuf, buf...)
   216  	}
   217  
   218  	return memoryReadsCondensed, fuzzbuf
   219  }
   220  
   221  type offsetReader struct {
   222  	buf  []byte
   223  	addr uint64
   224  }
   225  
   226  func (or *offsetReader) ReadMemory(buf []byte, addr uint64) (int, error) {
   227  	copy(buf, or.buf[addr-or.addr:][:len(buf)])
   228  	return len(buf), nil
   229  }
   230  
   231  type memoryReaderWithFailingWrites struct {
   232  	proc.MemoryReader
   233  }
   234  
   235  func (w memoryReaderWithFailingWrites) WriteMemory(uint64, []byte) (int, error) {
   236  	panic("should not be called")
   237  }
   238  
   239  type zeroReader struct{}
   240  
   241  func (*zeroReader) ReadMemory(b []byte, addr uint64) (int, error) {
   242  	for i := range b {
   243  		b[i] = 0
   244  	}
   245  	return len(b), nil
   246  }
   247  
   248  func (*zeroReader) WriteMemory(b []byte, addr uint64) (int, error) {
   249  	panic("should not be called")
   250  }