Skip to content

Commit 0ccd0bb

Browse files
committed
tools/syz-aflow: add command line tool for agentic workflows
1 parent f98c5b3 commit 0ccd0bb

File tree

1 file changed

+137
-0
lines changed

1 file changed

+137
-0
lines changed

tools/syz-aflow/aflow.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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 main
5+
6+
import (
7+
"context"
8+
"encoding/json"
9+
"flag"
10+
"fmt"
11+
"io"
12+
"log"
13+
"net/http"
14+
"os"
15+
"strings"
16+
17+
"github.com/google/syzkaller/pkg/aflow"
18+
"github.com/google/syzkaller/pkg/aflow/flow/patching"
19+
"github.com/google/syzkaller/pkg/aflow/journal"
20+
"github.com/google/syzkaller/pkg/osutil"
21+
"github.com/google/syzkaller/pkg/tool"
22+
)
23+
24+
func main() {
25+
var (
26+
flagFlow = flag.String("workflow", "", "workflow to execute")
27+
flagInput = flag.String("input", "", "input json file with workflow arguments")
28+
flagWorkdir = flag.String("workdir", "", "directory for kernel checkout, kernel builds, etc")
29+
flagLargeModel = flag.Bool("large-model", true, "use large/expensive or small/cheap model")
30+
flagDownloadBug = flag.String("download-bug", "", "extid of a bug to download from the dashboard and save into -input file")
31+
)
32+
defer tool.Init()()
33+
if *flagDownloadBug != "" {
34+
if err := downloadBug(*flagDownloadBug, *flagInput); err != nil {
35+
tool.Fail(err)
36+
}
37+
return
38+
}
39+
if *flagFlow == "" {
40+
fmt.Fprintf(os.Stderr, "syz-aflow usage:\n")
41+
flag.PrintDefaults()
42+
fmt.Fprintf(os.Stderr, "available workflows:\n")
43+
for _, flow := range aflow.Flows {
44+
fmt.Fprintf(os.Stderr, "\t%v: %v\n", flow.Name, flow.Description)
45+
}
46+
return
47+
}
48+
ctx := context.Background()
49+
out, err := run(ctx, *flagLargeModel, *flagFlow, *flagInput, *flagWorkdir)
50+
if err != nil {
51+
tool.Fail(err)
52+
}
53+
os.Stdout.Write(out)
54+
}
55+
56+
func run(ctx context.Context, largeModel bool, flowName, inputFile, workdir string) ([]byte, error) {
57+
flow := aflow.Flows[flowName]
58+
if flow == nil {
59+
return nil, fmt.Errorf("workflow %q is not found", flowName)
60+
}
61+
inputData, err := os.ReadFile(inputFile)
62+
if err != nil {
63+
return nil, fmt.Errorf("failed to open -input file: %w", err)
64+
}
65+
inputs, err := flow.UnmarshalInputs(inputData)
66+
if err != nil {
67+
return nil, err
68+
}
69+
if workdir == "" {
70+
return nil, fmt.Errorf("-workdir is empty")
71+
}
72+
out, err := flow.Execute(ctx, largeModel, workdir, inputs, nil, onEvent)
73+
if err != nil {
74+
return nil, err
75+
}
76+
return json.MarshalIndent(out, "", "\t")
77+
}
78+
79+
func onEvent(ev *journal.Event) error {
80+
log.Printf("%v%v", strings.Repeat(" ", ev.Nesting), ev.Description())
81+
dump, err := json.MarshalIndent(ev, "", "\t")
82+
if err != nil {
83+
return err
84+
}
85+
fmt.Printf("%s\n\n", dump)
86+
return nil
87+
}
88+
89+
func downloadBug(extID, inputFile string) error {
90+
if inputFile == "-download-bug requires -input flag" {
91+
return fmt.Errorf("")
92+
}
93+
resp, err := get(fmt.Sprintf("/bug?extid=%v&json=1", extID))
94+
if err != nil {
95+
return err
96+
}
97+
var info map[string]any
98+
if err := json.Unmarshal([]byte(resp), &info); err != nil {
99+
return err
100+
}
101+
// TODO: expose arch/vmarch, syz repro opts, total number of crashes in the API.
102+
crash := info["crashes"].([]any)[0].(map[string]any)
103+
inputs := patching.Inputs{
104+
SyzkallerCommit: crash["syzkaller-commit"].(string),
105+
}
106+
inputs.ReproSyz, err = get(crash["syz-reproducer"].(string))
107+
if err != nil {
108+
return err
109+
}
110+
inputs.ReproC, err = get(crash["c-reproducer"].(string))
111+
if err != nil {
112+
return err
113+
}
114+
inputs.KernelConfig, err = get(crash["kernel-config"].(string))
115+
if err != nil {
116+
return err
117+
}
118+
data, err := json.MarshalIndent(inputs, "", "\t")
119+
if err != nil {
120+
return err
121+
}
122+
return osutil.WriteFile(inputFile, data)
123+
}
124+
125+
func get(path string) (string, error) {
126+
if path == "" {
127+
return "", nil
128+
}
129+
const host = "https://syzbot.org"
130+
resp, err := http.Get(fmt.Sprintf("%v%v", host, path))
131+
if err != nil {
132+
return "", err
133+
}
134+
defer resp.Body.Close()
135+
body, err := io.ReadAll(resp.Body)
136+
return string(body), err
137+
}

0 commit comments

Comments
 (0)