github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/rpcserver/local.go (about) 1 // Copyright 2024 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 package rpcserver 5 6 import ( 7 "context" 8 "fmt" 9 "io" 10 "os" 11 "os/exec" 12 13 "github.com/google/syzkaller/pkg/flatrpc" 14 "github.com/google/syzkaller/pkg/fuzzer/queue" 15 "github.com/google/syzkaller/pkg/log" 16 "github.com/google/syzkaller/pkg/osutil" 17 "github.com/google/syzkaller/pkg/signal" 18 "github.com/google/syzkaller/pkg/vminfo" 19 "github.com/google/syzkaller/prog" 20 "golang.org/x/sync/errgroup" 21 ) 22 23 type LocalConfig struct { 24 Config 25 // syz-executor binary. 26 Executor string 27 // Temp dir where to run executor process, it's up to the caller to clean it up if necessary. 28 Dir string 29 // Handle ctrl+C and exit. 30 HandleInterrupts bool 31 // Run executor under gdb. 32 GDB bool 33 // Can be used to intercept stdout/stderr output. 34 OutputWriter io.Writer 35 MaxSignal []uint64 36 CoverFilter []uint64 37 MachineChecked func(features flatrpc.Feature, syscalls map[*prog.Syscall]bool) queue.Source 38 } 39 40 func RunLocal(ctx context.Context, cfg *LocalConfig) error { 41 localCtx, ctx, err := setupLocal(ctx, cfg) 42 if err != nil { 43 return err 44 } 45 defer localCtx.serv.Close() 46 // groupCtx will be cancelled once any goroutine returns an error. 47 eg, groupCtx := errgroup.WithContext(ctx) 48 eg.Go(func() error { 49 return localCtx.RunInstance(groupCtx, 0) 50 }) 51 eg.Go(func() error { 52 return localCtx.serv.Serve(groupCtx) 53 }) 54 return eg.Wait() 55 } 56 57 func setupLocal(ctx context.Context, cfg *LocalConfig) (*local, context.Context, error) { 58 if cfg.VMArch == "" { 59 cfg.VMArch = cfg.Target.Arch 60 } 61 cfg.UseCoverEdges = true 62 cfg.FilterSignal = true 63 cfg.RPC = ":0" 64 cfg.PrintMachineCheck = log.V(1) 65 cfg.Stats = NewStats() 66 localCtx := &local{ 67 cfg: cfg, 68 setupDone: make(chan bool), 69 } 70 serv := newImpl(&cfg.Config, localCtx) 71 if err := serv.Listen(); err != nil { 72 return nil, nil, err 73 } 74 localCtx.serv = serv 75 // setupDone synchronizes assignment to ctx.serv and read of ctx.serv in MachineChecked 76 // for the race detector b/c it does not understand the synchronization via TCP socket connect/accept. 77 close(localCtx.setupDone) 78 79 if cfg.HandleInterrupts { 80 ctx = cancelOnInterrupts(ctx) 81 } 82 return localCtx, ctx, nil 83 } 84 85 func cancelOnInterrupts(ctx context.Context) context.Context { 86 ret, cancel := context.WithCancel(ctx) 87 shutdown := make(chan struct{}) 88 osutil.HandleInterrupts(shutdown) 89 go func() { 90 select { 91 case <-ctx.Done(): 92 // Prevent goroutine leakage. 93 case <-shutdown: 94 cancel() 95 } 96 }() 97 return ret 98 } 99 100 type local struct { 101 cfg *LocalConfig 102 serv Server 103 setupDone chan bool 104 } 105 106 func (ctx *local) MachineChecked(features flatrpc.Feature, syscalls map[*prog.Syscall]bool) (queue.Source, error) { 107 <-ctx.setupDone 108 ctx.serv.TriagedCorpus() 109 return ctx.cfg.MachineChecked(features, syscalls), nil 110 } 111 112 func (ctx *local) BugFrames() ([]string, []string) { 113 return nil, nil 114 } 115 116 func (ctx *local) MaxSignal() signal.Signal { 117 return signal.FromRaw(ctx.cfg.MaxSignal, 0) 118 } 119 120 func (ctx *local) CoverageFilter(modules []*vminfo.KernelModule) ([]uint64, error) { 121 return ctx.cfg.CoverFilter, nil 122 } 123 124 func (ctx *local) Serve(context context.Context) error { 125 return ctx.serv.Serve(context) 126 } 127 128 func (ctx *local) RunInstance(baseCtx context.Context, id int) error { 129 connErr := ctx.serv.CreateInstance(id, nil, nil) 130 defer ctx.serv.ShutdownInstance(id, true) 131 132 cfg := ctx.cfg 133 bin := cfg.Executor 134 args := []string{"runner", fmt.Sprint(id), "localhost", fmt.Sprint(ctx.serv.Port())} 135 if cfg.GDB { 136 bin = "gdb" 137 args = append([]string{ 138 "--return-child-result", 139 "--ex=handle SIGPIPE nostop", 140 "--args", 141 cfg.Executor, 142 }, args...) 143 } 144 cmd := exec.CommandContext(baseCtx, bin, args...) 145 cmd.Dir = cfg.Dir 146 if cfg.OutputWriter != nil { 147 cmd.Stdout = cfg.OutputWriter 148 cmd.Stderr = cfg.OutputWriter 149 } else if cfg.Debug || cfg.GDB { 150 cmd.Stdout = os.Stdout 151 cmd.Stderr = os.Stderr 152 } 153 if cfg.GDB { 154 cmd.Stdin = os.Stdin 155 } 156 if err := cmd.Start(); err != nil { 157 return fmt.Errorf("failed to start executor: %w", err) 158 } 159 var retErr error 160 select { 161 case <-baseCtx.Done(): 162 case err := <-connErr: 163 if err != nil { 164 retErr = fmt.Errorf("connection error: %w", err) 165 } 166 cmd.Process.Kill() 167 } 168 err := cmd.Wait() 169 if retErr == nil { 170 retErr = fmt.Errorf("executor process exited: %w", err) 171 } 172 // Note that we ignore the error if we killed the process because of the context. 173 if baseCtx.Err() == nil { 174 return retErr 175 } 176 return nil 177 }