Golang程序  |  311行  |  8.95 KB

// Copyright 2015 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

package mgrconfig

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/google/syzkaller/pkg/config"
	"github.com/google/syzkaller/pkg/osutil"
	"github.com/google/syzkaller/prog"
	_ "github.com/google/syzkaller/sys" // most mgrconfig users want targets too
	"github.com/google/syzkaller/sys/targets"
)

type Config struct {
	// Instance name (used for identification and as GCE instance prefix).
	Name string `json:"name"`
	// Target OS/arch, e.g. "linux/arm64" or "linux/amd64/386" (amd64 OS with 386 test process).
	Target string `json:"target"`
	// TCP address to serve HTTP stats page (e.g. "localhost:50000").
	HTTP string `json:"http"`
	// TCP address to serve RPC for fuzzer processes (optional).
	RPC     string `json:"rpc"`
	Workdir string `json:"workdir"`
	// Directory with kernel object files.
	KernelObj string `json:"kernel_obj"`
	// Kernel source directory (if not set defaults to KernelObj).
	KernelSrc string `json:"kernel_src"`
	// Arbitrary optional tag that is saved along with crash reports (e.g. branch/commit).
	Tag string `json:"tag"`
	// Linux image for VMs.
	Image string `json:"image"`
	// SSH key for the image (may be empty for some VM types).
	SSHKey string `json:"sshkey"`
	// SSH user ("root" by default).
	SSHUser string `json:"ssh_user"`

	HubClient string `json:"hub_client"`
	HubAddr   string `json:"hub_addr"`
	HubKey    string `json:"hub_key"`

	// syz-manager will send crash emails to this list of emails using mailx (optional).
	EmailAddrs []string `json:"email_addrs"`

	DashboardClient string `json:"dashboard_client"`
	DashboardAddr   string `json:"dashboard_addr"`
	DashboardKey    string `json:"dashboard_key"`

	// Path to syzkaller checkout (syz-manager will look for binaries in bin subdir).
	Syzkaller string `json:"syzkaller"`
	// Number of parallel processes inside of every VM.
	Procs int `json:"procs"`

	// Type of sandbox to use during fuzzing:
	// "none": don't do anything special (has false positives, e.g. due to killing init), default
	// "setuid": impersonate into user nobody (65534)
	// "namespace": create a new namespace for fuzzer using CLONE_NEWNS/CLONE_NEWNET/CLONE_NEWPID/etc,
	//	requires building kernel with CONFIG_NAMESPACES, CONFIG_UTS_NS, CONFIG_USER_NS,
	//	CONFIG_PID_NS and CONFIG_NET_NS.
	Sandbox string `json:"sandbox"`

	// Use KCOV coverage (default: true).
	Cover bool `json:"cover"`
	// Reproduce, localize and minimize crashers (default: true).
	Reproduce bool `json:"reproduce"`

	EnabledSyscalls  []string `json:"enable_syscalls"`
	DisabledSyscalls []string `json:"disable_syscalls"`
	// Don't save reports matching these regexps, but reboot VM after them,
	// matched against whole report output.
	Suppressions []string `json:"suppressions"`
	// Completely ignore reports matching these regexps (don't save nor reboot),
	// must match the first line of crash message.
	Ignores []string `json:"ignores"`

	// VM type (qemu, gce, android, isolated, etc).
	Type string `json:"type"`
	// VM-type-specific config.
	VM json.RawMessage `json:"vm"`

	// Implementation details beyond this point.
	// Parsed Target:
	TargetOS     string `json:"-"`
	TargetArch   string `json:"-"`
	TargetVMArch string `json:"-"`
	// Syzkaller binaries that we are going to use:
	SyzFuzzerBin   string `json:"-"`
	SyzExecprogBin string `json:"-"`
	SyzExecutorBin string `json:"-"`
}

func LoadData(data []byte) (*Config, error) {
	cfg, err := LoadPartialData(data)
	if err != nil {
		return nil, err
	}
	if err := Complete(cfg); err != nil {
		return nil, err
	}
	return cfg, nil
}

func LoadFile(filename string) (*Config, error) {
	cfg, err := LoadPartialFile(filename)
	if err != nil {
		return nil, err
	}
	if err := Complete(cfg); err != nil {
		return nil, err
	}
	return cfg, nil
}

func LoadPartialData(data []byte) (*Config, error) {
	cfg := defaultValues()
	if err := config.LoadData(data, cfg); err != nil {
		return nil, err
	}
	return loadPartial(cfg)
}

func LoadPartialFile(filename string) (*Config, error) {
	cfg := defaultValues()
	if err := config.LoadFile(filename, cfg); err != nil {
		return nil, err
	}
	return loadPartial(cfg)
}

func defaultValues() *Config {
	return &Config{
		SSHUser:   "root",
		Cover:     true,
		Reproduce: true,
		Sandbox:   "none",
		RPC:       ":0",
		Procs:     1,
	}
}

func loadPartial(cfg *Config) (*Config, error) {
	var err error
	cfg.TargetOS, cfg.TargetVMArch, cfg.TargetArch, err = splitTarget(cfg.Target)
	if err != nil {
		return nil, err
	}
	return cfg, nil
}

