github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/vminfo/features.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
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/google/syzkaller/pkg/flatrpc"
    11  	"github.com/google/syzkaller/pkg/fuzzer/queue"
    12  	"github.com/google/syzkaller/pkg/ipc"
    13  	"github.com/google/syzkaller/prog"
    14  )
    15  
    16  type Feature struct {
    17  	Enabled   bool
    18  	NeedSetup bool
    19  	Reason    string
    20  }
    21  
    22  type Features map[flatrpc.Feature]Feature
    23  
    24  func (features Features) Enabled() flatrpc.Feature {
    25  	var mask flatrpc.Feature
    26  	for feat, info := range features {
    27  		if info.Enabled {
    28  			mask |= feat
    29  		}
    30  	}
    31  	return mask
    32  }
    33  
    34  func (features Features) NeedSetup() flatrpc.Feature {
    35  	var mask flatrpc.Feature
    36  	for feat, info := range features {
    37  		if info.Enabled && info.NeedSetup {
    38  			mask |= feat
    39  		}
    40  	}
    41  	return mask
    42  }
    43  
    44  type featureResult struct {
    45  	id     flatrpc.Feature
    46  	reason string
    47  }
    48  
    49  func (ctx *checkContext) startFeaturesCheck() {
    50  	testProg := ctx.target.DataMmapProg()
    51  	for feat := range flatrpc.EnumNamesFeature {
    52  		feat := feat
    53  		go func() {
    54  			envFlags, execFlags := ctx.featureToFlags(feat)
    55  			req := &queue.Request{
    56  				Prog:         testProg,
    57  				ReturnOutput: true,
    58  				ReturnError:  true,
    59  				ExecOpts: ipc.ExecOpts{
    60  					EnvFlags:   envFlags,
    61  					ExecFlags:  execFlags,
    62  					SandboxArg: ctx.cfg.SandboxArg,
    63  				},
    64  			}
    65  			ctx.executor.Submit(req)
    66  			res := req.Wait(ctx.ctx)
    67  			reason := ctx.featureSucceeded(feat, testProg, res)
    68  			ctx.features <- featureResult{feat, reason}
    69  		}()
    70  	}
    71  }
    72  
    73  func (ctx *checkContext) finishFeatures(featureInfos []*flatrpc.FeatureInfo) (Features, error) {
    74  	// Feature checking consists of 2 parts:
    75  	//  - we ask executor to try to setup each feature (results are returned in featureInfos)
    76  	//  - we also try to run a simple program with feature-specific flags
    77  	// Here we combine both results.
    78  	features := make(Features)
    79  	for _, info := range featureInfos {
    80  		features[info.Id] = Feature{
    81  			Reason:    info.Reason,
    82  			NeedSetup: info.NeedSetup,
    83  		}
    84  	}
    85  	outputReplacer := strings.NewReplacer(
    86  		"SYZFAIL:", "",
    87  		"\n", ". ",
    88  	)
    89  	for range flatrpc.EnumNamesFeature {
    90  		res := <-ctx.features
    91  		feat := features[res.id]
    92  		if feat.Reason == "" {
    93  			feat.Reason = res.reason
    94  		}
    95  		if feat.Reason == "" {
    96  			feat.Reason = "enabled"
    97  			feat.Enabled = true
    98  		}
    99  		if pos := strings.Index(feat.Reason, "loop exited with status"); pos != -1 {
   100  			feat.Reason = feat.Reason[:pos]
   101  		}
   102  		// If executor exited the output is prefixed with "executor 4: EOF".
   103  		const executorPrefix = ": EOF\n"
   104  		if pos := strings.Index(feat.Reason, executorPrefix); pos != -1 {
   105  			feat.Reason = feat.Reason[pos+len(executorPrefix):]
   106  		}
   107  		feat.Reason = strings.TrimSpace(outputReplacer.Replace(feat.Reason))
   108  		features[res.id] = feat
   109  	}
   110  	if feat := features[flatrpc.FeatureSandboxSetuid]; !feat.Enabled {
   111  		return features, fmt.Errorf("execution of simple program fails: %v", feat.Reason)
   112  	}
   113  	if feat := features[flatrpc.FeatureCoverage]; ctx.cfg.Cover && !feat.Enabled {
   114  		return features, fmt.Errorf("coverage is not supported: %v", feat.Reason)
   115  	}
   116  	return features, nil
   117  }
   118  
   119  // featureToFlags creates ipc flags required to test the feature on a simple program.
   120  // For features that has setup procedure in the executor, we just execute with the default flags.
   121  func (ctx *checkContext) featureToFlags(feat flatrpc.Feature) (flatrpc.ExecEnv, flatrpc.ExecFlag) {
   122  	envFlags := ctx.sandbox
   123  	// These don't have a corresponding feature and are always enabled.
   124  	envFlags |= flatrpc.ExecEnvEnableCloseFds | flatrpc.ExecEnvEnableCgroups | flatrpc.ExecEnvEnableNetReset
   125  	execFlags := flatrpc.ExecFlagThreaded
   126  	switch feat {
   127  	case flatrpc.FeatureCoverage:
   128  		envFlags |= flatrpc.ExecEnvSignal
   129  		execFlags |= flatrpc.ExecFlagCollectSignal | flatrpc.ExecFlagCollectCover
   130  	case flatrpc.FeatureComparisons:
   131  		envFlags |= flatrpc.ExecEnvSignal
   132  		execFlags |= flatrpc.ExecFlagCollectComps
   133  	case flatrpc.FeatureExtraCoverage:
   134  		envFlags |= flatrpc.ExecEnvSignal | flatrpc.ExecEnvExtraCover
   135  		execFlags |= flatrpc.ExecFlagCollectSignal | flatrpc.ExecFlagCollectCover
   136  	case flatrpc.FeatureDelayKcovMmap:
   137  		envFlags |= flatrpc.ExecEnvSignal | flatrpc.ExecEnvDelayKcovMmap
   138  		execFlags |= flatrpc.ExecFlagCollectSignal | flatrpc.ExecFlagCollectCover
   139  	case flatrpc.FeatureSandboxSetuid:
   140  		// We use setuid sandbox feature to test that the simple program
   141  		// succeeds under the actual sandbox (not necessary setuid).
   142  		// We do this because we don't have a feature for sandbox 'none'.
   143  	case flatrpc.FeatureSandboxNamespace:
   144  	case flatrpc.FeatureSandboxAndroid:
   145  	case flatrpc.FeatureFault:
   146  	case flatrpc.FeatureLeak:
   147  	case flatrpc.FeatureNetInjection:
   148  		envFlags |= flatrpc.ExecEnvEnableTun
   149  	case flatrpc.FeatureNetDevices:
   150  		envFlags |= flatrpc.ExecEnvEnableNetDev
   151  	case flatrpc.FeatureKCSAN:
   152  	case flatrpc.FeatureDevlinkPCI:
   153  		envFlags |= flatrpc.ExecEnvEnableDevlinkPCI
   154  	case flatrpc.FeatureNicVF:
   155  		envFlags |= flatrpc.ExecEnvEnableNicVF
   156  	case flatrpc.FeatureUSBEmulation:
   157  	case flatrpc.FeatureVhciInjection:
   158  		envFlags |= flatrpc.ExecEnvEnableVhciInjection
   159  	case flatrpc.FeatureWifiEmulation:
   160  		envFlags |= flatrpc.ExecEnvEnableWifi
   161  	case flatrpc.FeatureLRWPANEmulation:
   162  	case flatrpc.FeatureBinFmtMisc:
   163  	case flatrpc.FeatureSwap:
   164  	default:
   165  		panic(fmt.Sprintf("unknown feature %v", flatrpc.EnumNamesFeature[feat]))
   166  	}
   167  	return envFlags, execFlags
   168  }
   169  
   170  // featureSucceeded checks if execution of a simple program with feature-specific flags succeed.
   171  // This generally checks that just all syscalls were executed and succeed,
   172  // for coverage features we also check that we got actual coverage.
   173  func (ctx *checkContext) featureSucceeded(feat flatrpc.Feature, testProg *prog.Prog,
   174  	res *queue.Result) string {
   175  	if res.Status != queue.Success {
   176  		if len(res.Output) != 0 {
   177  			return string(res.Output)
   178  		}
   179  		if res.Err != nil {
   180  			return res.Err.Error()
   181  		}
   182  		return "test program execution failed"
   183  	}
   184  	if len(res.Info.Calls) != len(testProg.Calls) {
   185  		return fmt.Sprintf("only %v calls are executed out of %v",
   186  			len(res.Info.Calls), len(testProg.Calls))
   187  	}
   188  	for i, call := range res.Info.Calls {
   189  		if call.Errno != 0 {
   190  			return fmt.Sprintf("call %v failed with errno %v", i, call.Errno)
   191  		}
   192  	}
   193  	call := res.Info.Calls[0]
   194  	switch feat {
   195  	case flatrpc.FeatureCoverage:
   196  		if len(call.Cover) == 0 || len(call.Signal) == 0 {
   197  			return "got no coverage"
   198  		}
   199  	case flatrpc.FeatureComparisons:
   200  		if len(call.Comps) == 0 {
   201  			return "got no coverage"
   202  		}
   203  	}
   204  	return ""
   205  }