Skip to content

Commit 9488053

Browse files
committed
pkg/aflow: initial repro workflow implementation
1 parent cbc1a5e commit 9488053

File tree

15 files changed

+602
-152
lines changed

15 files changed

+602
-152
lines changed

pkg/aflow/action/crash/compare.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2026 syzkaller project authors. All rights reserved.
2+
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3+
4+
package crash
5+
6+
import (
7+
"github.com/google/syzkaller/pkg/aflow"
8+
)
9+
10+
var CompareCrashSignature = aflow.NewFuncAction("compare-crash-signature", compareCrash)
11+
12+
type CompareCrashArgs struct {
13+
BugTitle string
14+
ProducedBugTitle string
15+
}
16+
17+
type CompareCrashResult struct {
18+
Matches bool
19+
CompareErrors string
20+
}
21+
22+
func compareCrash(ctx *aflow.Context, args CompareCrashArgs) (CompareCrashResult, error) {
23+
if args.BugTitle != args.ProducedBugTitle {
24+
return CompareCrashResult{Matches: false, CompareErrors: "Crash signature did not match target"}, nil
25+
}
26+
return CompareCrashResult{Matches: true}, nil
27+
}

pkg/aflow/action/crash/compiler.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright 2026 syzkaller project authors. All rights reserved.
2+
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3+
4+
package crash
5+
6+
import (
7+
"regexp"
8+
"strings"
9+
10+
"github.com/google/syzkaller/pkg/aflow"
11+
"github.com/google/syzkaller/prog"
12+
_ "github.com/google/syzkaller/sys"
13+
)
14+
15+
var SyzCompilerCheck = aflow.NewFuncAction("syz-compiler-check", compilerCheck)
16+
17+
type CompilerCheckArgs struct {
18+
CandidateSyzlang string
19+
}
20+
21+
type CompilerCheckResult struct {
22+
CompilerSuccess bool
23+
CompilerErrors string
24+
}
25+
26+
func compilerCheck(ctx *aflow.Context, args CompilerCheckArgs) (CompilerCheckResult, error) {
27+
pt, err := prog.GetTarget("linux", "amd64")
28+
if err != nil {
29+
return CompilerCheckResult{CompilerSuccess: false, CompilerErrors: err.Error()}, nil
30+
}
31+
_, err = pt.Deserialize([]byte(args.CandidateSyzlang), prog.Strict)
32+
if err != nil {
33+
return CompilerCheckResult{CompilerSuccess: false, CompilerErrors: err.Error()}, nil
34+
}
35+
return CompilerCheckResult{CompilerSuccess: true}, nil
36+
}
37+
38+
var ExtractSyzCode = aflow.NewFuncAction("extract-syz-code", extractSyzCode)
39+
40+
type ExtractSyzCodeArgs struct {
41+
RawSyzlang string
42+
}
43+
44+
type ExtractSyzCodeResult struct {
45+
CandidateSyzlang string
46+
}
47+
48+
func extractSyzCode(ctx *aflow.Context, args ExtractSyzCodeArgs) (ExtractSyzCodeResult, error) {
49+
code := args.RawSyzlang
50+
// If the code is inside a markdown block, extract it.
51+
if match := reSyzBlock.FindStringSubmatch(code); match != nil {
52+
code = match[1]
53+
} else if match := reCodeBlock.FindStringSubmatch(code); match != nil {
54+
code = match[1]
55+
}
56+
code = strings.TrimSpace(code)
57+
return ExtractSyzCodeResult{CandidateSyzlang: code}, nil
58+
}
59+
60+
var (
61+
reSyzBlock = regexp.MustCompile("(?s)```(?:ex-?)?syz(?:kaller|lang)?\\n(.*?)```")
62+
reCodeBlock = regexp.MustCompile("(?s)```\\n(.*?)```")
63+
)
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2026 syzkaller project authors. All rights reserved.
2+
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3+
4+
package crash
5+
6+
import (
7+
"testing"
8+
9+
"github.com/google/syzkaller/pkg/aflow"
10+
)
11+
12+
func TestExtractSyzCode(t *testing.T) {
13+
tests := []struct {
14+
name string
15+
input string
16+
wantCode string
17+
}{
18+
{
19+
name: "raw_code",
20+
input: "getpid()\n",
21+
wantCode: "getpid()",
22+
},
23+
{
24+
name: "markdown_block",
25+
input: "Here is the code:\n```\ngetpid()\n```\n",
26+
wantCode: "getpid()",
27+
},
28+
{
29+
name: "syzlang_block",
30+
input: "Try this:\n```syzlang\ngetpid()\n```\n",
31+
wantCode: "getpid()",
32+
},
33+
{
34+
name: "syzkaller_block",
35+
input: "Try this:\n```syzkaller\ngetpid()\n```\n",
36+
wantCode: "getpid()",
37+
},
38+
{
39+
name: "chatty_no_block",
40+
input: "I think you should use open syscall.",
41+
wantCode: "I think you should use open syscall.",
42+
},
43+
}
44+
45+
for _, test := range tests {
46+
t.Run(test.name, func(t *testing.T) {
47+
res, err := extractSyzCode(&aflow.Context{}, ExtractSyzCodeArgs{RawSyzlang: test.input})
48+
if err != nil {
49+
t.Fatalf("unexpected error: %v", err)
50+
}
51+
if res.CandidateSyzlang != test.wantCode {
52+
t.Errorf("got code %q, want %q", res.CandidateSyzlang, test.wantCode)
53+
}
54+
})
55+
}
56+
}

