Syzkaller学习笔记---更新syz-extract/syz-sysgen(一)

news2024/9/23 23:24:06

Syzkaller学习笔记

  • Syzkaller 安装
    • 文件系统
    • 内核
    • Android common kernel
    • 参考文献
  • syzkaller 源码阅读笔记-1
    • 前言
    • syz-extract
      • main
      • archList
      • createArches
      • worker
      • processArch
      • processFile
      • extract
      • checkUnsupportedCalls
      • archList
      • 小结
    • syz-sysgen
      • main
      • processJob()
      • generateExecutorSyscalls()
      • writeExecutorSyscalls
      • 小结
    • 参考

Syzkaller 安装

先安装软件

sudo apt-get install debootstrap
sudo apt install qemu-kvm
sudo apt-get install subversion
sudo apt-get install git
sudo apt-get install make
sudo apt-get install qemu
sudo apt install libssl-dev libelf-dev
sudo apt-get install flex bison libc6-dev libc6-dev-i386 linux-libc-dev linux-libc-dev:i386 libgmp3-dev libmpfr-dev libmpc-dev
apt-get install g++
apt-get install build-essential
apt install gcc

安装go

add-apt-repository ppa:longsleep/golang-backports
apt-get update
sudo apt-get install golang-go
//go的版本为1.19

然后设置goproxy

go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct

然后go get源代码

go get -u -d github.com/google/syzkaller/prog

进入后进行编译

image-20230202163605050

发现报错,

dmesg | egrep -i -B100 'killed process'
执行命令 发现 OOM-Killer

image-20230202164905655

重新分配,16G,编译成功

当然也可以 建立swap分区

https://studygolang.com/articles/11781?fr=sidebar

image-20230202165719303

编译完成

文件系统

我们新建一个 image文件夹,下载create-image.sh 但是

https://raw.githubusercontent.com/google/syzkaller/master/tools/create-image.sh

网络问题,我们手动下载

# 安装debootstrap
sudo apt install debootstrap
# 下载脚本
wget https://raw.githubusercontent.com/google/syzkaller/master/tools/create-image.sh -O create-image.sh
# 添加可执行权限
chmod +x create-image.sh
# 使用清华源,不然慢死了
sed -i -e 's~sudo debootstrap .*~\0 https://mirrors.tuna.tsinghua.edu.cn/debian/~' create-image.sh
# 制作镜像,1024MB
./create-image.sh -s 1024

执行会有报错

image-20230202184647993

由于windows系统下换行符为 \r\n,linux下换行符为 \n,所以导致在windows下编写的文件会比linux下多回车符号 \r。
只需要去掉多余的 \r 回车符 即可。操作办法可以用sed命令进行全局替换
sed 's/\r//' -i gen_cert.sh

image-20230202185212833

内核

https://mirrors.edge.kernel.org/pub/linux/kernel

手动下载 或者wget

image-20230202185333825

# 先采用默认配置
make defconfig
# 启用kvm
make kvmconfig
# Syzkaller需要启用一些调试功能
echo '
CONFIG_KCOV=y
CONFIG_DEBUG_INFO=y
CONFIG_KASAN=y
CONFIG_KASAN_INLINE=y
CONFIG_CONFIGFS_FS=y
CONFIG_SECURITYFS=y' >> .config
# 再次对新引入的配置采用默认值
make olddefconfig

使用qemu进入测试,成功

root 密码空

image-20230202204536871

qemu-system-x86_64 -m 1G \
	-enable-kvm \
	-drive file=/home/test/go/src/github.com/google/syzkaller/image/stretch.img,format=raw \
	-kernel ./linux-4.4.146/arch/x86/boot/bzImage \
	-append root=/dev/sda

我们在syzkaller 中生成我们的cfg文件

{
    "target": "linux/amd64",
    "http": "0.0.0.0:8080",
    "workdir": "/home/test/go/src/github.com/google/syzkaller/bin/workdir",
    "kernel_obj": "/home/test/桌面/cheche/kernel/linux-4.4.146/",
    "image": "../image/stretch.img",
    "sshkey": "../image/stretch.id_rsa",
    "syzkaller": "/home/test/go/src/github.com/google/syzkaller",
    "enable_syscalls": ["chmod"],
    "procs": 1,
    "type": "qemu",
    "vm": {
        "count": 1,
        "kernel": "/home/test/桌面/cheche/kernel/linux-4.4.146/arch/x86/boot/bzImage",
        "cpu": 1,
        "mem": 1024
    }
}

image-20230202205559264

./syz-manager -config 4.14.cfg -vv 10

image-20230202220955391

config 字段的解释

https://github.com/google/syzkaller/blob/master/pkg/mgrconfig/config.go

Android common kernel

https://android.googlesource.com/kernel/common/

挂代理 git 所有后 才能进行查看

git log --all | grep 搜索 

image-20230206185802747

image-20230206185813253

git log查看的不全

我们搜索到之后

精确下载 某个版本

image-20230206190404803

proxychains git clone -b ASB-2018-08-05_4.4 https://android.googlesource.com/kernel/common

安装多次之后终于下载成功

image-20230206211105877

image-20230206211115821

参考文献

赛兹卡勒/setup_ubuntu-host_qemu-vm_x86-64-kernel.md at 大师 ·谷歌/Syzkaller ·GitHub

syzkaller/setup.md at master · google/syzkaller · GitHub

https://bbs.kanxue.com/thread-265405.htm#%E5%B0%9D%E8%AF%95%E4%BB%8E0%E5%88%B01%E5%BC%80%E5%A7%8B%E4%BD%BF%E7%94%A8syzkaller%E8%BF%9B%E8%A1%8Clinux%E5%86%85%E6%A0%B8%E6%BC%8F%E6%B4%9E%E6%8C%96%E6%8E%98

https://snappyjack.github.io/articles/2020-05/%E4%BD%BF%E7%94%A8Syzkaller%E8%BF%9B%E8%A1%8C%E5%86%85%E6%A0%B8fuzz

