Skip to content

Commit a10e175

Browse files
committed
pkg/aflow/flow/assessment: add UAF moderation workflow
Add workflow that can be used for moderation of UAF bugs (consistent/actionable reports), such UAF bugs can be upstreammed automatically, even if they happened only once and don't have a reproducer.
1 parent ebb1853 commit a10e175

File tree

6 files changed

+136
-2
lines changed

6 files changed

+136
-2
lines changed

dashboard/app/ai.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/google/syzkaller/dashboard/app/aidb"
1717
"github.com/google/syzkaller/dashboard/dashapi"
1818
"github.com/google/syzkaller/pkg/aflow/ai"
19+
"github.com/google/syzkaller/pkg/report/crash"
1920
"github.com/google/syzkaller/pkg/vcs"
2021
db "google.golang.org/appengine/v2/datastore"
2122
)
@@ -322,6 +323,7 @@ func bugJobCreate(ctx context.Context, workflow string, typ ai.WorkflowType, bug
322323
Description: bug.displayTitle(),
323324
Link: fmt.Sprintf("/bug?id=%v", bug.keyHash(ctx)),
324325
Args: spanner.NullJSON{Valid: true, Value: map[string]any{
326+
"BugTitle": bug.Title,
325327
"ReproOpts": string(crash.ReproOpts),
326328
"ReproSyzID": crash.ReproSyz,
327329
"ReproCID": crash.ReproC,
@@ -423,11 +425,21 @@ const currentAIJobCheckSeq = 1
423425

424426
func workflowsForBug(bug *Bug, manual bool) map[ai.WorkflowType]bool {
425427
workflows := make(map[ai.WorkflowType]bool)
426-
if strings.HasPrefix(bug.Title, "KCSAN: data-race") {
428+
typ := crash.TitleToType(bug.Title)
429+
// UAF bugs stuck in last but one reporting.
430+
if typ.IsUAF() && len(bug.Reporting) > 1 &&
431+
bug.Reporting[len(bug.Reporting)-1].Reported.IsZero() &&
432+
!bug.Reporting[len(bug.Reporting)-2].Reported.IsZero() {
433+
workflows[ai.WorkflowModeration] = true
434+
}
435+
if typ == crash.KCSANDataRace {
427436
workflows[ai.WorkflowAssessmentKCSAN] = true
428437
}
429438
if manual {
430439
// Types we don't create automatically yet, but can be created manually.
440+
if typ.IsUAF() {
441+
workflows[ai.WorkflowModeration] = true
442+
}
431443
if bug.HeadReproLevel > dashapi.ReproLevelNone {
432444
workflows[ai.WorkflowPatching] = true
433445
}

dashboard/app/ai_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ func TestAIJob(t *testing.T) {
124124
require.NotEqual(t, resp.ID, "")
125125
require.Equal(t, resp.Workflow, "assessment-kcsan")
126126
require.Equal(t, resp.Args, map[string]any{
127+
"BugTitle": "KCSAN: data-race in foo / bar",
127128
"CrashReport": "report1",
128129
"KernelRepo": "repo1",
129130
"KernelCommit": "1111111111111111111111111111111111111111",

pkg/aflow/ai/ai.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ package ai
66

77
type WorkflowType string
88

9+
// Note: don't change string values of these types w/o a good reason.
10+
// They are stored in the dashboard database as strings.
911
const (
1012
WorkflowPatching = WorkflowType("patching")
13+
WorkflowModeration = WorkflowType("moderation")
1114
WorkflowAssessmentKCSAN = WorkflowType("assessment-kcsan")
1215
)

pkg/aflow/flow/assessment/kcsan.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ by a mutual exclusion primitive.
7272
7373
In the final reply explain why you think the given data race is benign or is harmful.
7474
75-
Use the provided tools to confirm any assumptions, what variables/fields being accessed, etc.
75+
Use the provided tools to confirm any assumptions, variables/fields being accessed, etc.
7676
In particular, don't make assumptions about the kernel source code,
7777
use codesearch tools to read the actual source code.
7878
`
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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 assessmenet
5+
6+
import (
7+
"fmt"
8+
9+
"github.com/google/syzkaller/pkg/aflow"
10+
"github.com/google/syzkaller/pkg/aflow/action/kernel"
11+
"github.com/google/syzkaller/pkg/aflow/ai"
12+
"github.com/google/syzkaller/pkg/aflow/tool/codesearcher"
13+
"github.com/google/syzkaller/pkg/report/crash"
14+
)
15+
16+
type moderationInputs struct {
17+
BugTitle string
18+
CrashReport string
19+
KernelRepo string
20+
KernelCommit string
21+
KernelConfig string
22+
CodesearchToolBin string
23+
}
24+
25+
type moderationOutputs struct {
26+
Confident bool
27+
Actionable bool
28+
Explanation string
29+
}
30+
31+
func init() {
32+
aflow.Register[moderationInputs, moderationOutputs](
33+
ai.WorkflowModeration,
34+
"assess if a bug report is consistent and actionable or not",
35+
&aflow.Flow{
36+
Root: &aflow.Pipeline{
37+
Actions: []aflow.Action{
38+
aflow.NewFuncAction("extract-crash-type", extractCrashType),
39+
kernel.Checkout,
40+
kernel.Build,
41+
codesearcher.PrepareIndex,
42+
&aflow.LLMAgent{
43+
Name: "expert",
44+
Reply: "Explanation",
45+
Outputs: aflow.LLMOutputs[struct {
46+
Confident bool `jsonschema:"If you are confident in the verdict of the analysis or not."`
47+
Actionable bool `jsonschema:"If the report is actionable or not."`
48+
}](),
49+
Temperature: 1,
50+
Instruction: moderationInstruction,
51+
Prompt: moderationPrompt,
52+
Tools: codesearcher.Tools,
53+
},
54+
},
55+
},
56+
},
57+
)
58+
}
59+
60+
const moderationInstruction = `
61+
You are an experienced Linux kernel developer tasked with determining if the given kernel bug
62+
report is actionable or not. Actionable means that it contains enough info to root cause
63+
the underlying bug, and that the report is self-consistent and makes sense, rather than
64+
e.g. a one-off nonsensical crash induced by a previous memory corruption.
65+
66+
{{if .IsUAF}}
67+
The bug report is about a use-after-free bug generated by KASAN tool.
68+
It should contain 3 stack traces: the bad memory access stack, the heap block allocation stack,
69+
and the heap block free stack. If the report does not contain 3 stacks, it's not actionable.
70+
71+
All 3 stack traces should be related to the same object type,
72+
and usually be in the same kernel subsystem (at least leaf stack frames).
73+
An example of an actionable and consistent report would be: first access stack relates
74+
to an access to a field of struct Foo, allocation/free stacks relate to allocation/free
75+
of the struct Foo.
76+
In inconsistent/nonsensical reports an access may be to a struct Foo, but allocation
77+
stack allocates a different structure in a different subsystem.
78+
Look for other suspicious signals/inconsistencies that can make this report hard to
79+
debug/understand.
80+
{{end}}
81+
82+
In the final reply explain why you think the report is self-consistent and actionable,
83+
or why it's inconsistent and/or not actionable.
84+
85+
Use the provided tools to confirm any assumptions, variables/fields being accessed, etc.
86+
In particular, don't make assumptions about the kernel source code,
87+
use codesearch tools to read the actual source code.
88+
`
89+
90+
const moderationPrompt = `
91+
The bug report is:
92+
93+
{{.CrashReport}}
94+
`
95+
96+
type extractArgs struct {
97+
BugTitle string
98+
}
99+
100+
type extractResult struct {
101+
IsUAF bool
102+
}
103+
104+
func extractCrashType(ctx *aflow.Context, args extractArgs) (extractResult, error) {
105+
var res extractResult
106+
typ := crash.TitleToType(args.BugTitle)
107+
switch {
108+
case typ.IsUAF():
109+
res.IsUAF = true
110+
default:
111+
return res, fmt.Errorf("unsupported bug type")
112+
}
113+
return res, nil
114+
}

pkg/report/crash/types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ func (t Type) IsKASAN() bool {
7979
KASANUseAfterFreeRead, KASANUseAfterFreeWrite, KASANInvalidFree, KASANUnknown}, t)
8080
}
8181

82+
func (t Type) IsUAF() bool {
83+
return slices.Contains([]Type{KASANUseAfterFreeRead, KASANUseAfterFreeWrite}, t)
84+
}
85+
8286
func (t Type) IsKMSAN() bool {
8387
return slices.Contains([]Type{
8488
KMSANUninitValue, KMSANInfoLeak, KMSANUseAfterFreeRead, KMSANUnknown}, t)

0 commit comments

Comments
 (0)