pkg/aflow/action/crash/reproduce.go

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func ReproduceCrash(args ReproduceArgs, workdir string) (*report.Report, string,
8686
return nil, "", err
8787
}
8888
// TODO: run multiple instances, handle TestError.Infra, and aggregate results.
89-
results, err := env.Test(1, nil, nil, []byte(args.ReproC))
89+
results, err := env.Test(1, []byte(args.ReproSyz), []byte(args.ReproOpts), []byte(args.ReproC))
9090
if err != nil {
9191
return nil, "", err
9292
}
@@ -160,3 +160,85 @@ func reproduce(ctx *aflow.Context, args ReproduceArgs) (reproduceResult, error)
160160
CrashReport: cached.Report,
161161
}, nil
162162
}
163+
164+
var ReproduceSyzlang = aflow.NewFuncAction("crash-reproducer", reproduceSyzlang)
165+
166+
type ReproduceSyzlangArgs struct {
167+
Syzkaller string
168+
Image string
169+
Type string
170+
VM json.RawMessage
171+
CandidateSyzlang string
172+
SyzkallerCommit string
173+
KernelSrc string
174+
KernelObj string
175+
KernelCommit string
176+
KernelConfig string
177+
}
178+
179+
type reproduceSyzlangResult struct {
180+
ProducedBugTitle string
181+
ProducedCrashReport string
182+
ReproduceErrors string
183+
}
184+
185+
func reproduceSyzlang(ctx *aflow.Context, args ReproduceSyzlangArgs) (reproduceSyzlangResult, error) {
186+
imageData, err := os.ReadFile(args.Image)
187+
if err != nil {
188+
return reproduceSyzlangResult{}, err
189+
}
190+
desc := fmt.Sprintf("kernel commit %v, kernel config hash %v, image hash %v,"+
191+
" vm %v, vm config hash %v, syz repro hash %v, version 4",
192+
args.KernelCommit, hash.String(args.KernelConfig), hash.String(imageData),
193+
args.Type, hash.String(args.VM), hash.String(args.CandidateSyzlang))
194+
195+
type Cached struct {
196+
BugTitle string
197+
Report string
198+
Error string
199+
}
200+
201+
cached, err := aflow.CacheObject(ctx, "repro", desc, func() (Cached, error) {
202+
var res Cached
203+
workdir, err := ctx.TempDir()
204+
if err != nil {
205+
return res, err
206+
}
207+
208+
reproArgs := ReproduceArgs{
209+
Syzkaller: args.Syzkaller,
210+
Image: args.Image,
211+
Type: args.Type,
212+
VM: args.VM,
213+
ReproOpts: "",
214+
ReproSyz: args.CandidateSyzlang,
215+
ReproC: "",
216+
SyzkallerCommit: args.SyzkallerCommit,
217+
KernelSrc: args.KernelSrc,
218+
KernelObj: args.KernelObj,
219+
KernelCommit: args.KernelCommit,
220+
KernelConfig: args.KernelConfig,
221+
}
222+
223+
rep, buildError, err := ReproduceCrash(reproArgs, workdir)
224+
if rep != nil {
225+
res.BugTitle = rep.Title
226+
res.Report = string(rep.Report)
227+
}
228+
res.Error = buildError
229+
return res, err
230+
})
231+
232+
if err != nil {
233+
return reproduceSyzlangResult{}, err
234+
}
235+
if cached.Error != "" {
236+
return reproduceSyzlangResult{ReproduceErrors: cached.Error}, nil
237+
} else if cached.Report == "" {
238+
return reproduceSyzlangResult{ReproduceErrors: "reproducer did not crash"}, nil
239+
}
240+
return reproduceSyzlangResult{
241+
ProducedBugTitle: cached.BugTitle,
242+
ProducedCrashReport: cached.Report,
243+
}, nil
244+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright 2025 syzkaller project authors. All rights reserved.
2+
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3+
4+
package kernel
5+
6+
import (
7+
"errors"
8+
"fmt"
9+
"os/exec"
10+
"path/filepath"
11+
"strings"
12+
"time"
13+
14+
"github.com/google/syzkaller/pkg/aflow"
15+
"github.com/google/syzkaller/pkg/aflow/ai"
16+
"github.com/google/syzkaller/pkg/osutil"
17+
"github.com/google/syzkaller/pkg/vcs"
18+
)
19+
20+
var BaseCommitPicker = aflow.NewFuncAction("base-commit-picker", pickBaseCommit)
21+
22+
type baseCommitArgs struct {
23+
// Can be used to override the selected base commit (for manual testing).
24+
FixedBaseCommit string
25+
FixedRepository string
26+
}
27+
28+
type baseCommitResult struct {
29+
KernelRepo string
30+
KernelBranch string
31+
KernelCommit string
32+
}
33+
34+
func pickBaseCommit(ctx *aflow.Context, args baseCommitArgs) (baseCommitResult, error) {
35+
// Currently we use the latest RC of the mainline tree as the base.
36+
// This is a reasonable choice overall in lots of cases, and it enables good caching
37+
// of all artifacts (we need to rebuild them only approx every week).
38+
// Potentially we can use subsystem trees for few important, well-maintained subsystems
39+
// (mm, net, etc). However, it will work poorly for all subsystems. First, there is no
40+
// machine-usable mapping of subsystems to repo/branch; second, lots of them are poorly
41+
// maintained (can be much older than latest RC); third, it will make artifact caching
42+
// much worse.
43+
// In the future we ought to support automated rebasing of patches to requested trees/commits.
44+
// We need it anyway, but it will also alleviate imperfect base commit picking.
45+
const (
46+
baseRepo = "git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git"
47+
baseBranch = "master"
48+
)
49+
50+
res := baseCommitResult{
51+
KernelRepo: baseRepo,
52+
KernelBranch: baseBranch,
53+
KernelCommit: args.FixedBaseCommit,
54+
}
55+
if args.FixedRepository != "" {
56+
res.KernelRepo = args.FixedRepository
57+
}
58+
if args.FixedBaseCommit != "" {
59+
return res, nil
60+
}
61+
62+
err := UseLinuxRepo(ctx, func(_ string, repo vcs.Repo) error {
63+
head, err := repo.Poll(baseRepo, baseBranch)
64+
if err != nil {
65+
return err
66+
}
67+
tag, err := repo.ReleaseTag(head.Hash)
68+
if err != nil {
69+
return err
70+
}
71+
com, err := repo.SwitchCommit(tag)
72+
if err != nil {
73+
return err
74+
}
75+
res.KernelCommit = com.Hash
76+
return err
77+
})
78+
return res, err
79+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2025 syzkaller project authors. All rights reserved.
2+
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3+
4+
package kernel
5+
6+
import (
7+
"errors"
8+
"fmt"
9+
"os/exec"
10+
"path/filepath"
11+
"strings"
12+
"time"
13+
14+
"github.com/google/syzkaller/pkg/aflow"
15+
"github.com/google/syzkaller/pkg/aflow/ai"
16+
"github.com/google/syzkaller/pkg/osutil"
17+
"github.com/google/syzkaller/pkg/vcs"
18+
)
19+
20+
var GetMaintainers = aflow.NewFuncAction("get-maintainers", maintainers)
21+
22+
type maintainersArgs struct {
23+
KernelSrc string
24+
PatchDiff string
25+
}
26+
27+
type maintainersResult struct {
28+
Recipients []ai.Recipient
29+
}
30+
31+
func maintainers(ctx *aflow.Context, args maintainersArgs) (maintainersResult, error) {
32+
res := maintainersResult{}
33+
// See #1441 re --git-min-percent.
34+
script := filepath.Join(args.KernelSrc, "scripts/get_maintainer.pl")
35+
cmd := exec.Command(script, "--git-min-percent=15")
36+
cmd.Dir = args.KernelSrc
37+
cmd.Stdin = strings.NewReader(args.PatchDiff)
38+
output, err := osutil.Run(time.Minute, cmd)
39+
if err != nil {
40+
return res, err
41+
}
42+
for _, recipient := range vcs.ParseMaintainersLinux(output) {
43+
res.Recipients = append(res.Recipients, ai.Recipient{
44+
Name: recipient.Address.Name,
45+
Email: recipient.Address.Address,
46+
To: recipient.Type == vcs.To,
47+
})
48+
}
49+
return res, nil
50+
}

0 commit comments

Comments
 (0)