https://i-m.dev/posts/20200313-143737.html

https://blingblingxuanxuan.github.io/2019/10/26/syzkaller/

ARM syzkaller

http://wanjiabing.top/posts/zh/kerneldebug/syzkaller/

安卓模拟

https://www.owalle.com/2020/05/11/android-emulator/

http://pwn4.fun/2019/10/29/Syzkaller-Fuzz-Android-Kernel/

https://source.android.com/docs/core/tests/debug/kasan-kcov?hl=zh-cnzsy

syzkaller 源码阅读笔记-1

前言

syzkaller 是 google 开源的一款无监督覆盖率引导的 kernel fuzzer,支持包括 Linux、Windows 等操作系统的测试。

syzkaller 有很多个部件。其中:

  • syz-extract:用于解析 syzlang 中的常量
  • syz-sysgen:用于解析 syzlang,提取其中描述的 syscall 和参数类型,以及参数依赖关系
  • syz-manager:用于启动与管理 syzkaller
  • syz-fuzzer:实际在 VM 中运行的 fuzzer
  • syz-executor:实际在 VM 中运行的测试程序

image-20230209124913289

commit 14a312c837f1ebfece99a5cac64d37eba33654af

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

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

syz-extract

用途:解析并获取 syzlang 文件中的常量所对应的具体整型,并将结果存放至 xxx.txt.const 文件中

syz-extract main 函数位于 sys/syz-extract/extract.go 中。

开头导入了一些包,我们 暂且不看
import (
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"sort"
	"strings"

	"github.com/google/syzkaller/pkg/ast"
	"github.com/google/syzkaller/pkg/compiler"
	"github.com/google/syzkaller/pkg/osutil"
	"github.com/google/syzkaller/pkg/tool"
	"github.com/google/syzkaller/sys/targets"
)

main

在main函数中

func main() {
	flag.Parse()
	if *flagBuild && *flagBuildDir != "" {
		tool.Failf("-build and -builddir is an invalid combination")
	}

syz-extract 会尝试解析传入的参数,也就是flag

直接定义了我们的参数 和他的提示信息以及默认值

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)")
)
  • flagOS:是一个字符串类型的变量,默认值是当前系统的操作系统(runtime.GOOS)。它定义了命令行参数 “os”,表示目标操作系统。

  • flagBuild:是一个布尔类型的变量,默认值是 false。它定义了命令行参数 “build”,表示是否重新生成特定架构的内核头文件。

  • flagSourceDir:是一个字符串类型的变量,默认值是空字符串。它定义了命令行参数 “sourcedir”,表示内核源代码的存储路径。

  • flagIncludes:是一个字符串类型的变量,默认值是空字符串。它定义了命令行参数 “includedirs”,表示其他内核源代码包含目录,多个目录用逗号隔开。

  • flagBuildDir:是一个字符串类型的变量,默认值是空字符串。它定义了命令行参数 “builddir”,表示内核生成的文件存储路径。

  • flagArch:是一个字符串类型的变量,默认值是空字符串。它定义了命令行参数 “arch”,表示需要生成的架构,多个架构用逗号隔开,如果不指定则生成所有架构。

    这些值来自于go中的flag包

image-20230209173925099

​ 接下来,便是尝试获取 OS 所对应的 Extractor 结构体;如果 OS 不存在则肯定取不到,直接报错:

	OS := *flagOS
	extractor := 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) error
	prepareArch(arch *Arch) error
	processFile(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),
}

​ 回到 main 函数,syz-extract 要用已有的 OS 字符串、archArray 字符串数组,以及 syzlang 文件名数组来生成出对应的 arches 结构体数组

​ 在旧的版本中,会有单另的archFileList用来生成arch字符串,file等

​ 现在 合并在createArches中,archList函数之类的。

用已有的 OS 字符串、archArray 字符串数组(调用archList()获得),以及 syzlang 文件名数组来生成出对应的 arches 结构体数组

arches, nfiles, 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)"))
	}
  • OS 为操作系统字符串
  • archList结果为待生成的 arch 字符串数组
  • nfiles 为待分析的 syzlang 文件名 字符串数组

准备工作已经做的差不多了,接下来让 extractor 执行初始化操作:

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

这一步实际上会调用到 sys/syz-extract/linux.go 中的 prepare 函数:

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.KernelArch
			if 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
}

如果不指定重新生成 linux kernel header,那么只会做一些简单的检查。但如果指定重新生成了,则会尝试在 linux kernel src 上执行 make mrproper

回到 main 函数,接下来便是创建 go routine 通信管道和启动并行 worker:

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

	jobC := make(chan interface{}, len(arches)+nfiles)
	for _, arch := range arches {
		jobC <- arch
	}

	for p := 0; p < runtime.GOMAXPROCS(0); p++ {
		go worker(extractor, jobC)
	}

上面的代码创建了一个管道 jobC,该管道的容量为所有架构数量加上文件数量。然后,它循环所有的架构,并将每个架构作为接口类型的值放入该管道中。

接着,它循环该代码在一个系统上可以同时运行的最大的处理数,并在每次循环中启动一个新的工作程序,并将 extractorjobC 作为参数传递给该程序。这个工作程序的目的是从 jobC 管道中提取架构,并运行提取程序。

因此,通过创建多个工作程序,代码可以并行地处理所有架构。

总的来说,如果有多个架构,则启动多线程并发执行各自的 processArch() / processFile()

​ worker 启动后,main 函数就需要等待 worker 处理完成后才能保存处理结果至文件中,这就涉及到了线程协同。注意到代码中有 <-arch.done<-f.done 语句,这两个语句会一直阻塞等待管道,直到其传来信息。若 worker 函数中对管道执行 close 操作,则被关闭的管道将不再等待,继续向下执行。因此这里 syz-extract 就利用了管道来完成线程协同。