func Complete(cfg *Config) error {
	if cfg.TargetOS == "" || cfg.TargetVMArch == "" || cfg.TargetArch == "" {
		return fmt.Errorf("target parameters are not filled in")
	}
	if cfg.Workdir == "" {
		return fmt.Errorf("config param workdir is empty")
	}
	cfg.Workdir = osutil.Abs(cfg.Workdir)
	if cfg.Syzkaller == "" {
		return fmt.Errorf("config param syzkaller is empty")
	}
	if err := completeBinaries(cfg); err != nil {
		return err
	}
	if cfg.HTTP == "" {
		return fmt.Errorf("config param http is empty")
	}
	if cfg.Type == "" {
		return fmt.Errorf("config param type is empty")
	}
	if cfg.Procs < 1 || cfg.Procs > 32 {
		return fmt.Errorf("bad config param procs: '%v', want [1, 32]", cfg.Procs)
	}
	switch cfg.Sandbox {
	case "none", "setuid", "namespace":
	default:
		return fmt.Errorf("config param sandbox must contain one of none/setuid/namespace")
	}
	if err := checkSSHParams(cfg); err != nil {
		return err
	}

	cfg.KernelObj = osutil.Abs(cfg.KernelObj)
	if cfg.KernelSrc == "" {
		cfg.KernelSrc = cfg.KernelObj // assume in-tree build by default
	}
	cfg.KernelSrc = osutil.Abs(cfg.KernelSrc)
	if cfg.HubClient != "" && (cfg.Name == "" || cfg.HubAddr == "" || cfg.HubKey == "") {
		return fmt.Errorf("hub_client is set, but name/hub_addr/hub_key is empty")
	}
	if cfg.DashboardClient != "" && (cfg.Name == "" ||
		cfg.DashboardAddr == "" ||
		cfg.DashboardKey == "") {
		return fmt.Errorf("dashboard_client is set, but name/dashboard_addr/dashboard_key is empty")
	}

	return nil
}

func checkSSHParams(cfg *Config) error {
	if cfg.SSHUser == "" {
		return fmt.Errorf("bad config syzkaller param: ssh user is empty")
	}
	if cfg.SSHKey == "" {
		return nil
	}
	info, err := os.Stat(cfg.SSHKey)
	if err != nil {
		return err
	}
	if info.Mode()&0077 != 0 {
		return fmt.Errorf("sshkey %v is unprotected, ssh will reject it, do chmod 0600", cfg.SSHKey)
	}
	return nil
}

func completeBinaries(cfg *Config) error {
	sysTarget := targets.Get(cfg.TargetOS, cfg.TargetArch)
	if sysTarget == nil {
		return fmt.Errorf("unsupported OS/arch: %v/%v", cfg.TargetOS, cfg.TargetArch)
	}
	cfg.Syzkaller = osutil.Abs(cfg.Syzkaller)
	exe := sysTarget.ExeExtension
	targetBin := func(name, arch string) string {
		return filepath.Join(cfg.Syzkaller, "bin", cfg.TargetOS+"_"+arch, name+exe)
	}
	cfg.SyzFuzzerBin = targetBin("syz-fuzzer", cfg.TargetVMArch)
	cfg.SyzExecprogBin = targetBin("syz-execprog", cfg.TargetVMArch)
	cfg.SyzExecutorBin = targetBin("syz-executor", cfg.TargetArch)
	if !osutil.IsExist(cfg.SyzFuzzerBin) {
		return fmt.Errorf("bad config syzkaller param: can't find %v", cfg.SyzFuzzerBin)
	}
	if !osutil.IsExist(cfg.SyzExecprogBin) {
		return fmt.Errorf("bad config syzkaller param: can't find %v", cfg.SyzExecprogBin)
	}
	if !osutil.IsExist(cfg.SyzExecutorBin) {
		return fmt.Errorf("bad config syzkaller param: can't find %v", cfg.SyzExecutorBin)
	}
	return nil
}

func splitTarget(target string) (string, string, string, error) {
	if target == "" {
		return "", "", "", fmt.Errorf("target is empty")
	}
	targetParts := strings.Split(target, "/")
	if len(targetParts) != 2 && len(targetParts) != 3 {
		return "", "", "", fmt.Errorf("bad config param target")
	}
	os := targetParts[0]
	vmarch := targetParts[1]
	arch := targetParts[1]
	if len(targetParts) == 3 {
		arch = targetParts[2]
	}
	return os, vmarch, arch, nil
}

func ParseEnabledSyscalls(target *prog.Target, enabled, disabled []string) (map[int]bool, error) {
	syscalls := make(map[int]bool)
	if len(enabled) != 0 {
		for _, c := range enabled {
			n := 0
			for _, call := range target.Syscalls {
				if matchSyscall(call.Name, c) {
					syscalls[call.ID] = true
					n++
				}
			}
			if n == 0 {
				return nil, fmt.Errorf("unknown enabled syscall: %v", c)
			}
		}
	} else {
		for _, call := range target.Syscalls {
			syscalls[call.ID] = true
		}
	}
	for _, c := range disabled {
		n := 0
		for _, call := range target.Syscalls {
			if matchSyscall(call.Name, c) {
				delete(syscalls, call.ID)
				n++
			}
		}
		if n == 0 {
			return nil, fmt.Errorf("unknown disabled syscall: %v", c)
		}
	}
	if len(syscalls) == 0 {
		return nil, fmt.Errorf("all syscalls are disabled by disable_syscalls in config")
	}
	return syscalls, nil
}

func matchSyscall(name, pattern string) bool {
	if pattern == name || strings.HasPrefix(name, pattern+"$") {
		return true
	}
	if len(pattern) > 1 && pattern[len(pattern)-1] == '*' &&
		strings.HasPrefix(name, pattern[:len(pattern)-1]) {
		return true
	}
	return false
}