二 Syzkaller学习笔记---更新syz-manager(17)


repro函数
func (ctx *context) repro(entries []*prog.LogEntry, crashStart int) (*Result, error) {// Cut programs that were executed after crash.for i, ent := range entries {if ent.Start > crashStart {entries = entries[:i]break}}reproStart := time.Now()defer func() {ctx.reproLogf(3, "reproducing took %s", time.Since(reproStart))}()res, err := ctx.extractProg(entries)// [2] 提取出触发 crash 的程序!!!if err != nil {return nil, err}if res == nil {return nil, nil}defer func() {if res != nil {res.Opts.Repro = false}}()res, err = ctx.minimizeProg(res) // [3] 若成功复现, 则调用prog.Minimize(), 简化所有的调用和参数 !!!if err != nil {return nil, err}// Try extracting C repro without simplifying options first.res, err = ctx.extractC(res)// [4] 生成C代码,编译成二进制文件,执行并检查是否crash,若crash则赋值 res.CRepro = crashed !!!if err != nil {return nil, err}// Simplify options and try extracting C repro.if !res.CRepro {res, err = ctx.simplifyProg(res)//[5] !!! 进一步简化 。在 repro.go 中定义了 progSimplifies 数组作为简化规则,依次使用每一条规则后,如果crash还能被触发, 再调用 extractC(res) 尝试提取 C reproif err != nil {return nil, err}}// Simplify C related options.if res.CRepro {res, err = ctx.simplifyC(res)// [6] 对提取出的C程序进行简化 。跟上面的ctx.simplifyProg(res)差不多,就是规则使用了cSimplifies数组 。[5][6] 简化的是复现crash时设置的一些选项,比如线程、并发、沙盒等等 。if err != nil {return nil, err}}return res, nil}
这个函数是过程中重现崩溃的核心部分,它会尝试从记录的日志中提取触发崩溃的程序,并进行简化以便更容易重现崩溃 。具体来说:
首先,该函数会根据记录的日志,找到触发崩溃的程序所在的 ,并将后续的所有执行记录截断 。然后,该函数调用方法来提取出触发崩溃的程序,并将结果保存到 res 变量中 。如果成功提取出程序,则继续调用方法对其进行简化,以便更容易重现崩溃 。接下来,该函数会尝试提取 C 语言的重现程序,不论之前是否已经有过简化操作 。如果之前提取出的程序不是 C 语言程序,则调用方法对选项进行简化 。如果程序是 C 语言程序,则调用方法对 C 相关的选项进行简化 。最后,函数返回重现结果 res,以及可能出现的错误 。()
位置:pkg/repro/repro.go
功能:提取出触发 crash 的程序 。
说明:按照时间从短到长, 从后向前, 从单个到多个的顺序复现crash 。
func (ctx *context) extractProg(entries []*prog.LogEntry) (*Result, error) {ctx.reproLogf(2, "extracting reproducer from %v programs", len(entries))start := time.Now()defer func() {ctx.stats.ExtractProgTime = time.Since(start)}()// Extract last program on every proc.procs := make(map[int]int)for i, ent := range entries {procs[ent.Proc] = i}var indices []intfor _, idx := range procs {// [1] 在所有程序 (用entries数组存放) 中提取出每个proc所执行的最后一个程序indices = append(indices, idx)}sort.Ints(indices)var lastEntries []*prog.LogEntryfor i := len(indices) - 1; i >= 0; i-- {// [2] 将程序按倒序存放到 lastEntries (通常最后一个程序就是触发crash的程序)lastEntries = append(lastEntries, entries[indices[i]])}for _, timeout := range ctx.testTimeouts {// [3] 不同类型的漏洞漏洞需要不同的复现时间, 复杂crash耗时长(eg, race)// Execute each program separately to detect simple crashes caused by a single program.// Programs are executed in reverse order, usually the last program is the guilty one.res, err := ctx.extractProgSingle(lastEntries, timeout)// [4] 倒序执行单个程序, 若触发crash则返回if err != nil {return nil, err}if res != nil {ctx.reproLogf(3, "found reproducer with %d syscalls", len(res.Prog.Calls))return res, nil}// Don't try bisecting if there's only one entry.if len(entries) == 1 {continue}// [5] 若单个程序无法触发crash, 则采用二分查找的方法找出哪几个程序一起触发crash 。先调用bisectProgs()进行分组,看哪一组可以触发crash 。!!!// Execute all programs and bisect the log to find multiple guilty programs.res, err = ctx.extractProgBisect(entries, timeout)if err != nil {return nil, err}if res != nil {ctx.reproLogf(3, "found reproducer with %d syscalls", len(res.Prog.Calls))return res, nil}}ctx.reproLogf(0, "failed to extract reproducer")return nil, nil}