constFiles := make(map[string]*compiler.ConstFile)
	for _, arch := range arches {
		fmt.Printf("generating %v/%v...\n", OS, arch.target.Arch)
		<-arch.done
		if arch.err != nil {
			failed = true
			fmt.Printf("%v\n", arch.err)
			continue
		}
		for _, f := range arch.files {
			<-f.done
			if f.err != nil {
				failed = true
				fmt.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)
		}
	}

剩下的部分就是将生成结果保存在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)
	}

main函数的主要逻辑如下:

  1. 首先,调用flag.Parse()来解析命令行参数,主要是OS,arch,syzlang文件名。

  2. 检查传入的参数是否合法:如果flagBuildflagBuildDir同时出现,输出错误信息;如果没有提供操作系统的类型,也输出错误信息。

  3. 获取参数OS的值,并通过extractors字典来获取对应的提取器。如果没有对应的提取器,输出错误信息。

  4. 通过createArches函数生成需要处理的架构,并向jobC channel 中添加需要处理的任务。见 createArches()

  5. sys/syz-extract/linux.go: prepare() —— 初始化操作,如果设置了 build 参数,表示重新生成特定架构的内核头文件,先删除之前编译所生成的文件和配置文件;

  6. 启动GOMAXPROCS(0)个工作协程,它们从jobC channel 中读取任务并处理。

  7. 对每种arch架构,多线程并发执行 worker()(边进行常量提取,边将先前已有的提取结果存放进文件中,提高效率),真正执行变量解析工作;—— 见 1-4 processArch()

    • sys/syz-extract/extract.go: processArch():处理传入的 Extractor 和 Arch 结构体,生成 const 信息。
      • 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():编译生成可执行文件,并搜集常量;
      • sys/syz-extract/fetch.go: extract():主要函数。

    8.等待 worker() 多线程执行完成,结果保存到 const 文件。

总体流程

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

archList

获取架构 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 []string
	for 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
			},
		},
		I386: {
			VMArch:           AMD64,
			PtrSize:          4,
			PageSize:         4 << 10,
			Int64Alignment:   4,
			LittleEndian:     true,
			CFlags:           []string{"-m32"},
			Triple:           "x86_64-linux-gnu",
			KernelArch:       "i386",
			KernelHeaderArch: "x86",
		},
		ARM64: {
			PtrSize:          8,
			PageSize:         4 << 10,
			LittleEndian:     true,
			Triple:           "aarch64-linux-gnu",
			KernelArch:       "arm64",
			KernelHeaderArch: "arm64",
		},
		ARM: {
			VMArch:           ARM64,
			PtrSize:          4,
			PageSize:         4 << 10,
			LittleEndian:     true,
			CFlags:           []string{"-D__LINUX_ARM_ARCH__=6", "-march=armv6"},
			Triple:           "arm-linux-gnueabi",
			KernelArch:       "arm",
			KernelHeaderArch: "arm",
		},
		...
}

createArches

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

注:syzlang 可以用来写syscall模板

syzlang 是 syzkaller 中的一个组件,它提供了一种高级语言,用于描述系统调用和系统数据结构。这种语言称为 syzlang,并且它抽象了底层细节,方便描述复杂的系统调用和数据结构。它使用起来更方便,并且可以在不涉及技术细节的情况下描述系统调用。

func createArches(OS string, archArray, files []string) ([]*Arch, int, 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, 0, fmt.Errorf("%v", errBuf.String())
	}
	allFiles := compiler.FileList(top, OS, eh)
	if allFiles == nil {
		return nil, 0, fmt.Errorf("%v", errBuf.String())
	}
	nfiles := 0
	var arches []*Arch
	for _, archStr := range archArray { // [1] 遍历架构 name 数组
		buildDir := "" // [2] 确定 build 文件夹路径
		if *flagBuild {
			dir, err := ioutil.TempDir("", "syzkaller-kernel-build")
			if err != nil {
				return nil, 0, 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, 0, 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 header
			done:        make(chan bool), // 管道,用于 go routine 间通信。当 arch 分析完成后,将会向该管道通知
		}
		archFiles := files
		if 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{ //将文件的信息(通过 File 对象)附加到 "arch.files" 列表
				arch: arch,
				name: f,
				done: make(chan bool),// 管道,用于 go routine 间通信。当 file 分析完成后,将会向该管道通知
			})
		}
		arches = append(arches, arch)
		nfiles += len(arch.files)
	}
	return arches, nfiles, nil
}

它叫做 “createArches”,接受三个参数:

  1. “OS” - 字符串类型,代表操作系统的名称。
  2. “archArray” - 字符串数组,代表你想要构建的架构。
  3. “files” - 字符串数组,代表要打包的文件。

它返回两个结果:

  1. []*Arch - 一个指针数组,代表创建的架构。
  2. int - 一个整数,代表打包后的文件数量。
  3. error - 错误信息,如果出现错误,则返回错误信息。

例如,你可以调用该函数如下:

os := "Linux"
archArray := []string{"x86", "x64"}
files := []string{"file1.txt", "file2.txt"}
arches, count, err := createArches(os, archArray, files)
if err != nil {
    fmt.Println(err)
} else {
    fmt.Println("Arches:", arches)
    fmt.Println("Count:", count)
}

worker

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

参数: 传给 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 = err
			close(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 管道中
				} //"jobC <- f" 表示将一个 "f" 变量写入 "jobC" 通道。
			}
		case *File:
			j.consts, j.undeclared, j.err = processFile(extractor, j.arch, j)
			close(j.done)
		}
	}
}

​ 该函数在一个 for 循环中不断读取 “jobC” 通道中的任务,并对其进行处理。每个任务是一个接口类型,该程序通过一个 switch 语句判断每个任务的具体类型。

​ 如果任务的类型为 *Arch,则使用 “processArch” 函数处理该任务,并关闭 “j.done” 通道,如果 “j.err” 等于 nil,则对每个 “j.files” 中的文件再次进行处理并写入 “jobC” 通道。

