github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/syz-verifier/rpcserver.go (about)

     1  // Copyright 2021 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 main
     5  
     6  import (
     7  	"errors"
     8  	"net"
     9  	"os"
    10  	"sync"
    11  
    12  	"github.com/google/syzkaller/pkg/log"
    13  	"github.com/google/syzkaller/pkg/rpctype"
    14  )
    15  
    16  // RPCServer is a wrapper around the rpc.Server. It communicates with  Runners,
    17  // generates programs and sends complete Results for verification.
    18  type RPCServer struct {
    19  	vrf  *Verifier
    20  	port int
    21  
    22  	// protects next variables
    23  	mu sync.Mutex
    24  	// used to count the pools w/o UnsupportedCalls result
    25  	notChecked int
    26  	// vmTasks store the per-VM currently assigned tasks Ids
    27  	vmTasksInProgress map[int]map[int64]bool
    28  }
    29  
    30  func startRPCServer(vrf *Verifier) (*RPCServer, error) {
    31  	srv := &RPCServer{
    32  		vrf:        vrf,
    33  		notChecked: len(vrf.pools),
    34  	}
    35  
    36  	s, err := rpctype.NewRPCServer(vrf.addr, "Verifier", srv)
    37  	if err != nil {
    38  		return nil, err
    39  	}
    40  
    41  	log.Logf(0, "serving rpc on tcp://%v", s.Addr())
    42  	srv.port = s.Addr().(*net.TCPAddr).Port
    43  
    44  	go s.Serve()
    45  	return srv, nil
    46  }
    47  
    48  // Connect notifies the RPCServer that a new Runner was started.
    49  func (srv *RPCServer) Connect(a *rpctype.RunnerConnectArgs, r *rpctype.RunnerConnectRes) error {
    50  	r.CheckUnsupportedCalls = !srv.vrf.pools[a.Pool].checked
    51  	return nil
    52  }
    53  
    54  // UpdateUnsupported communicates to the server the list of system calls not
    55  // supported by the kernel corresponding to this pool and updates the list of
    56  // enabled system calls. This function is called once for each kernel.
    57  // When all kernels have reported the list of unsupported system calls, the
    58  // choice table will be created using only the system calls supported by all
    59  // kernels.
    60  func (srv *RPCServer) UpdateUnsupported(a *rpctype.UpdateUnsupportedArgs, r *int) error {
    61  	srv.mu.Lock()
    62  	defer srv.mu.Unlock()
    63  
    64  	if srv.vrf.pools[a.Pool].checked {
    65  		return nil
    66  	}
    67  	srv.vrf.pools[a.Pool].checked = true
    68  	vrf := srv.vrf
    69  
    70  	for _, unsupported := range a.UnsupportedCalls {
    71  		if c := vrf.target.Syscalls[unsupported.ID]; vrf.calls[c] {
    72  			vrf.reasons[c] = unsupported.Reason
    73  		}
    74  	}
    75  
    76  	srv.notChecked--
    77  	if srv.notChecked == 0 {
    78  		vrf.finalizeCallSet(os.Stdout)
    79  
    80  		vrf.stats.SetSyscallMask(vrf.calls)
    81  		vrf.SetPrintStatAtSIGINT()
    82  
    83  		vrf.choiceTable = vrf.target.BuildChoiceTable(nil, vrf.calls)
    84  		vrf.progGeneratorInit.Done()
    85  	}
    86  	return nil
    87  }
    88  
    89  // NextExchange is called when a Runner requests a new program to execute and,
    90  // potentially, wants to send a new Result to the RPCServer.
    91  func (srv *RPCServer) NextExchange(a *rpctype.NextExchangeArgs, r *rpctype.NextExchangeRes) error {
    92  	if a.Info.Calls != nil {
    93  		srv.stopWaitResult(a.Pool, a.VM, a.ExecTaskID)
    94  		srv.vrf.PutExecResult(&ExecResult{
    95  			Pool:       a.Pool,
    96  			Hanged:     a.Hanged,
    97  			Info:       a.Info,
    98  			ExecTaskID: a.ExecTaskID,
    99  		})
   100  	}
   101  
   102  	// TODO: NewEnvironment is the currently hardcoded logic. Relax it.
   103  	task := srv.vrf.GetRunnerTask(a.Pool, NewEnvironment)
   104  	srv.startWaitResult(a.Pool, a.VM, task.ID)
   105  	r.ExecTask = *task
   106  
   107  	return nil
   108  }
   109  
   110  func vmTasksKey(poolID, vmID int) int {
   111  	return poolID*1000 + vmID
   112  }
   113  
   114  func (srv *RPCServer) startWaitResult(poolID, vmID int, taskID int64) {
   115  	srv.mu.Lock()
   116  	defer srv.mu.Unlock()
   117  
   118  	if srv.vmTasksInProgress == nil {
   119  		srv.vmTasksInProgress = make(map[int]map[int64]bool)
   120  	}
   121  
   122  	if srv.vmTasksInProgress[vmTasksKey(poolID, vmID)] == nil {
   123  		srv.vmTasksInProgress[vmTasksKey(poolID, vmID)] =
   124  			make(map[int64]bool)
   125  	}
   126  
   127  	srv.vmTasksInProgress[vmTasksKey(poolID, vmID)][taskID] = true
   128  }
   129  
   130  func (srv *RPCServer) stopWaitResult(poolID, vmID int, taskID int64) {
   131  	srv.mu.Lock()
   132  	defer srv.mu.Unlock()
   133  	delete(srv.vmTasksInProgress[vmTasksKey(poolID, vmID)], taskID)
   134  }
   135  
   136  // cleanup is called when a vm.Instance crashes.
   137  func (srv *RPCServer) cleanup(poolID, vmID int) {
   138  	srv.mu.Lock()
   139  	defer srv.mu.Unlock()
   140  
   141  	// Signal error for every VM related task and let upper level logic to process it.
   142  	for taskID := range srv.vmTasksInProgress[vmTasksKey(poolID, vmID)] {
   143  		srv.vrf.PutExecResult(&ExecResult{
   144  			Pool:       poolID,
   145  			ExecTaskID: taskID,
   146  			Crashed:    true,
   147  			Error:      errors.New("VM crashed during the task execution"),
   148  		})
   149  	}
   150  	delete(srv.vmTasksInProgress, vmTasksKey(poolID, vmID))
   151  }