github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/sys/syz-extract/extract.go (about)

     1  // Copyright 2016 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  	"bytes"
     8  	"flag"
     9  	"fmt"
    10  	"os"
    11  	"path/filepath"
    12  	"runtime"
    13  	"sort"
    14  
    15  	"github.com/google/syzkaller/pkg/ast"
    16  	"github.com/google/syzkaller/pkg/compiler"
    17  	"github.com/google/syzkaller/pkg/osutil"
    18  	"github.com/google/syzkaller/pkg/tool"
    19  	"github.com/google/syzkaller/sys/targets"
    20  )
    21  
    22  var (
    23  	flagOS        = flag.String("os", runtime.GOOS, "target OS")
    24  	flagBuild     = flag.Bool("build", false, "regenerate arch-specific kernel headers")
    25  	flagSourceDir = flag.String("sourcedir", "", "path to kernel source checkout dir")
    26  	flagIncludes  = flag.String("includedirs", "", "path to other kernel source include dirs separated by commas")
    27  	flagBuildDir  = flag.String("builddir", "", "path to kernel build dir")
    28  	flagArch      = flag.String("arch", "", "comma-separated list of arches to generate (all by default)")
    29  	flagConfig    = flag.String("config", "", "base kernel config file instead of defconfig")
    30  )
    31  
    32  type Arch struct {
    33  	target      *targets.Target
    34  	sourceDir   string
    35  	includeDirs string
    36  	buildDir    string
    37  	build       bool
    38  	files       []*File
    39  	configFile  string
    40  	err         error
    41  	done        chan bool
    42  }
    43  
    44  type File struct {
    45  	arch       *Arch
    46  	name       string
    47  	consts     map[string]uint64
    48  	undeclared map[string]bool
    49  	info       *compiler.ConstInfo
    50  	err        error
    51  	done       chan bool
    52  }
    53  
    54  type Extractor interface {
    55  	prepare(sourcedir string, build bool, arches []*Arch) error
    56  	prepareArch(arch *Arch) error
    57  	processFile(arch *Arch, info *compiler.ConstInfo) (map[string]uint64, map[string]bool, error)
    58  }
    59  
    60  var extractors = map[string]Extractor{
    61  	targets.Linux:   new(linux),
    62  	targets.FreeBSD: new(freebsd),
    63  	targets.Darwin:  new(darwin),
    64  	targets.NetBSD:  new(netbsd),
    65  	targets.OpenBSD: new(openbsd),
    66  	"android":       new(linux),
    67  	targets.Fuchsia: new(fuchsia),
    68  	targets.Windows: new(windows),
    69  	targets.Trusty:  new(trusty),
    70  }
    71  
    72  func main() {
    73  	flag.Parse()
    74  	if *flagBuild && *flagBuildDir != "" {
    75  		tool.Failf("-build and -builddir is an invalid combination")
    76  	}
    77  	OS := *flagOS
    78  	extractor := extractors[OS]
    79  	if extractor == nil {
    80  		tool.Failf("unknown os: %v", OS)
    81  	}
    82  	archList, err := tool.ParseArchList(OS, *flagArch)
    83  	if err != nil {
    84  		tool.Failf("failed to parse arch flag: %v", err)
    85  	}
    86  	arches, nfiles, err := createArches(OS, archList, flag.Args())
    87  	if err != nil {
    88  		tool.Fail(err)
    89  	}
    90  	if *flagSourceDir == "" {
    91  		tool.Failf("provide path to kernel checkout via -sourcedir " +
    92  			"flag (or make extract SOURCEDIR)")
    93  	}
    94  	if err := extractor.prepare(*flagSourceDir, *flagBuild, arches); err != nil {
    95  		tool.Fail(err)
    96  	}
    97  
    98  	jobC := make(chan interface{}, len(arches)+nfiles)
    99  	for _, arch := range arches {
   100  		jobC <- arch
   101  	}
   102  
   103  	for p := 0; p < runtime.GOMAXPROCS(0); p++ {
   104  		go worker(extractor, jobC)
   105  	}
   106  
   107  	failed := false
   108  	constFiles := make(map[string]*compiler.ConstFile)
   109  	for _, arch := range arches {
   110  		fmt.Printf("generating %v/%v...\n", OS, arch.target.Arch)
   111  		<-arch.done
   112  		if arch.err != nil {
   113  			failed = true
   114  			fmt.Printf("%v\n", arch.err)
   115  			continue
   116  		}
   117  		for _, f := range arch.files {
   118  			<-f.done
   119  			if f.err != nil {
   120  				failed = true
   121  				fmt.Printf("%v: %v\n", f.name, f.err)
   122  				continue
   123  			}
   124  			if constFiles[f.name] == nil {
   125  				constFiles[f.name] = compiler.NewConstFile()
   126  			}
   127  			constFiles[f.name].AddArch(f.arch.target.Arch, f.consts, f.undeclared)
   128  		}
   129  	}
   130  	for file, cf := range constFiles {
   131  		outname := filepath.Join("sys", OS, file+".const")
   132  		data := cf.Serialize()
   133  		if len(data) == 0 {
   134  			os.Remove(outname)
   135  			continue
   136  		}
   137  		if err := osutil.WriteFile(outname, data); err != nil {
   138  			tool.Failf("failed to write output file: %v", err)
   139  		}
   140  	}
   141  
   142  	if !failed && *flagArch == "" {
   143  		failed = checkUnsupportedCalls(arches)
   144  	}
   145  	for _, arch := range arches {
   146  		if arch.build {
   147  			os.RemoveAll(arch.buildDir)
   148  		}
   149  	}
   150  	if failed {
   151  		os.Exit(1)
   152  	}
   153  }
   154  
   155  func worker(extractor Extractor, jobC chan interface{}) {
   156  	for job := range jobC {
   157  		switch j := job.(type) {
   158  		case *Arch:
   159  			infos, err := processArch(extractor, j)
   160  			j.err = err
   161  			close(j.done)
   162  			if j.err == nil {
   163  				for _, f := range j.files {
   164  					f.info = infos[filepath.Join("sys", j.target.OS, f.name)]
   165  					jobC <- f
   166  				}
   167  			}
   168  		case *File:
   169  			j.consts, j.undeclared, j.err = processFile(extractor, j.arch, j)
   170  			close(j.done)
   171  		}
   172  	}
   173  }
   174  
   175  func createArches(OS string, archArray, files []string) ([]*Arch, int, error) {
   176  	errBuf := new(bytes.Buffer)
   177  	eh := func(pos ast.Pos, msg string) {
   178  		fmt.Fprintf(errBuf, "%v: %v\n", pos, msg)
   179  	}
   180  	top := ast.ParseGlob(filepath.Join("sys", OS, "*.txt"), eh)
   181  	if top == nil {
   182  		return nil, 0, fmt.Errorf("%v", errBuf.String())
   183  	}
   184  	allFiles := compiler.FileList(top, OS, eh)
   185  	if allFiles == nil {
   186  		return nil, 0, fmt.Errorf("%v", errBuf.String())
   187  	}
   188  	if len(files) == 0 {
   189  		for file := range allFiles {
   190  			files = append(files, file)
   191  		}
   192  	}
   193  	nfiles := 0
   194  	var arches []*Arch
   195  	for _, archStr := range archArray {
   196  		buildDir := ""
   197  		if *flagBuild {
   198  			dir, err := os.MkdirTemp("", "syzkaller-kernel-build")
   199  			if err != nil {
   200  				return nil, 0, fmt.Errorf("failed to create temp dir: %w", err)
   201  			}
   202  			buildDir = dir
   203  		} else if *flagBuildDir != "" {
   204  			buildDir = *flagBuildDir
   205  		} else {
   206  			buildDir = *flagSourceDir
   207  		}
   208  
   209  		target := targets.Get(OS, archStr)
   210  		if target == nil {
   211  			return nil, 0, fmt.Errorf("unknown arch: %v", archStr)
   212  		}
   213  
   214  		arch := &Arch{
   215  			target:      target,
   216  			sourceDir:   *flagSourceDir,
   217  			includeDirs: *flagIncludes,
   218  			buildDir:    buildDir,
   219  			build:       *flagBuild,
   220  			done:        make(chan bool),
   221  			configFile:  *flagConfig,
   222  		}
   223  		var archFiles []string
   224  		for _, file := range files {
   225  			meta, ok := allFiles[file]
   226  			if !ok {
   227  				return nil, 0, fmt.Errorf("unknown file: %v", file)
   228  			}
   229  			if meta.NoExtract || !meta.SupportsArch(archStr) {
   230  				continue
   231  			}
   232  			archFiles = append(archFiles, file)
   233  		}
   234  		sort.Strings(archFiles)
   235  		for _, f := range archFiles {
   236  			arch.files = append(arch.files, &File{
   237  				arch: arch,
   238  				name: f,
   239  				done: make(chan bool),
   240  			})
   241  		}
   242  		arches = append(arches, arch)
   243  		nfiles += len(arch.files)
   244  	}
   245  	return arches, nfiles, nil
   246  }
   247  
   248  func checkUnsupportedCalls(arches []*Arch) bool {
   249  	supported := make(map[string]bool)
   250  	unsupported := make(map[string]string)
   251  	for _, arch := range arches {
   252  		for _, f := range arch.files {
   253  			for name := range f.consts {
   254  				supported[name] = true
   255  			}
   256  			for name := range f.undeclared {
   257  				unsupported[name] = f.name
   258  			}
   259  		}
   260  	}
   261  	failed := false
   262  	for name, file := range unsupported {
   263  		if supported[name] {
   264  			continue
   265  		}
   266  		failed = true
   267  		fmt.Printf("%v: %v is unsupported on all arches (typo?)\n",
   268  			file, name)
   269  	}
   270  	return failed
   271  }
   272  
   273  func processArch(extractor Extractor, arch *Arch) (map[string]*compiler.ConstInfo, error) {
   274  	errBuf := new(bytes.Buffer)
   275  	eh := func(pos ast.Pos, msg string) {
   276  		fmt.Fprintf(errBuf, "%v: %v\n", pos, msg)
   277  	}
   278  	top := ast.ParseGlob(filepath.Join("sys", arch.target.OS, "*.txt"), eh)
   279  	if top == nil {
   280  		return nil, fmt.Errorf("%v", errBuf.String())
   281  	}
   282  	infos := compiler.ExtractConsts(top, arch.target, eh)
   283  	if infos == nil {
   284  		return nil, fmt.Errorf("%v", errBuf.String())
   285  	}
   286  	if err := extractor.prepareArch(arch); err != nil {
   287  		return nil, err
   288  	}
   289  	return infos, nil
   290  }
   291  
   292  func processFile(extractor Extractor, arch *Arch, file *File) (map[string]uint64, map[string]bool, error) {
   293  	inname := filepath.Join("sys", arch.target.OS, file.name)
   294  	if file.info == nil {
   295  		return nil, nil, fmt.Errorf("const info for input file %v is missing", inname)
   296  	}
   297  	if len(file.info.Consts) == 0 {
   298  		return nil, nil, nil
   299  	}
   300  	return extractor.processFile(arch, file.info)
   301  }