​ 如果任务的类型为 *File,则使用 “processFile” 函数处理该任务,并将处理结果写入 “j.consts”,“j.undeclared” 和 “j.err” 字段,然后关闭 “j.done” 通道。

这个代码是一个并行处理任务的示例,通过不断读取通道中的任务并处理,实现了并行的效果。

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

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

processArch

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

func processArch(extractor Extractor, arch *Arch) (map[string]*compiler.ConstInfo, error) {
	errBuf := new(bytes.Buffer)
	// 定义 error handler 函数
	eh := func(pos ast.Pos, msg string) {
		fmt.Fprintf(errBuf, "%v: %v\n", pos, msg)
	}
	// 解析 sys/linux/*.txt 的 syzlang 文件,形成一个 AST 数组
	// 因此 top 变量就是 ast 森林的根节点
	top := ast.ParseGlob(filepath.Join("sys", arch.target.OS, "*.txt"), eh)
	if top == nil {
		return nil, fmt.Errorf("%v", errBuf.String())
	}
	// 调用 compiler.ExtractConsts 获取每个 syzlang 文件中所对应的 const 信息
	infos := compiler.ExtractConsts(top, arch.target, eh)
	if infos == nil {
		return nil, fmt.Errorf("%v", errBuf.String())
	}
	// 让 Extractor 为 arch 做些准备
	if err := extractor.prepareArch(arch); err != nil {
		return nil, err
	}
	return infos, nil //将获取到的consts infos 返回给调用者
}
  • 调用 pkg/ast/parser.go: ParseGlob() -> pkg/ast/parser.go: Parse() 将编写的txt文件解析成AST。

    • Parse() -> parseTopRecover() 解析出节点加入到top中,并且会在struct前后加上空行,移除重复的空行。
    • parseTopRecover() -> parseTop() 根据标识符的类型调用不同的函数处理。
  • 调用了库函数 pkg\compiler\const.gocompiler.ExtractConsts() ,主要调用pkg\compiler\compiler.go 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)。

    其中,compiler.ExtractConsts 只是一个简单的 wrapper 函数,获取编译 syzlang 结果中的 fileConsts 字段:

    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
    }
    

image-20230211211258346

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

extractor.prepareArch 函数在 linux.go 中,做的操作主要是定义了几个头文件:

"stdarg.h": `
#pragma once
#define va_list __builtin_va_list
#define va_start __builtin_va_start
#define va_end __builtin_va_end
#define va_arg __builtin_va_arg
#define va_copy __builtin_va_copy
#define __va_copy __builtin_va_copy
`,

"asm/a.out.h":    "",
"asm/prctl.h":    "",
"asm/mce.h":      "",
"uapi/asm/msr.h": "",

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

回到 processArch 函数,该函数最后会把先前获取到的 consts info 返回给调用者:

processFile

编译并搜集常量

功能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 = ???

type Extractor interface {
	prepare(sourcedir string, build bool, arches []*Arch) error
	prepareArch(arch *Arch) error
	processFile(arch *Arch, info *compiler.ConstInfo) (map[string]uint64, map[string]bool, error)
}

func processFile(extractor Extractor, arch *Arch, file *File) (map[string]uint64, map[string]bool, error) {
	inname := filepath.Join("sys", arch.target.OS, file.name)
	if file.info == nil {
		return nil, nil, fmt.Errorf("const info for input file %v is missing", inname)
	}
	if len(file.info.Consts) == 0 {
		return nil, nil, nil
	}
	return extractor.processFile(arch, file.info)
}
//sys/syz-extract/linux.go: processFile()
func (*linux) processFile(arch *Arch, info *compiler.ConstInfo) (map[string]uint64, map[string]bool, error) {
	headerArch := arch.target.KernelHeaderArch // [1] 生成编译代码模板所用到的 gcc 编译参数:args
	sourceDir := arch.sourceDir
	buildDir := arch.buildDir
	args := []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.CCompiler
	res, 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] 返回结果
}

核心代码extract 是这个

	params := &extractParams{ // [2] 准备 extract 参数: params, 准备待使用的CC编译器
		AddSource:      "#include <asm/unistd.h>",
		ExtractFromELF: true,
		TargetEndian:   arch.target.HostEndian,
	}
	cc := arch.target.CCompiler
	res, undeclared, err := extract(info, cc, args, params) // [3] 执行核心函数 extract,生成 res 映射和 undeclared 集合
	if err != nil {
		return nil, nil, err
	}

image-20230211212645060

image-20230211212701105

extract

编译并搜集常量

位置sys/syz-extract/fetch.go

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

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

func extract(info *compiler.ConstInfo, cc string, args []string, params *extractParams) (
	map[string]uint64, map[string]bool, error) {
	data := &CompileData{ // [1] 初始化: 声明一系列的 map
		extractParams: params,
		Defines:       info.Defines,
		Includes:      info.Includes,
		Values:        info.Consts,
	}
	// 编译生成的程序路径
	bin := ""
	// 这个字段貌似没有用途,先行忽略
	missingIncludes := make(map[string]bool)
	// 未定义的 const,通常是自己定义的常量
	undeclared := make(map[string]bool)
	// 声明并初始化 valMap 中各个元素为 true
	valMap := make(map[string]bool)
	for _, val := range info.Consts {
		valMap[val] = true
	}
	for {
		// [2] 尝试将 consts 常量字符串与模板C代码结合,并编译结合后的代码,形成一个可执行文件
		bin1, out, err := compile(cc, args, data) // [2-1] 编译操作, 返回结果分别为编译出的可执行文件路径 / 编译器标准输出信息 / 编译器标准错误信息
		if err == nil {
			bin = bin1
			break
		}
		// 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 := false
		for _, 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] = true
					tryAgain = 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 = nil
		for _, v := range info.Includes {
			if missingIncludes[v] {
				continue
			}
			data.Includes = append(data.Includes, v)
		}
	}
	defer os.Remove(bin) // [3] 将新编译出的二进制文件删除

	var flagVals []uint64
	var err error
	if 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
}

