文章目录

  • 1. syz-extract
    • 1-0 总结
    • 1-1. `main()`
    • 1-2 `archList()` — `1-1 (3)` 获取架构 name list
    • 1-3 `createArches()` — `1-1 (3)` 生成Arch结构体数组
    • 1-4 `worker()` — `1-1 (5)` 变量解析
    • 1-5 `processArch()` — `1-4` 搜集字符串const 信息
    • 1-6 `processFile()` — `1-4` 编译并搜集常量
    • 1-7 `extract()` — `1-6` 编译并搜集常量
  • 2. syz-sysgen
    • 2-0 总结
    • 2-1 `main()`
    • 2-2 `processJob()` — `2-1 (2-2)` 生成go源代码
    • 2-3 `generateExecutorSyscalls()` — `[2-2]` 生成syscall信息
    • 2-4 `writeExecutorSyscalls()` — `[2-1] (3)` 生成 executor 头文件
  • 参考

功能总结:编译系统调用模板的原理,可以理解成syzkaller实现了一种描述系统调用的小型的编程语言。

  • syz-extract :根据 syzlang 文件从内核源文件中提取出使用的对应的宏、系统调用号等的值,生成 .const 文件(例如,xxx.txt.const)。
  • syz-sysgen :通过 syzlang 文件与 .const 文件进行语法分析与语义分析,生成抽象语法树,最终生成供 syzkaller 使用的 golang 代码,分为如下四个步骤:
    • assignSyscallNumbers:分配系统调用号,检测不支持的系统调用并丢弃;
    • patchConsts:将 AST 中的常量替换为对应的值;
    • check:进行语义分析;
    • genSyscalls:从 AST 生成 prog 对象。

1. syz-extract

1-0 总结

sys/syz-extract/extract.go: main() 代码流程

  • [1]:解析参数,主要是 OS、arch、syzlang文件名。
  • [2]createArches() —— 根据 OS、arch 生成 Arch 结构体数组;—— 见 1-3
  • [3]sys/syz-extract/linux.go: prepare() —— 初始化操作,如果设置了 build 参数,表示重新生成特定架构的内核头文件,先删除之前编译所生成的文件和配置文件;
  • [4]:对每种arch架构,多线程并发执行 worker()(边进行常量提取,边将先前已有的提取结果存放进文件中,提高效率),真正执行变量解析工作;—— 见 1-4
    • sys/syz-extract/extract.go: processArch():处理传入的 Extractor 和 Arch 结构体,生成 const 信息。—— 1-5

      • pkg/ast/parser.go: ParseGlob() :将编写的txt文件解析成AST;
      • pkg/compiler/consts.go: ExtractConsts():从每个syzlang文件中提取出const值;返回 syzlang 文件名与其用到的常量数组的映射;
      • sys/syz-extract/linux.go: prepareArch():补全某些 arch 的 kernel src 可能会缺失的头文件;
    • sys/syz-extract/linux.go: processFile():编译生成可执行文件,并搜集常量;—— 见 1-6
      • sys/syz-extract/fetch.go: extract():主要函数。
  • [5]:等待 worker() 多线程执行完成,结果保存到 const 文件。

总体流程

  • 调用自定义 compiler 解析 syzlang 为 AST 森林,并依次提取每个 AST 树上的 consts 节点,然后将这些 consts 节点上的字符串放置进模板中,编译模板生成一个 ELF 或其他可执行文件;
  • 分析 ELF 文件上的数据,或者尝试执行可执行文件来解析其输出,以获得各个 consts 字符串所对应的具体整型值;
  • 将获取到的 consts 字符串与具体整型的映射关系,一个个序列化并填入 .const 文件中,这样便生成了对应于每个 syzlang 文件的 .const 文件。

1-1. main()

sys/syz-extract 文件夹下除了 extract.go 还有对应不同操作系统的go文件,我们以 sys/syz-extract/linux.go 为例,从extract.go: main() 函数开始分析。

(1) extract.go: main():解析传入的参数。

  • flagOS 为操作系统字符串
  • flagArch 为待生成的 arch 字符串数组
  • flag.Args() 为待分析的 syzlang 文件名 字符串数组
