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  }