因为上面提到了compile函数,我们进行查看

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) {
    // 创建填充好后的 C 代码缓冲区
    src := new(bytes.Buffer)
    // 使用传入的 data 对代码模板 srcTemplate 进行填充
    if err := srcTemplate.Execute(src, data); err != nil {
        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 :指定代码语言为 C 语言
        // - :指定代码从标准输入而不是从文件中读取
        "-x", "c", "-",
        // 指定文件输出的路径
        "-o", binFile,
        "-w",
    }...)
    if data.ExtractFromELF {
        // gcc -c 参数:只编译但不链接
        // 由于我们测试时使用的是 Linux,因此会进入该分支
        args = append(args, "-c")
    }
    // 执行程序
    cmd := osutil.Command(cc, args...)
    // 将填充后的代码模板喂给 gcc 编译
    cmd.Stdin = src
    // 将 stdin 和 stdout 的输入糅合,使得他俩的输出完全一致
    // 通俗的说就是让 stdin 和 stdout 都指向同一个管道
    if out, err := cmd.CombinedOutput(); err != nil {
        os.Remove(binFile)
        return "", out, err
    }
    return binFile, nil, nil
}

执行至 compile的图

image-20230211214831671

代码模板 如下

var srcTemplate = template.Must(template.New("").Parse(`
{{if not .ExtractFromELF}}
#define __asm__(...)
{{end}}

{{if .DefineGlibcUse}}
#ifndef __GLIBC_USE
#    define __GLIBC_USE(X) 0
#endif
{{end}}

{{range $incl := $.Includes}}
#include <{{$incl}}>
{{end}}

{{range $name, $val := $.Defines}}
#ifndef {{$name}}
#    define {{$name}} {{$val}}
#endif
{{end}}

{{.AddSource}}

{{if .DeclarePrintf}}
int printf(const char *format, ...);
{{end}}

{{if .ExtractFromELF}}
__attribute__((section("syz_extract_data")))
unsigned long long vals[] = {
    {{range $val := $.Values}}(unsigned long long){{$val}},
    {{end}}
};
{{else}}
int main() {
    int i;
    unsigned long long vals[] = {
        {{range $val := $.Values}}(unsigned long long){{$val}},
        {{end}}
    };
    for (i = 0; i < sizeof(vals)/sizeof(vals[0]); i++) {
        if (i != 0)
            printf(" ");
        printf("%llu", vals[i]);
    }
    return 0;
}
{{end}}
`))

可以很容易的看出来,该模板会将先前从 syzlang 收集到的 include、define 和 consts 字符串全部融合:

  • 如果设置了 ExtractFromELF 标志位,则 consts 值将全部放置在一个名为 syz_extract_data 的 section 上
  • 如果没有设置该标志位,则编译出来的程序在执行时将会依次打印 consts 值,以 %llu 的输出格式&使用空格来区分每个变量,输出至 stdout中。这样,sys-extract 就可以通过分析所编译程序的输出,来确定每个 consts 字符串所对应的数值是多少。

checkUnsupportedCalls

func checkUnsupportedCalls(arches []*Arch) bool {
	supported := make(map[string]bool)
	unsupported := make(map[string]string)
	for _, arch := range arches {
		for _, f := range arch.files {
			for name := range f.consts {
				supported[name] = true
			}
			for name := range f.undeclared {
				unsupported[name] = f.name
			}
		}
	}
	failed := false
	for name, file := range unsupported {
		if supported[name] {
			continue
		}
		failed = true
		fmt.Printf("%v: %v is unsupported on all arches (typo?)\n",
			file, name)
	}
	return failed
}
  1. 首先,使用 make 函数创建两个 map,一个是 supported,一个是 unsupported。supported 用来存储已经支持的名称,unsupported 用来存储未支持的名称。
  2. 然后,对于 arches 中的每个架构,遍历该架构的所有文件,并对这些文件中的常量和未声明的变量进行处理。如果是常量,则将其名称添加到 supported 中;如果是未声明的变量,则将其名称和对应的文件名添加到 unsupported 中。
  3. 最后,对于 unsupported 中的每个未支持的变量,如果该变量的名称在 supported 中,则说明该变量是支持的;否则,打印出该变量不支持的错误信息。

最终,返回该函数是否有失败(failed)。如果 failed 为 true,则说明存在不支持的调用;否则,说明所有的调用都是支持的。

archList

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

archList 用来返回archArray 简单的拆分

小结

syz-extract 会调用自定义 compiler 解析 syzlang 为 ast 森林,并依次提取每个 ast 树上的 consts 节点,然后将这些 consts 节点上的字符串放置进模板中,编译模板生成一个 ELF 或其他可执行文件。

接下来 syz-extract 会分析 ELF 文件上的数据,或者尝试执行可执行文件来解析其输出,以获得各个 consts 字符串所对应的具体整型值。

最后 syz-extract 将获取到的 consts 字符串与具体整型的映射关系,一个个序列化并填入 .const 文件中,这样便生成了对应于每个 syzlang 文件的 .const 文件。

在 syz-extract 执行的整个过程中,syz-extract 另起一个 go routine 来执行 worker,是为了能达到边进行常量提取,边将先前已有的提取结果存放进文件中,这样做是为了提高效率,加快常量提取的速度。

syz-sysgen

位置sys/syz-sysgen/sysgen.go

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

main

