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 }