github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/vminfo/vminfo.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 vminfo extracts information about the target VM.
     5  // The package itself runs on the host, which may be a different OS/arch.
     6  // User of the package first requests set of files that needs to be fetched from the VM
     7  // and set of test programs that needs to be executed in the VM (Checker.RequiredThings),
     8  // then fetches these files and executes test programs, and calls Checker.MachineInfo
     9  // to parse the files and extract information about the VM, and optionally calls
    10  // Checker.Check to obtain list of enabled/disabled syscalls.
    11  // The information includes information about kernel modules and OS-specific info
    12  // (for Linux that includes things like parsed /proc/cpuinfo).
    13  package vminfo
    14  
    15  import (
    16  	"bytes"
    17  	"context"
    18  	"errors"
    19  	"fmt"
    20  	"io"
    21  	"io/fs"
    22  	"os"
    23  	"strings"
    24  
    25  	"github.com/google/syzkaller/pkg/flatrpc"
    26  	"github.com/google/syzkaller/pkg/fuzzer/queue"
    27  	"github.com/google/syzkaller/prog"
    28  	"github.com/google/syzkaller/sys/targets"
    29  )
    30  
    31  type KernelModule struct {
    32  	Name string
    33  	Addr uint64
    34  	Size uint64
    35  	Path string
    36  }
    37  
    38  type Checker struct {
    39  	checker
    40  	cfg      *Config
    41  	source   queue.Source
    42  	executor queue.Executor
    43  }
    44  
    45  type Config struct {
    46  	Target *prog.Target
    47  	VMType string
    48  	// Set of features to check, missing features won't be checked/enabled after Run.
    49  	Features flatrpc.Feature
    50  	// Set of syscalls to check.
    51  	Syscalls   []int
    52  	Debug      bool
    53  	Cover      bool
    54  	Sandbox    flatrpc.ExecEnv
    55  	SandboxArg int64
    56  }
    57  
    58  func New(cfg *Config) *Checker {
    59  	var impl checker
    60  	switch cfg.Target.OS {
    61  	case targets.Linux:
    62  		impl = &linux{vmType: cfg.VMType}
    63  	case targets.NetBSD:
    64  		impl = new(netbsd)
    65  	case targets.OpenBSD:
    66  		impl = new(openbsd)
    67  	default:
    68  		impl = new(nopChecker)
    69  	}
    70  	executor := queue.Plain()
    71  	return &Checker{
    72  		cfg:      cfg,
    73  		checker:  impl,
    74  		executor: executor,
    75  		source:   queue.Deduplicate(executor),
    76  	}
    77  }
    78  
    79  func (checker *Checker) MachineInfo(fileInfos []*flatrpc.FileInfo) ([]*KernelModule, []byte, error) {
    80  	files := createVirtualFilesystem(fileInfos)
    81  	modules, err := checker.parseModules(files)
    82  	if err != nil {
    83  		return nil, nil, err
    84  	}
    85  	info := new(bytes.Buffer)
    86  	tmp := new(bytes.Buffer)
    87  	for _, fn := range checker.machineInfos() {
    88  		tmp.Reset()
    89  		name, err := fn(files, tmp)
    90  		if err != nil {
    91  			if !os.IsNotExist(err) {
    92  				return nil, nil, err
    93  			}
    94  			continue
    95  		}
    96  		if tmp.Len() == 0 {
    97  			continue
    98  		}
    99  		fmt.Fprintf(info, "[%v]\n%s\n%v\n\n", name, tmp.Bytes(), strings.Repeat("-", 80))
   100  	}
   101  	return modules, info.Bytes(), nil
   102  }
   103  
   104  var ErrAborted = errors.New("aborted through the context")
   105  
   106  func (checker *Checker) Run(ctx context.Context, files []*flatrpc.FileInfo, featureInfos []*flatrpc.FeatureInfo) (
   107  	map[*prog.Syscall]bool, map[*prog.Syscall]string, Features, error) {
   108  	cc := newCheckContext(ctx, checker.cfg, checker.checker, checker.executor)
   109  	enabled, disabled, features, err := cc.do(files, featureInfos)
   110  	if ctx.Err() != nil {
   111  		return nil, nil, nil, ErrAborted
   112  	}
   113  	return enabled, disabled, features, err
   114  }
   115  
   116  // Implementation of the queue.Source interface.
   117  func (checker *Checker) Next() *queue.Request {
   118  	return checker.source.Next()
   119  }
   120  
   121  var _ queue.Source = &Checker{}
   122  
   123  type machineInfoFunc func(files filesystem, w io.Writer) (string, error)
   124  
   125  type checker interface {
   126  	RequiredFiles() []string
   127  	CheckFiles() []string
   128  	parseModules(files filesystem) ([]*KernelModule, error)
   129  	machineInfos() []machineInfoFunc
   130  	syscallCheck(*checkContext, *prog.Syscall) string
   131  }
   132  
   133  type filesystem map[string]*flatrpc.FileInfo
   134  
   135  func createVirtualFilesystem(fileInfos []*flatrpc.FileInfo) filesystem {
   136  	files := make(filesystem)
   137  	for _, file := range fileInfos {
   138  		if file.Exists {
   139  			files[file.Name] = file
   140  		}
   141  	}
   142  	return files
   143  }
   144  
   145  func (files filesystem) ReadFile(name string) ([]byte, error) {
   146  	file, ok := files[name]
   147  	if !ok {
   148  		return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
   149  	}
   150  	if file.Error != "" {
   151  		return nil, errors.New(file.Error)
   152  	}
   153  	return file.Data, nil
   154  }
   155  
   156  func (files filesystem) ReadDir(dir string) []string {
   157  	var res []string
   158  	dedup := make(map[string]bool)
   159  	for _, file := range files {
   160  		if len(file.Name) < len(dir)+2 ||
   161  			!strings.HasPrefix(file.Name, dir) ||
   162  			file.Name[len(dir)] != '/' {
   163  			continue
   164  		}
   165  		name := file.Name[len(dir)+1:]
   166  		if slash := strings.Index(name, "/"); slash != -1 {
   167  			name = name[:slash]
   168  		}
   169  		if dedup[name] {
   170  			continue
   171  		}
   172  		dedup[name] = true
   173  		res = append(res, name)
   174  	}
   175  	return res
   176  }
   177  
   178  type nopChecker int
   179  
   180  func (nopChecker) RequiredFiles() []string {
   181  	return nil
   182  }
   183  
   184  func (nopChecker) CheckFiles() []string {
   185  	return nil
   186  }
   187  
   188  func (nopChecker) parseModules(files filesystem) ([]*KernelModule, error) {
   189  	return nil, nil
   190  }
   191  
   192  func (nopChecker) machineInfos() []machineInfoFunc {
   193  	return nil
   194  }
   195  
   196  func (nopChecker) syscallCheck(*checkContext, *prog.Syscall) string {
   197  	return ""
   198  }