func main() {
	defer tool.Init()()

	var OSList []string
	for OS := range targets.List {
		OSList = append(OSList, OS)
	}
	sort.Strings(OSList)

	data := &ExecutorData{}

首先,使用 defer 关键字和 tool.Init() 函数在 main 函数结束之前初始化某些工具。

defer 关键字在 Go 语言中用于延迟函数的执行。当遇到 defer 语句时,Go 程序会将该语句所在的函数的执行推迟到函数返回时再执行。

举个例子,如果你有一个文件需要打开,并在程序执行完毕后关闭,你可以使用 defer 来做到这一点:

f, err := os.Open("file.txt")
if err != nil {
 log.Fatal(err)
}
defer f.Close()
// 程序将在这里执行其他操作,而不是在这里关闭文件

这样做的优势在于,即使程序需要从多个不同的地方返回,您仍然可以确保文件将在最终关闭。

func Init() func() {
	flagCPUProfile := flag.String("cpuprofile", "", "write CPU profile to this file")
	flagMEMProfile := flag.String("memprofile", "", "write memory profile to this file")
	if err := ParseFlags(flag.CommandLine, os.Args[1:]); err != nil {
		Fail(err)
	}
	return installProfiling(*flagCPUProfile, *flagMEMProfile)
}

涉及到cpuprofile 和memprofile 暂时不看

根据ki 爷的图 执行到这里

image-20230212154821430

紧接着便是一个 for 循环,遍历 OSList 中的每个 OS 字符串,并解析其中的 syzlang 代码。将这个 for 循环分为了上中下三个部分:

第一部分

	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 []string
		for arch := range targets.List[OS] {
			archs = append(archs, arch)
		}
		sort.Strings(archs)
...

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

偷KI爷的图

image-20230212160403886

之后还是收集当前 OS 所对应的全部 arch 字符串集合,并做一次排序操作。

第二部分

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 并行执行这些 job
			job := job
			go func() {
				defer wg.Done()
				processJob(job, descriptions, constFile) // processJob() 重要函数
			}()
		}
		wg.Wait()
...
}

​ 首先是为每个 arch 都创建了一个 Job 结构体,将其添加进数组 jobs中,并为数组执行排序操作,其中排序规则是自定义的。

​ 接下来创建了一个 sync.WaitGroup 结构体,这个结构体用于等待指定数量的 go routine 集合执行完成。其内部原理有点类似于信号量,执行 wg.Add 函数以增加其内部计数器值,执行 wg.Done 函数以减小其内部计数器值,执行 wg.Wait 则判断内部计数器值状态,进而选择是否挂起等待。

​ 其中最重要的是,syz-sysgen 依次遍历 jobs 数组中的每个 job,并创建 go routine 并行执行这些 job。函数 processJob 用于编译先前 parse 的 syzlang AST、分析其中的类型信息与依赖关系,并将其序列化为 golang 代码至 sys/<OS>/gen/<arch>.go 中,同时还将 syscall 属性相关的信息保存在 job.ArchData 中,供后续生成 sys-executor 关键头文件代码所用。

第三部分

for _, OS := range OSList {
    ...
    
    var syscallArchs []ArchData
    unsupported := 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{
        GOOS:  OS,
        Archs: syscallArchs,
    })

    for what, count := range unsupported {
        if count == len(jobs) {
            tool.Failf("%v is unsupported on all arches (typo?)", what)
        }
    }
}

​ 第三部分没什么需要特别关注的,这部分主要是做了一些检查,并将先前 worker 里生成的 ArchData 提取进变量 data 中。

for 循环结束后吗,main 函数最后这部分的代码继续为变量 data 设置一些字段:

	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)
}

​ 这部分代码只是分别将 prog.SyscallAttrsprog.CallProps 这两个结构体对应的字段名存了起来。俩结构体声明如下:

// SyscallAttrs represents call attributes in syzlang.
//
// This structure is the source of truth for the all other parts of the system.
// pkg/compiler uses this structure to parse descriptions.
// syz-sysgen uses this structure to generate code for executor.
//
// Only `bool`s and `uint64`s are currently supported.
//
// See docs/syscall_descriptions_syntax.md for description of individual attributes.
type SyscallAttrs struct {
	Disabled      bool
	Timeout       uint64
	ProgTimeout   uint64
	IgnoreReturn  bool
	BreaksReturns bool
	NoGenerate    bool
	NoMinimize    bool
}

prog\prog.go
// These properties are parsed and serialized according to the tag and the type
// of the corresponding fields.
// IMPORTANT: keep the exact values of "key" tag for existing props unchanged,
// otherwise the backwards compatibility would be broken.
type CallProps struct {
	FailNth int  `key:"fail_nth"`
	Async   bool `key:"async"`
	Rerun   int  `key:"rerun"`
}

image-20230212165940203

通过对上面源码的分析,我发现 syz-sysgen 将整个 prog.SyscallAttrs 结构体的字段名和每个 syscall 所对应的数据,全都转换成了普通字符串型和整型。看上去这像是要用这些数据来填充 C 语言模板?我们接下来再来看看 writeExecutorSyscalls 函数,看看这里面具体是做了什么。

writeExecutorSyscalls 函数源码分析位于下文,这里不再赘述。

processJob()

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

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

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

首先,该函数会生成一个 error handler,用于输出错误信息;之后从 ConstFile 结构体中,取出对应 arch 的 consts 字符串->整型映射表:

eh := func(pos ast.Pos, msg string) { // [1] 生成一个 error handler, 用于输出错误信息;
		job.Errors = append(job.Errors, fmt.Sprintf("%v: %v\n", pos, msg))
	}

image-20230212174737319

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>.go
	sysFile := 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 = nil
	job.OK = true
}

syz-sysgen 需要分析 AST 信息,对 syzlang 进行编译:

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
	}

返回的 Prog 结构体声明如下:

// Prog is description compilation result.
type Prog struct {
    Resources []*prog.ResourceDesc
    Syscalls  []*prog.Syscall
    Types     []prog.Type
    // Set of unsupported syscalls/flags.
    Unsupported map[string]bool
    // Returned if consts was nil.
    fileConsts map[string]*ConstInfo
}

​ [4]编译操作和先前 syz-extract 类似,不同的是这次提供了 consts 信息,因此会执行完整的编译过程,分析 syzlang 代码中描述的全部 syscall 参数类型信息。返回的 Prog 结构体中:

  • 字段 fileConsts 为空
  • 涉及到的类型信息保存在了 Resource 和 Types 字段
  • syscall 的描述则存放在 Syscalls 字段中。