func main() {flag.Parse()if *flagBuild && *flagBuildDir != "" {tool.Failf("-build and -builddir is an invalid combination")}// 参数列表: 解析出来的参数都会存放在 flagOS / flagBuild / flagSourceDir / flagIncludes / flagBuildDir / flagArch
var (flagOS        = flag.String("os", runtime.GOOS, "target OS")flagBuild     = flag.Bool("build", false, "regenerate arch-specific kernel headers")flagSourceDir = flag.String("sourcedir", "", "path to kernel source checkout dir")flagIncludes  = flag.String("includedirs", "", "path to other kernel source include dirs separated by commas")flagBuildDir  = flag.String("builddir", "", "path to kernel build dir")flagArch      = flag.String("arch", "", "comma-separated list of arches to generate (all by default)")
)

(2) extract.go: main():获取 OS 所对应的 extractor 结构体

 OS := *flagOSextractor := extractors[OS]if extractor == nil {tool.Failf("unknown os: %v", OS)}// extractors 数组如下所示,该数组为不同的 OS 实例化了不同的 Extractor 类。其中 linux OS 所对应的 Extractor 实例(即那三个函数的实现)位于 sys/syz-extract/linux.go 中。
type Extractor interface {prepare(sourcedir string, build bool, arches []*Arch) errorprepareArch(arch *Arch) errorprocessFile(arch *Arch, info *compiler.ConstInfo) (map[string]uint64, map[string]bool, error)
}var extractors = map[string]Extractor{targets.Akaros:  new(akaros),targets.Linux:   new(linux),targets.FreeBSD: new(freebsd),targets.Darwin:  new(darwin),targets.NetBSD:  new(netbsd),targets.OpenBSD: new(openbsd),"android":       new(linux),targets.Fuchsia: new(fuchsia),targets.Windows: new(windows),targets.Trusty:  new(trusty),
}

(3) extract.go: main():用已有的 OS 字符串、archArray 字符串数组(调用archList()获得,参见 1-2),以及 syzlang 文件名数组来生成出对应的 arches 结构体数组

 arches, err := createArches(OS, archList(OS, *flagArch), flag.Args())if err != nil {tool.Fail(err)}if *flagSourceDir == "" {tool.Fail(fmt.Errorf("provide path to kernel checkout via -sourcedir " +"flag (or make extract SOURCEDIR)"))}

(4)extract.go: main() -> sys/syz-extract/linux.go: prepare() :调用了对应extractor的 prepare() 函数,进行初始化操作。

// extract.go: main()if err := extractor.prepare(*flagSourceDir, *flagBuild, arches); err != nil {tool.Fail(err)}

sys/syz-extract/linux.go: prepare() :如果设置build参数表示重新生成特定架构的内核头文件(linux kernel header),于是执行 make mrproper (先删除之前编译所生成的文件和配置文件,备份文件等);如果没有设置build参数则不能指定多个架构。

func (*linux) prepare(sourcedir string, build bool, arches []*Arch) error {if build {// Run 'make mrproper', otherwise out-of-tree build fails.// However, it takes unreasonable amount of time,// so first check few files and if they are missing hope for best.for _, a := range arches {arch := a.target.KernelArchif osutil.IsExist(filepath.Join(sourcedir, ".config")) ||osutil.IsExist(filepath.Join(sourcedir, "init/main.o")) ||osutil.IsExist(filepath.Join(sourcedir, "include/config")) ||osutil.IsExist(filepath.Join(sourcedir, "include/generated/compile.h")) ||osutil.IsExist(filepath.Join(sourcedir, "arch", arch, "include", "generated")) {fmt.Printf("make mrproper ARCH=%v\n", arch)out, err := osutil.RunCmd(time.Hour, sourcedir, "make", "mrproper", "ARCH="+arch,"-j", fmt.Sprint(runtime.NumCPU()))if err != nil {return fmt.Errorf("make mrproper failed: %v\n%s", err, out)}}}} else {if len(arches) > 1 {return fmt.Errorf("more than 1 arch is invalid without -build")}}return nil
}

(5) extract.go: main():创建 go routine 通信管道和启动并行 worker()worker() 函数见 1-4总的来说,如果有多个架构,则启动多线程并发执行各自的 processArch() / processFile()

PS:go routine 是 go 的轻量级线程,其中关键字 go 后面的语句将被放进新的 go routine 中执行。

 jobC := make(chan interface{}, len(arches))for _, arch := range arches {  // 将 arch 结构体放置进 jobC 管道中jobC <- arch}for p := 0; p < runtime.GOMAXPROCS(0); p++ {go worker(extractor, jobC)}

(6) extract.go: main()worker() 线程启动后,main() 函数就需要等待 worker() 处理完成后才能保存处理结果至文件中,这就涉及到了线程协同。

注意到代码中有 <-arch.done<-f.done 语句,这两个语句会一直阻塞等待管道,直到其传来信息。若 worker 函数中对管道执行 close 操作,则被关闭的管道将不再等待,继续向下执行。因此这里 syz-extract 就利用了管道来完成线程协同。

 failed := falseconstFiles := make(map[string]*compiler.ConstFile)for _, arch := range arches {fmt.Printf("generating %v/%v...\n", OS, arch.target.Arch)<-arch.doneif arch.err != nil {failed = truefmt.Printf("%v\n", arch.err)continue}for _, f := range arch.files {<-f.doneif f.err != nil {failed = truefmt.Printf("%v: %v\n", f.name, f.err)continue}if constFiles[f.name] == nil {constFiles[f.name] = compiler.NewConstFile()}constFiles[f.name].AddArch(f.arch.target.Arch, f.consts, f.undeclared)}}

(7) extract.go: main():将生产的结果保存到 .const 文件中。

 for file, cf := range constFiles {outname := filepath.Join("sys", OS, file+".const")data := cf.Serialize()if len(data) == 0 {os.Remove(outname)continue}if err := osutil.WriteFile(outname, data); err != nil {tool.Failf("failed to write output file: %v", err)}}if !failed && *flagArch == "" {failed = checkUnsupportedCalls(arches)}for _, arch := range arches {if arch.build {os.RemoveAll(arch.buildDir)}}if failed {os.Exit(1)}

1-2 archList()1-1 (3) 获取架构 name list

功能:确定待分析的目标架构,如果指定了架构则直接返回,如果未指定架构则返回所有架构的架构name数组。注意所有架构的信息保存在 sys/targets/targets.go: targets.List 中。

参数:OS 字符串、arch 字符串。

func archList(OS, arches string) []string {if arches != "" {return strings.Split(arches, ",")}var archArray []stringfor arch := range targets.List[OS] {archArray = append(archArray, arch)}sort.Strings(archArray)return archArray
}

targets.List 示例:

var List = map[string]map[string]*Target{...,Linux: {AMD64: {PtrSize:          8,PageSize:         4 << 10,LittleEndian:     true,CFlags:           []string{"-m64"},Triple:           "x86_64-linux-gnu",KernelArch:       "x86_64",KernelHeaderArch: "x86",NeedSyscallDefine: func(nr uint64) bool {// Only generate defines for new syscalls// (added after commit 8a1ab3155c2ac on 2012-10-04).return nr >= 313},},...
}

1-3 createArches()1-1 (3) 生成Arch结构体数组

功能:生成与参数对应的 Arch 结构体数组。

func createArches(OS string, archArray, files []string) ([]*Arch, error) {errBuf := new(bytes.Buffer)eh := func(pos ast.Pos, msg string) {fmt.Fprintf(errBuf, "%v: %v\n", pos, msg)}top := ast.ParseGlob(filepath.Join("sys", OS, "*.txt"), eh)if top == nil {return nil, fmt.Errorf("%v", errBuf.String())}allFiles := compiler.FileList(top, OS, eh)if allFiles == nil {return nil, fmt.Errorf("%v", errBuf.String())}var arches []*Archfor _, archStr := range archArray { // [1] 遍历架构 name 数组buildDir := "" // [2] 确定 build 文件夹路径if *flagBuild {dir, err := ioutil.TempDir("", "syzkaller-kernel-build")if err != nil {return nil, fmt.Errorf("failed to create temp dir: %v", err)}buildDir = dir} else if *flagBuildDir != "" {buildDir = *flagBuildDir} else {buildDir = *flagSourceDir}target := targets.Get(OS, archStr) // [3] 获取 targets.List 中对应与 OS 和 arch 的 `Target` 结构体if target == nil {return nil, fmt.Errorf("unknown arch: %v", archStr)}arch := &Arch{ // [4] 创建 arch 结构体target:      target,          // 存放特定 OS 特定 arch 的一些信息sourceDir:   *flagSourceDir,  // kernel source 路径includeDirs: *flagIncludes,   // kernel source header 路径buildDir:    buildDir,        // build 路径build:       *flagBuild,      // bool 值,是否需要重新生成架构指定的 kernel headerdone:        make(chan bool), // 管道,用于 go routine 间通信。当 arch 分析完成后,将会向该管道通知}archFiles := filesif len(archFiles) == 0 {for file, meta := range allFiles {if meta.NoExtract || !meta.SupportsArch(archStr) {continue}archFiles = append(archFiles, file)}}sort.Strings(archFiles)for _, f := range archFiles { // [5] 将 syzlang 文件名数组添加进 arch 结构体中arch.files = append(arch.files, &File{arch: arch,name: f,done: make(chan bool), // 管道,用于 go routine 间通信。当 file 分析完成后,将会向该管道通知})}arches = append(arches, arch)}return arches, nil
}

1-4 worker()1-1 (5) 变量解析

功能:执行真正的变量解析工作。分别对Arch和 syzlang File 调用 processArch() 函数和 processFile() 函数处理。

参数:在 1-1 (5) 中传给 worker()jobC 参数就是 Arch 结构体数组。所以在 worker() 函数中会进入 case *Arch 分支。

func worker(extractor Extractor, jobC chan interface{}) {for job := range jobC {switch j := job.(type) { // [1] j 赋值为 jobC 管道中的对象,初始时为 Arch 结构体case *Arch:infos, err := processArch(extractor, j) // [2] 执行 processArch(), 生成 const 信息j.err = errclose(j.done)if j.err == nil {for _, f := range j.files {f.info = infos[filepath.Join("sys", j.target.OS, f.name)]jobC <- f // [3] processArch() 执行完后,从 infos 映射中遍历取出对应文件的信息,并将其填充至 arch 结构体中 files 结构体数组内的各个元素字段里; 将这个 File 结构体放入 jobC 管道中}}case *File:j.consts, j.undeclared, j.err = processFile(extractor, j.arch, j)close(j.done)}}
}

流程说明:由于 worker() 会循环读取 jobC 内数据,因此接下来便会取出刚刚新放入的 File 结构体,执行 processFile() 函数。在 processFile() 中,syz-extract 将会获取各个 const 变量(例如 O_RDWR)所对应的整型值(例如2)。

注意worker() 中需注意,当 processFile() 执行完成后,worker 函数接下来都会执行 close(j.done) ,将通信管道关闭。这样做的是为了通知 main() 函数 goroutine “某部分工作已经完成”。这个操作有点类似于使用信号量来保证线程同步。

1-5 processArch()1-4 搜集字符串const 信息

(1)processArch()

位置sys/syz-extract/extract.go

功能:处理传入的 Extractor 和 Arch 结构体,生成 const 信息。

说明

  • [2] 调用 pkg/ast/parser.go: ParseGlob() -> pkg/ast/parser.go: Parse() 将编写的txt文件解析成AST。

    • Parse() -> parseTopRecover() 解析出节点加入到top中,并且会在struct前后加上空行,移除重复的空行。
    • parseTopRecover() -> parseTop() 根据标识符的类型调用不同的函数处理。
  • [3] 只是调用了库函数 compiler.ExtractConsts() ,主要调用 Compile() 提取出常量标识符。返回编译 syzlang 结果中的 res.fileConsts 字段;

    • ExtractConsts() -> Compile()

      • createCompiler() :在 syscall_descriptions_syntax.md 中可以看到syzkaller内建的一些别名和模板,在 createCompiler() 函数中对它们进行了初始化。
      • typecheck():分别调用 checkDirectives()checkNames()checkFields()checkTypedefs()checkTypes() 这五个函数进行一些检查。对于可能出现的错误可以对照consts_errors.txt,errors.txt和errors2.txt中给出的例子。
      • extractConsts():返回提取const值所需的文本常量和其它信息的列表(负责提取目录/头文件/定义的name/系统调用名/call/struct/resource中的常量)。列表中的内容分别为常量(consts),定义(defines),包含头文件数组(includeArray),包含目录数组(incdirArray)。
func processArch(extractor Extractor, arch *Arch) (map[string]*compiler.ConstInfo, error) {errBuf := new(bytes.Buffer)eh := func(pos ast.Pos, msg string) { // [1] 定义 error handler 函数 eh()fmt.Fprintf(errBuf, "%v: %v\n", pos, msg)}top := ast.ParseGlob(filepath.Join("sys", arch.target.OS, "*.txt"), eh) // [2] 解析 sys/linux/*.txt 的 syzlang 文件,形成一个 AST 数组。top 变量就是 ast 森林的根结点。if top == nil {return nil, fmt.Errorf("%v", errBuf.String())}infos := compiler.ExtractConsts(top, arch.target, eh) // [3] 调用 compiler.ExtractConsts 获取每个 syzlang 文件中所对应的 const 信息if infos == nil {return nil, fmt.Errorf("%v", errBuf.String())}if err := extractor.prepareArch(arch); err != nil { // [4] 调用 Extractor 中的 prepareArch() 前期准备,补全某些arch缺失的头文件,并重新make内核。return nil, err}return infos, nil // [5] 将获取到的consts infos 返回给调用者

(2)compiler.ExtractConsts() —— [3]

位置pkg/compiler/consts.go

功能res.fileConsts 包含了 syzlang 文件名与其用到的常量数组的映射,以及其所 include 的头文件数组的映射。这些东西都将会用到获取 consts 对应的具体整数操作中。

// pkg/compiler/consts.go
func ExtractConsts(desc *ast.Description, target *targets.Target, eh ast.ErrorHandler) map[string]*ConstInfo {res := Compile(desc, nil, target, eh)if res == nil {return nil}return res.fileConsts
}type Prog struct {Resources []*prog.ResourceDescSyscalls  []*prog.SyscallTypes     []prog.Type// Set of unsupported syscalls/flags.Unsupported map[string]bool// Returned if consts was nil.fileConsts map[string]*ConstInfo   // <----------
}
type ConstInfo struct {File     stringConsts   []stringIncludes []stringIncdirs  []stringDefines  map[string]string
}

(3)extractor.prepareArch() —— [4]

位置sys/syz-extract/linux.go

功能:定义几个头文件。因为某些 arch 的 kernel src 可能会缺失这些文件,需要自己手动补全。补全之后 extractor.prepareArch 会重新执行一次 linux kernel make 生成。

1-6 processFile()1-4 编译并搜集常量

功能sys/syz-extract/extract.go: processFile() 只是封装了 sys/syz-extract/linux.go: processFile()。查找const值(主要在 [3] 处调用 sys/syz-extract/fetch.go: extract() 函数)。

说明:最后生成的 res 映射和 undeclared 集合。res 是 const 字符串与整型的映射;undeclared 是未声明 const 字符串与 bool 值的映射,通常这里的 bool 值都为 true。

undeclared 所对应的常量将在 .const 文件中标明其值为 ???,例如

O_RDWR = 2
MyConst = ???

func (*linux) processFile(arch *Arch, info *compiler.ConstInfo) (map[string]uint64, map[string]bool, error) {headerArch := arch.target.KernelHeaderArch // [1] 生成编译代码模板所用到的 gcc 编译参数:argssourceDir := arch.sourceDirbuildDir := arch.buildDirargs := []string{// This makes the build completely hermetic, only kernel headers are used."-nostdinc","-w", "-fmessage-length=0","-O3", // required to get expected values for some __builtin_constant_p"-I.","-D__KERNEL__","-DKBUILD_MODNAME=\"-\"","-I" + sourceDir + "/arch/" + headerArch + "/include","-I" + buildDir + "/arch/" + headerArch + "/include/generated/uapi","-I" + buildDir + "/arch/" + headerArch + "/include/generated","-I" + sourceDir + "/arch/" + headerArch + "/include/asm/mach-malta","-I" + sourceDir + "/arch/" + headerArch + "/include/asm/mach-generic","-I" + buildDir + "/include","-I" + sourceDir + "/include","-I" + sourceDir + "/arch/" + headerArch + "/include/uapi","-I" + buildDir + "/arch/" + headerArch + "/include/generated/uapi","-I" + sourceDir + "/include/uapi","-I" + buildDir + "/include/generated/uapi","-I" + sourceDir,"-I" + sourceDir + "/include/linux","-I" + buildDir + "/syzkaller","-include", sourceDir + "/include/linux/kconfig.h",}args = append(args, arch.target.CFlags...)for _, incdir := range info.Incdirs {args = append(args, "-I"+sourceDir+"/"+incdir)}if arch.includeDirs != "" {for _, dir := range strings.Split(arch.includeDirs, ",") {args = append(args, "-I"+dir)}}params := &extractParams{ // [2] 准备 extract 参数: params, 准备待使用的CC编译器AddSource:      "#include <asm/unistd.h>",ExtractFromELF: true,TargetEndian:   arch.target.HostEndian,}cc := arch.target.CCompilerres, undeclared, err := extract(info, cc, args, params) // [3] 执行核心函数 extract,生成 res 映射和 undeclared 集合if err != nil {return nil, nil, err}if arch.target.PtrSize == 4 { // [4] 若当前架构是32位, 则 syz-extract 需要使用 mmap2 来替换 mmap,以避免一些可能的错误// mmap syscall on i386/arm is translated to old_mmap and has different signature.// As a workaround fix it up to mmap2, which has signature that we expect.// pkg/csource has the same hack.const mmap = "__NR_mmap"const mmap2 = "__NR_mmap2"if res[mmap] != 0 || undeclared[mmap] {if res[mmap2] == 0 {return nil, nil, fmt.Errorf("%v is missing", mmap2)}res[mmap] = res[mmap2]delete(undeclared, mmap)}}return res, undeclared, nil // [5] 返回结果
}

1-7 extract()1-6 编译并搜集常量

位置sys/syz-extract/fetch.go

功能:调用编译器来编译代码模板,并根据编译出的二进制文件来获取 consts 常量整数。若编译过程出错,则会尝试自动纠错。

参数:Info 便是单个文件存放 const 数据的结构体,cc 是编译器名称字符串,args 是编译器执行参数,params 是用于 extract 执行过程用的选项。

(1)sys/syz-extract/fetch.go: extract()

func extract(info *compiler.ConstInfo, cc string, args []string, params *extractParams) (map[string]uint64, map[string]bool, error) {data := &CompileData{ // [1] 初始化: 声明一系列的 mapextractParams: params,Defines:       info.Defines,Includes:      info.Includes,Values:        info.Consts,}bin := ""                                // 编译生成的程序路径missingIncludes := make(map[string]bool) // 这个字段貌似没有用途,先行忽略undeclared := make(map[string]bool)      // 未定义的 const,通常是自己定义的常量valMap := make(map[string]bool)          // 声明并初始化 valMap 中各个元素为 truefor _, val := range info.Consts {valMap[val] = true}for { // [2] 尝试将 consts 常量字符串与模板C代码结合,并编译结合后的代码,形成一个可执行文件bin1, out, err := compile(cc, args, data) // [2-1] 编译操作, 返回结果分别为编译出的可执行文件路径 / 编译器标准输出信息 / 编译器标准错误信息if err == nil {bin = bin1break}// Some consts and syscall numbers are not defined on some archs.// Figure out from compiler output undefined consts,// and try to compile again without them.// May need to try multiple times because some severe errors terminate compilation.tryAgain := falsefor _, errMsg := range []string{ // [2-2] 遍历所有预先定义的错误信息,并使用正则表达式匹配`error: [‘']([a-zA-Z0-9_]+)[’'] undeclared`,`note: in expansion of macro [‘']([a-zA-Z0-9_]+)[’']`,`note: expanded from macro [‘']([a-zA-Z0-9_]+)[’']`,`error: use of undeclared identifier [‘']([a-zA-Z0-9_]+)[’']`,} {re := regexp.MustCompile(errMsg)matches := re.FindAllSubmatch(out, -1)for _, match := range matches { // [2-3] 如果匹配到了,则将出问题的常量存于 undeclared 中val := string(match[1])if valMap[val] && !undeclared[val] {undeclared[val] = truetryAgain = true}}}if !tryAgain {return nil, nil, fmt.Errorf("failed to run compiler: %v %v\n%v\n%s",cc, args, err, out)}data.Values = nil               // 重置编译用的 consts 数组for _, v := range info.Consts { // [2-4] 将出错的 consts 剔除,并将剩余没出错的 consts 存入编译用的 consts 数组if undeclared[v] {continue}data.Values = append(data.Values, v)}data.Includes = nilfor _, v := range info.Includes { // 这部分代码没咋看懂,因为 data.Includes 没有被重置,没必要重复添加if missingIncludes[v] { // missingIncludes 没有初始化,因此是个一直为空的变量continue}data.Includes = append(data.Includes, v)}}defer os.Remove(bin) // [3] 将新编译出的二进制文件删除var flagVals []uint64var err errorif data.ExtractFromELF { // [4] 从编译出的二进制文件中读取数值,解析并返回flagVals, err = extractFromELF(bin, params.TargetEndian) // [4-1] OS 为 Linux 时, 走这个分支,不会实际执行程序,而是从 ELF 文件中一个名为 syz_extract_data 的 section 中读取常量值} else {flagVals, err = extractFromExecutable(bin) // 若 ExtractFromELF 字段为 false, 实际执行目标程序,解析其输出并转换为整型数组}if err != nil {return nil, nil, err}if len(flagVals) != len(data.Values) {return nil, nil, fmt.Errorf("fetched wrong number of values %v, want != %v",len(flagVals), len(data.Values))}res := make(map[string]uint64)for i, name := range data.Values {res[name] = flagVals[i]}return res, undeclared, nil
}

(2)sys/syz-extract/fetch.go: compile()

功能:将 consts 常量字符串与模板C代码结合,并编译结合后的代码,形成一个可执行文件。

说明:模板C代码存于 srcTemplate 变量,该模板会将先前从 syzlang 收集到的 include、define 和 consts 字符串全部融合:

  • 如果设置了 ExtractFromELF 标志位,则 consts 值将全部放置在一个名为 syz_extract_data 的 section 上
  • 如果没有设置该标志位,则编译出来的程序在执行时将会依次打印 consts 值,以 %llu 的输出格式&使用空格来区分每个变量,输出至 stdout中。这样,sys-extract 就可以通过分析所编译程序的输出,来确定每个 consts 字符串所对应的数值是多少。
func compile(cc string, args []string, data *CompileData) (string, []byte, error) {src := new(bytes.Buffer)                               // 创建填充好后的 C 代码缓冲区if err := srcTemplate.Execute(src, data); err != nil { // 使用传入的 data 对代码模板 srcTemplate 进行填充return "", nil, fmt.Errorf("failed to generate source: %v", err)}binFile, err := osutil.TempFile("syz-extract-bin") // 创建一个临时可执行文件路径if err != nil {return "", nil, err}args = append(args, []string{ // 为编译器添加额外的参数"-x", "c", "-", // -x c : 指定代码语言为 C 语言; - : 指定代码从标准输入而不是从文件中读取"-o", binFile, // 指定文件输出的路径"-w",}...)if data.ExtractFromELF {args = append(args, "-c") // gcc -c 参数: 只编译但不链接 (由于我们测试时使用的是 Linux,因此会进入该分支)}cmd := osutil.Command(cc, args...)                // 执行程序cmd.Stdin = src                                   // 将填充后的代码模板喂给 gcc 编译if out, err := cmd.CombinedOutput(); err != nil { // 将 stdin 和 stdout 的输入糅合,使得他俩的输出完全一致 (就是让 stdin 和 stdout 都指向同一个管道)os.Remove(binFile)return "", out, err}return binFile, nil, nil
}

示例ipc.txt 用模板C代码 srcTemplate ,解析之后的结果如下

#define __asm__(...)
#include <linux/fcntl.h>
#include <linux/stat.h>
#include <linux/ipc.h>
#include <linux/shm.h>
#include <linux/msg.h>
#include <linux.sem.h>#include <asm/unistd.h>
unsigned long phys_base;
#ifdef __phys_addr
unsigned long __phys_addr(unsigned long addr) { return 0;}int printf(const char *format, ...);int main()  {int i;unsigned long long vals[] = {(unsigned long long)GETALL, (unsigned long long)GETNCNT, (unsigned long long)GETPID, (unsigned long long)GETVAL, (unsigned long long)GETZCNT, ...};for (i = 0; i < sizeof(vals)/sizeof(vals[0]); i++) {if (i != 0)printf(" ");printf("%llu", vals[i]);}return 0;
}

2. syz-sysgen

位置sys/syz-sysgen/sysgen.go

功能解析人工编写的syzlang代码文件,并将syzlang内部定义的syscall类型信息转换成后续syzkaller能够使用的数据结构。简单地说,syz-sysgen 解析 syzlang 文件,并为 syz-fuzzer 和 syz-executor 的编译运行做准备。

2-0 总结

sys/syz-sysgen/sysgen.go 代码流程:将整个 prog.SyscallAttrs 结构体的字段名和每个 syscall 所对应的数据,全都转换成了普通字符串型和整型。

  • 遍历 OS 所对应的 sys/<os>/*.txtsys/<os>/*.const文件,分别解析成 AST 树 ( ast.Description 类型) 和 ConstFile 结构体。创建 sys/<os>/gen 文件夹,整个 syz-sysgen 的输出将存放在该文件夹下。
  • 为每个arch 创建一个Job结构体,然后创建多线程并发执行 processJob() 函数,处理每个arch的syzlang AST 和 .const 文件。以下是 processJob() 函数:
    • 从 constFile 结构体取出对应 arch 的 consts (字符串->整型) 映射表,传给Compile()进行编译。
    • Compile()——编译syzlang AST,分析syzlang代码中描述的全部syscall 参数类型信息。返回Prog结构体。
    • 将分析结果,序列化为go语言源代码,留待后续 syz-fuzzer 使用,代码存放在 sys/<OS>/gen/<arch>.go
    • 调用 generateExecutorSyscalls() 函数来创建 Executor 的 syscall 信息,并将其返回给 main 函数。这个信息将用于生成 syz-exexcutor 的 C 代码。
      • generateExecutorSyscalls()——为生成 syz-executor 准备相关的 syscall 数据,就是遍历 Syscall,将对应的 SyscallData 添加到 data.Calls
  • processJob() 生成的 job.ArchData (syscall属性相关的信息) 保存到data中。
  • 调用writeExecutorSyscalls()——生成 syz-executor 所使用的 C 代码头文件写入 executor/defs.h ,将系统调用名和对应的系统调用号写入 executor\syscalls.h 文件。

总体流程

  • 当执行完 syz-extractor 为每个 syslang 文件生成一个常量映射表 .const 文件后,syz-sysgen 便会利用常量映射表,来彻底的解析 syzlang 源码,获取到其中声明的类型信息与 syscall 参数依赖关系;
  • 当这些信息全都收集完毕后,syz-sysgen 便会将这些数据全部序列化为 go 文件,以供后续 syz-fuzzer 所使用;
  • 除此之外,syz-sysgen 还会创建 executor/defs.hexecutor/syscalls.h,将部分信息导出至 C 头文件,以供后续 syz-executor 编译使用。

2-1 main()

(1)将所有OS的类型名都取出来,创建用于存储结果的结构体—data

 defer tool.Init()() // defer 定义的函数将在整个函数正常返回时被执行var OSList []string // [1] 将所有OS的类型名都取出来,创建用于存储结果的结构体—datafor OS := range targets.List {OSList = append(OSList, OS)}sort.Strings(OSList)data := &ExecutorData{}

(2)for 循环,遍历OSList中每个OS字符串,并解析其中的syzlang代码。分为3部分来分析。

(2-1)将当前遍历到的 OS 所对应的 sys/<os>/*.txtsys/<os>/*.const文件,分别解析成 AST 树 ( ast.Description 类型) 和 ConstFile 结构体。之后创建 sys/<os>/gen 文件夹,整个 syz-sysgen 的输出将存放在该文件夹下:

 for _, OS := range OSList { // [2] for 循环,遍历OSList中每个OS字符串,并解析其中的syzlang代码descriptions := ast.ParseGlob(filepath.Join(*srcDir, "sys", OS, "*.txt"), nil)if descriptions == nil { // [2-1] syzlang文件解析成AST数树os.Exit(1)}constFile := compiler.DeserializeConstFile(filepath.Join(*srcDir, "sys", OS, "*.const"), nil)if constFile == nil { // .const 文件解析成 ConstFile 结构体os.Exit(1)}osutil.MkdirAll(filepath.Join(*outDir, "sys", OS, "gen")) // syz-sysgen 输出结果存放在本目录var archs []stringfor arch := range targets.List[OS] {archs = append(archs, arch)}sort.Strings(archs)...}

(2-2)为每个arch 创建一个Job结构体,然后创建多线程并发执行 processJob() 函数,处理每个arch的syzlang AST 和 .const 文件。

注意processJob() 编译先前解析的syzlang AST, 分析其中的类型信息与依赖关系,将其序列化为 golang 代码至 sys/<OS>/gen/<arch>.go 中,同时将syscall 属性相关的信息保存在 job.ArchData 中,供后续生成 sys-executor 关键头文件代码所用。

 for _, OS := range OSList {...var jobs []*Job // [2-2] 为每个arch都创建1个Job结构体, 将其添加进数组jobs中, 并为数组执行排序操作for _, arch := range archs {jobs = append(jobs, &Job{Target:      targets.List[OS][arch],Unsupported: make(map[string]bool),})}sort.Slice(jobs, func(i, j int) bool {return jobs[i].Target.Arch < jobs[j].Target.Arch})var wg sync.WaitGroup // sync.WaitGroup 结构体, 用于等待指定数量的 go routine 集合执行完成, 类似于信号量wg.Add(len(jobs))     // wg.Add(): 增加内部计数器值; wg.Done(): 减小内部计数器值; wg.Wait():判断内部计数器值状态, 进而选择是否挂起等待for _, job := range jobs { // 遍历 jobs 数组中每个 job, 创建 go routine 并行执行这些 jobjob := jobgo func() {defer wg.Done()processJob(job, descriptions, constFile) // processJob() 重要函数}()}wg.Wait()...}

(2-3)将 processJob() 生成的 job.ArchData (syscall属性相关的信息) 保存到data中。

 for _, OS := range OSList {...var syscallArchs []ArchDataunsupported := make(map[string]int)for _, job := range jobs {if !job.OK {fmt.Printf("compilation of %v/%v target failed:\n", job.Target.OS, job.Target.Arch)for _, msg := range job.Errors {fmt.Print(msg)}os.Exit(1)}syscallArchs = append(syscallArchs, job.ArchData)for u := range job.Unsupported {unsupported[u]++}}data.OSes = append(data.OSes, OSData{ // [2-3] 将 processJob() 生成的 job.ArchData (syscall属性相关的信息) 保存到data中GOOS:  OS,Archs: syscallArchs,})for what, count := range unsupported {if count == len(jobs) {tool.Failf("%v is unsupported on all arches (typo?)", what)}}}

(3)分别将 prog.SyscallAttrsprog.CallProps 这两个结构体对应的字段名存到 data.CallAttrsdata.CallProps

 attrs := reflect.TypeOf(prog.SyscallAttrs{}) // [3] 分别将 prog.SyscallAttrs 和 prog.CallProps 这两个结构体对应的字段名存起来for i := 0; i < attrs.NumField(); i++ {data.CallAttrs = append(data.CallAttrs, prog.CppName(attrs.Field(i).Name))}props := prog.CallProps{}props.ForeachProp(func(name, _ string, value reflect.Value) {data.CallProps = append(data.CallProps, CallPropDescription{Type: value.Kind().String(),Name: prog.CppName(name),})})writeExecutorSyscalls(data)
}

总结:syz-sysgen 将整个 prog.SyscallAttrs 结构体的字段名和每个 syscall 所对应的数据,全都转换成了普通字符串型和整型。看上去这像是要用这些数据来填充 C 语言模板?接下来分析 writeExecutorSyscalls() 函数,见 2-4

2-2 processJob()2-1 (2-2) 生成go源代码

功能:编译传入的 syzlang AST,分析其中的 syscall 类型信息等,并反序列化为一个 golang 语法源码。

参数:传入的参数 job ,结构体声明如下:

type Job struct {Target      *targets.Target // 存放着一些关于特定 OS 特定 arch 的一些常量信息OK          boolErrors      []string        // 保存报错信息的字符串集合,一条字符串表示一行报错信息Unsupported map[string]bool // 存放不支持的 syscall 集合ArchData    ArchData        // 存放待从 worker routine 返回给 main 函数的数据
}

说明

  • [4] 中再次调用 Compile() 编译 syzlang AST,但不同于先前的 syz-extract ,这次提供了 consts 信息,因此会执行完整的编译过程,分析syzlang代码中描述的全部syscall 参数类型信息。返回Prog结构体:

    // Prog is description compilation result.
    type Prog struct {Resources []*prog.ResourceDesc        // 类型信息Syscalls  []*prog.Syscall            // syscall 的描述Types     []prog.Type                 // 类型信息// Set of unsupported syscalls/flags.Unsupported map[string]bool// Returned if consts was nil.fileConsts map[string]*ConstInfo   // 空
    }
    
    • Compile() 除了调用 createCompiler() 函数和 typecheck() 函数,接下来首先调用的是assignSyscallNumbers() / patchConsts() / check() 函数。

      • assignSyscallNumbers() 函数分配系统调用号,检测不受支持的系统调用并丢弃。
      • patchConsts() 函数将AST中的常量patch成对应的值。
      • check() 函数对AST进行语义检查。
      • genSyscalls() 主要是调用了 genSyscall() 函数,然后按照系统调用名排序。
        • genSyscall() 函数中调用 genType() 函数生成返回值,调用 genFieldArray() 函数生成每个参数。
      • 返回的 Prog 对象中调用 genResources() 函数生成资源,generateTypes() 函数生成结构体的描述。
  • [5] 将分析结果,序列化为go语言源代码,留待后续 syz-fuzzer 使用,代码存放在 sys/<OS>/gen/<arch>.go

  • [6] 调用 generateExecutorSyscalls() 函数来创建 Executor 的 syscall 信息,并将其返回给 main 函数。这个信息将用于生成 syz-exexcutor 的 C 代码。

func processJob(job *Job, descriptions *ast.Description, constFile *compiler.ConstFile) {eh := func(pos ast.Pos, msg string) { // [1] 生成一个 error handler, 用于输出错误信息;job.Errors = append(job.Errors, fmt.Sprintf("%v: %v\n", pos, msg))}consts := constFile.Arch(job.Target.Arch) // [2] 从 constFile 结构体取出对应 arch 的 consts 字符串->整型 映射表if job.Target.OS == targets.TestOS {      // [3] 过滤掉自己开发人员测试用的 testOS (targets.TestOS 即为字符串 test)constInfo := compiler.ExtractConsts(descriptions, job.Target, eh)compiler.FabricateSyscallConsts(job.Target, constInfo, consts)}prog := compiler.Compile(descriptions, consts, job.Target, eh) // [4] 对 syzlang AST 进行编译, 继续分析 AST 信息。if prog == nil {                                               // 这次编译提供了consts信息,因此会执行完整的编译过程return}for what := range prog.Unsupported {job.Unsupported[what] = true}// [5] 将分析结果,序列化为go语言源代码,留待后续 syz-fuzzer 使用,代码存放在 sys/<OS>/gen/<arch>.gosysFile := filepath.Join(*outDir, "sys", job.Target.OS, "gen", job.Target.Arch+".go")out := new(bytes.Buffer)generate(job.Target, prog, consts, out)rev := hash.String(out.Bytes())fmt.Fprintf(out, "const revision_%v = %q\n", job.Target.Arch, rev)writeSource(sysFile, out.Bytes())// [6] 调用 generateExecutorSyscalls 函数来创建 Executor 的 syscall 信息,并将其返回给 main 函数job.ArchData = generateExecutorSyscalls(job.Target, prog.Syscalls, rev)// Don't print warnings, they are printed in syz-check.job.Errors = niljob.OK = true
}

golang 代码示例:以 /sys/linux/gen/amd64.go 为例。

说明

  • 开头的 init() 函数用于将当前这个 linux amd64 的 target,注册进 targets 数组中以供后续 syz-fuzzer 取出使用。
  • 其中声明了多个数组:
    • resources_amd64 数组:存放着每个 syzlang 代码中声明的 resource 变量
    • syscalls_amd64 数组:存放着每个 syscall 所对应的名称、调用号,以及各个参数的名称和类型。
    • types_amd64 数组:每个类型的具体信息,例如数组、结构体类型信息等等
    • consts_amd64:存放 consts 字符串与整型的映射关系
    • revision_amd64:amd64.go 源码的哈希值
// AUTOGENERATED FILE
// +build !codeanalysis
// +build !syz_target syz_target,syz_os_linux,syz_arch_amd64package genimport . "github.com/google/syzkaller/prog"
import . "github.com/google/syzkaller/sys/linux"func init() {RegisterTarget(&Target{OS: "linux", Arch: "amd64", Revision: revision_amd64, PtrSize: 8, PageSize: 4096, NumPages: 4096, DataOffset: 536870912, LittleEndian: true, ExecutorUsesShmem: true, Syscalls: syscalls_amd64, Resources: resources_amd64, Consts: consts_amd64}, types_amd64, InitTarget)
}var resources_amd64 = []*ResourceDesc{{Name:"ANYRES16",Kind:[]string{"ANYRES16"},Values:[]uint64{18446744073709551615,0}},
{Name:"ANYRES32",Kind:[]string{"ANYRES32"},Values:[]uint64{18446744073709551615,0}},
{Name:"ANYRES64",Kind:[]string{"ANYRES64"},Values:[]uint64{18446744073709551615,0}},
{Name:"IMG_DEV_VIRTADDR",Kind:[]string{"IMG_DEV_VIRTADDR"},Values:[]uint64{0}},
{Name:"IMG_HANDLE",Kind:[]string{"IMG_HANDLE"},Values:[]uint64{0}},
{Name:"assoc_id",Kind:[]string{"assoc_id"},Values:[]uint64{0}},
....
}var syscalls_amd64 = []*Syscall{{NR:43,Name:"accept",CallName:"accept",Args:[]Field{{Name:"fd",Type:Ref(11199)},
{Name:"peer",Type:Ref(10021)},
{Name:"peerlen",Type:Ref(10305)},
},Ret:Ref(11199)},
{NR:43,Name:"accept$alg",CallName:"accept",Args:[]Field{{Name:"fd",Type:Ref(11202)},
{Name:"peer",Type:Ref(4943)},
{Name:"peerlen",Type:Ref(4943)},
},Ret:Ref(11203)},
{NR:43,Name:"accept$ax25",CallName:"accept",Args:[]Field{{Name:"fd",Type:Ref(11204)},
{Name:"peer",Type:Ref(10033)},
{Name:"peerlen",Type:Ref(10305)},
},Ret:Ref(11204)},
{NR:43,Name:"accept$inet",CallName:"accept",Args:[]Field{{Name:"fd",Type:Ref(11223)},
{Name:"peer",Type:Ref(10025)},
{Name:"peerlen",Type:Ref(10305)},
},Ret:Ref(11223)},
....
}var types_amd64 = []Type{&ArrayType{TypeCommon:TypeCommon{TypeName:"array",TypeAlign:1,IsVarlen:true},Elem:Ref(17155)},
&ArrayType{TypeCommon:TypeCommon{TypeName:"array",TypeAlign:1,IsVarlen:true},Elem:Ref(14707),Kind:1,RangeEnd:32},
&ArrayType{TypeCommon:TypeCommon{TypeName:"array",TypeAlign:1,IsVarlen:true},Elem:Ref(14707),Kind:1,RangeEnd:8},
&ArrayType{TypeCommon:TypeCommon{TypeName:"array",TypeAlign:1,IsVarlen:true},Elem:Ref(14560)},
&ArrayType{TypeCommon:TypeCommon{TypeName:"array",TypeAlign:1,IsVarlen:true},Elem:Ref(14575)},
....
}var consts_amd64 = []ConstValue{{"ABS_CNT",64},
{"ABS_MAX",63},
{"ACL_EXECUTE",1},
{"ACL_GROUP",8},
{"ACL_GROUP_OBJ",4},
{"ACL_LINK",1},
....
}const revision_amd64 = "e61403f96ca19fc071d8e9c946b2259a2804c68e"

2-3 generateExecutorSyscalls()[2-2] 生成syscall信息

功能:为生成 syz-executor 准备相关的 syscall 数据,因此起名神似 生成(generate) executor 的 syscall 数据。具体来说,就是遍历 Syscall,将对应的 SyscallData 添加到 data.Calls

func generateExecutorSyscalls(target *targets.Target, syscalls []*prog.Syscall, rev string) ArchData {data := ArchData{ // [1] 创建 ArchData结构体,该结构体最后会返回给 main()Revision:   rev,GOARCH:     target.Arch,PageSize:   target.PageSize,NumPages:   target.NumPages,DataOffset: target.DataOffset,}if target.ExecutorUsesForkServer { // 若目标 OS & arch 对应的target结构体,设置了对 ForkServer 和 Shmem(共享内存)的支持, 则设置data中相应字段, 这样 syz-executor便能使用这两种技术加速fuzzdata.ForkServer = 1}if target.ExecutorUsesShmem {data.Shmem = 1}defines := make(map[string]string)for _, c := range syscalls { // [2] 遍历各个 Syscall 类型的结构体var attrVals []uint64attrs := reflect.ValueOf(c.Attrs) // 将变量 c 中结构体 SyscallAttrs 里的各个字段取出,并将其依次存放至整型数组 attrVals (bool值和整型值)last := -1for i := 0; i < attrs.NumField(); i++ {attr := attrs.Field(i)val := uint64(0)switch attr.Type().Kind() {case reflect.Bool:if attr.Bool() {val = 1}case reflect.Uint64:val = attr.Uint()default:panic("unsupported syscall attribute type")}attrVals = append(attrVals, val)if val != 0 {last = i}} // 再使用生成的 attrVals 数组进一步生成 SyscallData 结构体data.Calls = append(data.Calls, newSyscallData(target, c, attrVals[:last+1]))// Some syscalls might not be present on the compiling machine, so we// generate definitions for them.if target.SyscallNumbers && !strings.HasPrefix(c.CallName, "syz_") &&target.NeedSyscallDefine(c.NR) {defines[target.SyscallPrefix+c.CallName] = fmt.Sprintf("%d", c.NR)}}sort.Slice(data.Calls, func(i, j int) bool { // [3] 将生成的 data.Calls 数组进行排序,并返回 data 变量return data.Calls[i].Name < data.Calls[j].Name})// Get a sorted list of definitions.defineNames := []string{}for key := range defines {defineNames = append(defineNames, key)}sort.Strings(defineNames)for _, key := range defineNames {data.Defines = append(data.Defines, Define{key, defines[key]})}return data
}

说明

  • [2] 作用,遍历各个 Syscall 类型的结构体, 将变量 c 中结构体 SyscallAttrs 里的各个字段取出,并将其依次存放至整型数组 attrVals (bool值和整型值);再使用生成的 attrVals 数组进一步生成 SyscallData 结构体

  • Syscall 结构体 -> SyscallAttrs 结构体

    type Syscall struct {ID          intNR          uint64 // kernel syscall numberName        stringCallName    stringMissingArgs int // number of trailing args that should be zero-filledArgs        []FieldRet         TypeAttrs       SyscallAttrsinputResources  []*ResourceDescoutputResources []*ResourceDesc
    }
    type SyscallAttrs struct {Disabled      boolTimeout       uint64ProgTimeout   uint64IgnoreReturn  boolBreaksReturns bool
    }
    
  • data.CallsSyscallData 结构体示例与说明:

    [0]:<main.SyscallData>Name: "accept"CallName: "accept"NR: 30NeedCall: false// sys/syz-sysgen/sysgen.go
    type SyscallData struct {Name     string      // syzlang 中的调用名,例如 accept$inetCallName string      // 实际的 syscall 调用名,例如 acceptNR       int32       // syscall 对应的调用号,例如 30NeedCall bool        // 一个用于后续的 syz-executor 源码生成的标志,后面会提到Attrs    []uint64    // 存放分析 syzlang 所生成的 SyscallAttrs 数据数组
    }
    

2-4 writeExecutorSyscalls()[2-1] (3) 生成 executor 头文件

功能:生成 syz-executor 所使用的 C 代码头文件写入 executor/defs.h ,将系统调用名和对应的系统调用号写入 executor\syscalls.h 文件。

func writeExecutorSyscalls(data *ExecutorData) {osutil.MkdirAll(filepath.Join(*outDir, "executor"))sort.Slice(data.OSes, func(i, j int) bool {return data.OSes[i].GOOS < data.OSes[j].GOOS})buf := new(bytes.Buffer) // [1] 生成 defs.h 文件if err := defsTempl.Execute(buf, data); err != nil {tool.Failf("failed to execute defs template: %v", err)}writeFile(filepath.Join(*outDir, "executor", "defs.h"), buf.Bytes())buf.Reset() // [2] 生成 syscalls.h 文件if err := syscallsTempl.Execute(buf, data); err != nil {tool.Failf("failed to execute syscalls template: %v", err)}writeFile(filepath.Join(*outDir, "executor", "syscalls.h"), buf.Bytes())
}

(1)defsTempl 模板

说明:syz-sysgen 会将把先前 generateExecutorSyscalls 函数中所生成的 ArchData 结构体数据,导出至 executor/defs.h 文件中,供后续编译 syz-executor 所使用。syz-sysgen 将所有OS所有架构所对应的 ArchData 数据全部导出至一个文件中,并使用宏定义来选择启用哪一部分的数据。

模板如下:混杂着 C 宏定义与模板描述。

var defsTempl = template.Must(template.New("").Parse(`// AUTOGENERATED FILEstruct call_attrs_t { {{range $attr := $.CallAttrs}}uint64_t {{$attr}};{{end}}
};struct call_props_t { {{range $attr := $.CallProps}}{{$attr.Type}} {{$attr.Name}};{{end}}
};#define read_call_props_t(var, reader) { \{{range $attr := $.CallProps}}(var).{{$attr.Name}} = ({{$attr.Type}})(reader); \{{end}}
}{{range $os := $.OSes}}
#if GOOS_{{$os.GOOS}}
#define GOOS "{{$os.GOOS}}"
{{range $arch := $os.Archs}}
#if GOARCH_{{$arch.GOARCH}}
#define GOARCH "{{.GOARCH}}"
#define SYZ_REVISION "{{.Revision}}"
#define SYZ_EXECUTOR_USES_FORK_SERVER {{.ForkServer}}
#define SYZ_EXECUTOR_USES_SHMEM {{.Shmem}}
#define SYZ_PAGE_SIZE {{.PageSize}}
#define SYZ_NUM_PAGES {{.NumPages}}
#define SYZ_DATA_OFFSET {{.DataOffset}}
{{range $c := $arch.Defines}}#ifndef {{$c.Name}}
#define {{$c.Name}} {{$c.Value}}
#endif
{{end}}#endif
{{end}}
#endif
{{end}}
`))

executor/defs.h 示例

// AUTOGENERATED FILEstruct call_attrs_t { uint64_t disabled;uint64_t timeout;uint64_t prog_timeout;uint64_t ignore_return;uint64_t breaks_returns;
};struct call_props_t { int fail_nth;
};#define read_call_props_t(var, reader) { \(var).fail_nth = (int)(reader); \
}#if GOOS_akaros
#define GOOS "akaros"#if GOARCH_amd64
#define GOARCH "amd64"
#define SYZ_REVISION "361c8bb8e04aa58189bcdd153dc08078d629c0b5"
#define SYZ_EXECUTOR_USES_FORK_SERVER 1
#define SYZ_EXECUTOR_USES_SHMEM 0
#define SYZ_PAGE_SIZE 4096
#define SYZ_NUM_PAGES 4096
#define SYZ_DATA_OFFSET 536870912
#endif#endif...#if GOOS_linux
#define GOOS "linux"...
#if GOARCH_amd64
#define GOARCH "amd64"
#define SYZ_REVISION "e61403f96ca19fc071d8e9c946b2259a2804c68e"
#define SYZ_EXECUTOR_USES_FORK_SERVER 1
#define SYZ_EXECUTOR_USES_SHMEM 1
#define SYZ_PAGE_SIZE 4096
#define SYZ_NUM_PAGES 4096
#define SYZ_DATA_OFFSET 536870912
#endif...
#endif...#if GOOS_windows
#define GOOS "windows"#if GOARCH_amd64
#define GOARCH "amd64"
#define SYZ_REVISION "8967babc353ed00daaa6992068d3044bad9d29fa"
#define SYZ_EXECUTOR_USES_FORK_SERVER 0
#define SYZ_EXECUTOR_USES_SHMEM 0
#define SYZ_PAGE_SIZE 4096
#define SYZ_NUM_PAGES 4096
#define SYZ_DATA_OFFSET 536870912
#endif#endif

(2)syscallsTempl 模板

说明executor/syscalls.h 下会存放着各个 syzlang 中所声明的 syscall 名与 syscall调用号的映射关系,以及可能有的 SyscallData。同时,也是使用宏定义来控制使用哪个OS哪个Arch下的 syscalls 映射关系

模板如下

var syscallsTempl = template.Must(template.New("").Parse(`// AUTOGENERATED FILE
// clang-format off
{{range $os := $.OSes}}
#if GOOS_{{$os.GOOS}}
{{range $arch := $os.Archs}}
#if GOARCH_{{$arch.GOARCH}}
const call_t syscalls[] = {
{{range $c := $arch.Calls}}    {"{{$c.Name}}", {{$c.NR}}{{if or $c.Attrs $c.NeedCall}}, { {{- range $attr := $c.Attrs}}{{$attr}}, {{end}}}{{end}}{{if $c.NeedCall}}, (syscall_t){{$c.CallName}}{{end}}},
{{end}}};
#endif
{{end}}
#endif
{{end}}
`))

executor/syscalls.h 示例

...
#if GOOS_linux
...
#if GOARCH_amd64
const call_t syscalls[] = {{"accept", 43},{"accept$alg", 43},{"accept$ax25", 43},{"accept$inet", 43},{"accept$inet6", 43},{"accept$netrom", 43},{"accept$nfc_llcp", 43},....,{"bind", 49},{"bind$802154_dgram", 49},{"bind$802154_raw", 49},{"bind$alg", 49},{"bind$ax25", 49},{"bind$bt_hci", 49},{"bind$bt_l2cap", 49},....{"prctl$PR_CAPBSET_DROP", 167, {0, 0, 0, 1, 1, }},{"prctl$PR_CAPBSET_READ", 167, {0, 0, 0, 1, 1, }},{"prctl$PR_CAP_AMBIENT", 167, {0, 0, 0, 1, 1, }},....
}
#endif
...
#endif
...

参考

内核漏洞挖掘技术系列(4)——syzkaller(2)

syzkaller 源码阅读笔记-1

syzkaller 源码阅读笔记1(syz-extract syz-sysgen)相关推荐

  1. syzkaller 源码阅读笔记3(syz-fuzzer)

    文章目录 1. `main()` 2. `poll()` 3. `BuildChoiceTable()` 3-1 `calcStaticPriorities()` 3-2 `calcDynamicPr ...

  2. syzkaller 源码阅读笔记2(syz-manager)

    文章目录 1. 介绍 2. `main()` 3. `RunManager()` 4. `vmLoop()` 4-1 crash复现 (1)`extractProg()` (3)`extractC() ...

  3. Transformers包tokenizer.encode()方法源码阅读笔记

    Transformers包tokenizer.encode()方法源码阅读笔记_天才小呵呵的博客-CSDN博客_tokenizer.encode

  4. 源码阅读笔记 BiLSTM+CRF做NER任务 流程图

    源码阅读笔记 BiLSTM+CRF做NER任务(二) 源码地址:https://github.com/ZhixiuYe/NER-pytorch 本篇正式进入源码的阅读,按照流程顺序,一一解剖. 一.流 ...

  5. 代码分析:NASM源码阅读笔记

    NASM源码阅读笔记 NASM(Netwide Assembler)的使用文档和代码间的注释相当齐全,这给阅读源码 提供了很大的方便.按作者的说法,这是一个模块化的,可重用的x86汇编器, 而且能够被 ...

  6. CI框架源码阅读笔记4 引导文件CodeIgniter.php

    到了这里,终于进入CI框架的核心了.既然是"引导"文件,那么就是对用户的请求.参数等做相应的导向,让用户请求和数据流按照正确的线路各就各位.例如,用户的请求url: http:// ...

  7. Yii源码阅读笔记 - 日志组件

    2015-03-09 一 By youngsterxyf 使用 Yii框架为开发者提供两个静态方法进行日志记录: Yii::log($message, $level, $category); Yii: ...

  8. AQS源码阅读笔记(一)

    AQS源码阅读笔记 先看下这个类张非常重要的一个静态内部类Node.如下: static final class Node {//表示当前节点以共享模式等待锁static final Node SHA ...

  9. 【Flink】Flink 源码阅读笔记(20)- Flink 基于 Mailbox 的线程模型

    1.概述 转载:Flink 源码阅读笔记(20)- Flink 基于 Mailbox 的线程模型 相似文章:[Flink]Flink 基于 MailBox 实现的 StreamTask 线程模型 Fl ...

最新文章

  1. python使用正则表达式判别用户输入密码的强弱并提示
  2. CV领域论文常用单词汇总
  3. Highlight – 代码高亮html输出软件
  4. docker下载慢,卡顿解决办法——免费安装人人都有的docker加速器
  5. elementui下拉框选择图片_Element UI系列:Select下拉框实现默认选择
  6. Qt 自定义界面(实现无边框、可移动)
  7. PHP学习笔记01——基础语法
  8. python数据分析百度云资源_数据分析师视频教程百度云网盘下载
  9. html css开关按钮样式,纯CSS实现开关按钮
  10. 线性表之带头双向循环链表
  11. lwj_C#_集合的使用、接口方法的实现;
  12. uniapp h5浏览器文件下载
  13. HTML记事本滚动字幕怎么弄,滚动字幕怎么用word做 如何用WORD制作滚动字幕
  14. phpnow怎么改php版本,phpnow升级php版本的方法
  15. 24 个必须掌握的数据库⾯试题
  16. 大于3小于4的整数bleem_六年级数学小升初知识点梳理
  17. 大数据即席查询工具——秒级响应
  18. 绝地求生全军出击服务器维护中,绝地求生全军出击卡住了进不去游戏解决办法...
  19. 计算机分屏解决方案,多屏显示解决方案
  20. [Pytorch框架] PyTorch 中文手册

热门文章

  1. python设置打印机参数_打印文件并配置打印机设置
  2. [ubuntu]用SSH实现ubuntu系统互联并传输文件(无图形界面)
  3. 2021全国计算机ps一级,PS2021版全新功能:“颜色分级”堪称最强调色工具
  4. delphi 项目软件架构升级-行业软件
  5. 梦幻西游 python.dll_梦幻西游游戏参数
  6. 什么是 POD 数据类型?
  7. SRE学习笔记2:衡量系统稳定性的指标
  8. myeclipse/ eclipse断点没有对勾,断点打不上,方法无法访问
  9. 冠心病人做完心脏支架手术后适合吃什么水果
  10. 一文理清数据仓库实施方法论