Compile() 除了调用 createCompiler() 函数和 typecheck() 函数,接下来首先调用的是assignSyscallNumbers() / patchConsts() / check() 函数。

  • assignSyscallNumbers() 函数分配系统调用号,检测不受支持的系统调用并丢弃。
  • patchConsts() 函数将AST中的常量patch成对应的值。
  • check() 函数对AST进行语义检查。
  • genSyscalls() 主要是调用了 genSyscall() 函数,然后按照系统调用名排序。
  • genSyscall() 函数中调用 genType() 函数生成返回值,调用 genFieldArray() 函数生成每个参数。
  • 返回的 Prog 对象中调用 genResources() 函数生成资源,generateTypes() 函数生成结构体的描述。

我们来看看生成出的 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_amd64

package gen

import . "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"

generateExecutorSyscalls()

功能:为生成 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便能使用这两种技术加速fuzz
		data.ForkServer = 1
	}
	if target.ExecutorUsesShmem {
		data.Shmem = 1
	}
	defines := make(map[string]string)
	for _, c := range syscalls { // [2] 遍历各个 Syscall 类型的结构体
		var attrVals []uint64
		attrs := reflect.ValueOf(c.Attrs) // 将变量 c 中结构体 SyscallAttrs 里的各个字段取出,并将其依次存放至整型数组 attrVals (bool值和整型值)
		last := -1
		for 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
}

​ reflect.ValueOf(c.Attrs) 在运行中获取c.Attrs 的值的意思嘛 反射

说明

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

  • Syscall 结构体 -> SyscallAttrs 结构体

    type Syscall struct {
    	ID          int
    	NR          uint64 // kernel syscall number
    	Name        string
    	CallName    string
    	MissingArgs int // number of trailing args that should be zero-filled
    	Args        []Field
    	Ret         Type
    	Attrs       SyscallAttrs
      
    	inputResources  []*ResourceDesc
    	outputResources []*ResourceDesc
    }
    type SyscallAttrs struct {
    	Disabled      bool
    	Timeout       uint64
    	ProgTimeout   uint64
    	IgnoreReturn  bool
    	BreaksReturns bool
    }
    
  • data.CallsSyscallData 结构体示例与说明:

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

image-20230212182125986

writeExecutorSyscalls

功能:生成 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())
}

代码中提到 defsTempl 和 syscallsTempl模板如下

defsTempl 模板

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

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

var defsTempl = template.Must(template.New("").Parse(`// AUTOGENERATED FILE

struct 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 FILE

struct 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

syscallsTempl 模板

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

模板如下

// nolint: lll
var syscallsTempl = template.Must(template.New("").Parse(`// AUTOGENERATED FILE
// clang-format offz
{{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
...

syscallData 结构体

type SyscallData struct {
    Name     string
    CallName string
    NR       int32
    NeedCall bool
    Attrs    []uint64
}

小结

当执行完 syz-extractor 为每个 syslang 文件生成一个常量映射表 .const 文件后,syz-sysgen 便会利用常量映射表,来彻底的解析 syzlang 源码,获取到其中声明的类型信息与 syscall 参数依赖关系。

当这些信息全都收集完毕后,syz-sysgen 便会将这些数据全部序列化为 go 文件,以供后续 syz-fuzzer 所使用。除此之外,syz-sysgen 还会创建 executor/defs.h 和 executor/syscalls.h,将部分信息导出至 C 头文件,以供后续 syz-executor 编译使用。

简单地说,syz-sysgen 解析 syzlang 文件,并为 syz-fuzzer 和 syz-executor 的编译运行做准备。

参考

https://kiprey.github.io/2022/03/syzkaller-1/

https://bsauce.github.io/2022/05/13/syzkaller1/
https://47.99.84.243/fuzz/Syzkaller%20executor%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/

https://github.com/google/syzkaller

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/341005.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux ALSA 之十一:ALSA ASOC Path 完整路径追踪

ALSA ASOC Path 完整路径追踪一、ASoc Path 简介二、ASoc Path 完整路径2.1 tinymix 设置2.2 完整路径 route一、ASoc Path 简介 如前面小节所描述&#xff0c;ASoc 中 Machine Driver 是 platform driver 和 codec driver 的粘合剂&#xff0c;audio path 离不开 FE/BE/DAI l…

绕过Nginx Host限制

目录绕过Nginx Host限制SNI第三种方法&#xff1a;总结绕过Nginx Host限制 SNI SNI&#xff08;Server Name Indication&#xff09;是 TLS 的扩展&#xff0c;这允许在握手过程开始时通过客户端告诉它正在连接的服务器的主机名称。 作用&#xff1a;用来解决一个服务器拥有…

机器视觉 多模态学习11篇经典论文代码以及解读

此处整理了深度学习&#xff0d;机器视觉&#xff0c;最新的发展方向&#xff0d;多模态学习&#xff0c;中的11篇经典论文&#xff0c;整理了相关解读博客和对应的Github代码&#xff0c;看完此系列论文和博客&#xff0c;相信你能快速切入这个方向。每篇论文、博客或代码都有…

【C++1】函数重载,类和对象,引用,string类,vector容器,类继承和多态,/socket,进程信号,public,ooci

文章目录1.函数重载&#xff1a;writetofile()&#xff0c;Ctrue和false&#xff0c;C0和非02.类和对象&#xff1a;vprintf2.1 构造函数&#xff1a;对成员变量初始化2.2 析构函数&#xff1a;一个类只有一个&#xff0c;不允许被重载3.引用&#xff1a;C中&取地址&#x…

Elasticsearch在Windows系统下的安装

Elasticsearch在Windows系统下的安装Elasticsearch在Windows系统下的安装1、安装 Java 环境2、安装 Elasticsearch&#xff08;1&#xff09;下载 Elasticsearch 的 zip 安装包&#xff08;2&#xff09;下载安装包后解压文件&#xff08;3&#xff09;启动 Elasticsearch 服务…

C++多态(下)

大家好&#xff01;上一篇文章&#xff0c;主要是说了多态的概念和使用。这篇文章就会说一下多态的底层原理&#xff0c;如果对多态的使用和概念不清的可以看一下上篇文章(多态概念)。 文章目录1. 多态的原理1.1 虚函数表1.2 多态的原理1.3 动态绑定与静态绑定2. 多继承关系的…

第四章 MergeTree原理分析

一、存储结构 1.1 表引擎语法结构 CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster] (name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],...INDEX index_name1 expr1 TYPE type1(...) GRANULARITY va…

【遇见青山】项目难点:解决超卖问题

【遇见青山】项目难点&#xff1a;解决超卖问题1.乐观锁方案2.悲观锁方案1.乐观锁方案 原始实现下单功能的方法&#xff1a; /*** 秒杀实现** param voucherId 秒杀券的ID* return Result*/ Override Transactional public Result seckillVoucher(Long voucherId) {// 查询优…

备战蓝桥杯【高精度加法和高精度减法】

&#x1f339;作者:云小逸 &#x1f4dd;个人主页:云小逸的主页 &#x1f4dd;Github:云小逸的Github &#x1f91f;motto:要敢于一个人默默的面对自己&#xff0c;强大自己才是核心。不要等到什么都没有了&#xff0c;才下定决心去做。种一颗树&#xff0c;最好的时间是十年前…

IDEA中使用自定义的maven

步骤 1.下载maven maven下载 2.配置maven 2.1设置环境变量 1.在“环境环境“–“系统环境“–“path”中加入&#xff08;设置到bin目录下&#xff09; 2.测试环境变量是否成功 C:\Users>mvn -v //在控制台输入mav -v,看是否输出以下结果 Apache Maven 3.9.0 (9b…

《MySQL系列-InnoDB引擎23》文件-InnoDB存储引擎文件-重做日志文件

InnoDB存储引擎文件 之前介绍的文件都是MySQL数据库本身的文件&#xff0c;和存储引擎无关。除了这些文件外&#xff0c;每个表存储引擎都有其自己独有的文件。本节将具体介绍与InnoDB存储引擎密切相关的文件&#xff0c;这些文件包括重做日志文件、表空间文件。 重做日志文件…

Docker的资源控制管理

目录 一、CPU控制 1、设置CPU使用率上限 2、设置CPU资源占用比&#xff08;设置多个容器时才有效&#xff09; 3、设置容器绑定指定的CPU 二、对内存使用进行限制 1、创建指定物理内存的容器 2、创建指定物理内存和swap的容器 3、 对磁盘IO配额控制&#xff08;blkio&a…

使用Docker容器部署java运行环境(java8 + mysql5.7 + redis5.0 + nginx1.14.1

环境&#xff1a;阿里云ECS服务器一.Docker环境安装1.1 安装工具sudo yum install -y yum-utils device-mapper-persistent-data lvm21.2 为yum源添加docker仓库位置yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo1.3 将软件…

【微信小游戏开发笔记】第二节:Cocos开发界面常用功能简介

Cocos开发界面常用功能简介 本章只介绍微信小游戏开发时常用的功能&#xff0c;其他功能不常用&#xff0c;写多了记不住&#xff08;其实是懒 -_-!&#xff09;&#xff1a; 层级管理器&#xff0c;用于操作各个节点。资源管理器&#xff0c;用于操作各种文件资源。场景编辑…

SpringMVC--简介和入门案例

SpringMVC简介 什么是MVC MVC是一种软件架构的思想&#xff0c;将软件按照模型、视图、控制器来划分 M:Model&#xff0c;模型层&#xff0c;指工程中的JavaBean&#xff0c;作用是处理数据 JavaBean分为两类: 一类称为实体类Bean:专门存储业务数据的&#xff0c;如 Studen…

西湖论剑初赛web wp

Node Magical Login 简单的js代码审计。 Flag分成了两部分。 第一部分&#xff1a; 这里就简单的判断了一下user是否等于admin&#xff0c;直接绕过。 第二部分&#xff1a; checkcode ! “aGr5AtSp55dRacer”&#xff0c;让其为真&#xff0c;利用数组绕过。 Flag为&#x…

家政服务小程序实战教程03-创建自定义应用

我们上一篇讲解了创建模型应用&#xff0c;模型应用是给管理员使用的。普通用户日常办理业务还是在小程序完成。 微搭中的小程序需要通过创建自定义应用来创建&#xff0c;进入控制台&#xff0c;点击应用&#xff0c;点击新建应用&#xff0c;选择新建自定义应用 输入应用的名…

微信小程序 java家校通Springboot中小学家校联系电子作业系统

小程序前端框架&#xff1a;uniapp 小程序运行软件&#xff1a;微信开发者 后端技术:javaSsm(SpringSpringMVCMyBatis)vue.js 后端开发环境:idea/eclipse 数据库:mysql 通过对各种资料的收集&#xff0c;了解到“校讯通”是联系社会的窗口&#xff0c;是实现家校联系工作和学校…

【参加CUDA线上训练营】零基础cuda—矩阵转置实现及其优化

【参加CUDA线上训练营】零基础cuda—矩阵转置实现及其优化1.不使用Shared Memory2.使用Shared Memory3.使用Shared Memory&#xff0c;并加入No Bank Conflicts4.效果对比参考文献本文参考Nvidia官方blog[An Efficient Matrix Transpose in CUDA C/C及其对应的github代码transp…

可视化图表的思路

数据表达 excel — 小量级一次性的数据处理 Tableau等BI — 批量的数据读取与分析 python — 复杂的数据清洗、爬虫和算法建模 图表展示原则&#xff1a;客观&#xff0c;高效&#xff0c;直观 表达格式&#xff1a;观点数据补充信息图表 图表选择思路 规模、趋势、占比、关…