Skip to content

Commit f963d06

Browse files
author
Ethan Graham
committed
pkg/kfuzztest: use dwarf type in syscall description generation
Prior to this, we were doing pattern matching on the dwarf type names. This is brittle, and means that support for new types (e.g., u64) would have to be added manually. Switch to an approach that recurses over the dwarf types, resolving typedefs into primitive types that map very easily onto syzkaller types. Also refactor some of the code related to annotation processing, which was become unwieldly.
1 parent 5d7b6ef commit f963d06

File tree

6 files changed

+175
-116
lines changed

6 files changed

+175
-116
lines changed

pkg/kfuzztest/builder.go

Lines changed: 150 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
package kfuzztest
44

55
import (
6+
"debug/dwarf"
67
"fmt"
7-
"regexp"
88
"strings"
99

1010
"github.com/google/syzkaller/pkg/ast"
@@ -53,7 +53,11 @@ func (b *Builder) EmitSyzlangDescription() (string, error) {
5353
var descBuilder strings.Builder
5454
descBuilder.WriteString("# This description was automatically generated with tools/kfuzztest-gen\n")
5555
for _, s := range b.structs {
56-
descBuilder.WriteString(syzStructToSyzlang(s, constraintMap, annotationMap))
56+
structDesc, err := syzStructToSyzlang(s, constraintMap, annotationMap)
57+
if err != nil {
58+
return "", err
59+
}
60+
descBuilder.WriteString(structDesc)
5761
descBuilder.WriteString("\n\n")
5862
}
5963

@@ -64,9 +68,7 @@ func (b *Builder) EmitSyzlangDescription() (string, error) {
6468
}
6569
}
6670

67-
fmt.Println(descBuilder.String())
68-
69-
// Format the output syzlang descriptions.
71+
// Format the output syzlang descriptions for consistency.
7072
var astError error
7173
eh := func(pos ast.Pos, msg string) {
7274
astError = fmt.Errorf("Failure: %v: %v\n", pos, msg)
@@ -76,135 +78,181 @@ func (b *Builder) EmitSyzlangDescription() (string, error) {
7678
return "", astError
7779
}
7880
if descAst == nil {
79-
return "", fmt.Errorf("Failed to format generated syzlang. Is it well-formed?")
81+
return "", fmt.Errorf("failed to format generated syzkaller description - is it well-formed?")
8082
}
81-
8283
return string(ast.Format(descAst)), nil
8384
}
8485

85-
// FIXME: this function is gross because of the weird logic cases that arises
86-
// from having annotations that determine the type. I'm sure there's a much
87-
// nicer way of writing this control flow.
8886
func syzStructToSyzlang(s SyzStruct, constraintMap map[string]map[string]SyzConstraint,
89-
annotationMap map[string]map[string]SyzAnnotation) string {
90-
out := fmt.Sprintf("%s {\n", s.Name)
91-
for _, field := range s.Fields {
92-
out += "\t"
93-
typeName := dwarfToSyzlangType(field.TypeName)
94-
95-
aSubMap, ok := annotationMap["struct "+s.Name]
96-
if ok {
97-
annotation, ok := aSubMap[field.Name]
98-
if !ok {
99-
goto append_type
100-
}
101-
102-
// Annotated fields require special handling.
103-
switch annotation.Attribute {
104-
case AttributeLen:
105-
out += fmt.Sprintf("%s\tlen[%s, %s]", field.Name, annotation.LinkedFieldName, typeName)
106-
case AttributeString:
107-
out += fmt.Sprintf("%s\tptr[in, string]", field.Name)
108-
case AttributeArray:
109-
// An array type is prefixed with a leading "*", which we remove
110-
// to resolve the underlying type.
111-
arrayType := typeName[1:]
112-
out += fmt.Sprintf("%s\tptr[in, array[%s]]", field.Name, arrayType)
113-
}
114-
out += "\n"
115-
continue
116-
}
87+
annotationMap map[string]map[string]SyzAnnotation) (string, error) {
88+
var builder strings.Builder
11789

118-
// just appends the type as it appear in the
119-
append_type:
120-
out += fmt.Sprintf("%s\t%s", field.Name, typeName)
121-
122-
subMap, ok := constraintMap["struct "+s.Name]
123-
if ok {
124-
constraint, ok := subMap[field.Name]
125-
if ok {
126-
out += syzConstraintToSyzlang(constraint)
127-
}
90+
fmt.Fprintf(&builder, "%s {\n", s.Name)
91+
structAnnotations := annotationMap["struct "+s.Name]
92+
structConstraints := constraintMap["struct "+s.Name]
93+
for _, field := range s.Fields {
94+
line, err := syzFieldToSyzLang(field, structConstraints, structAnnotations)
95+
if err != nil {
96+
return "", err
12897
}
129-
out += "\n"
98+
fmt.Fprintf(&builder, "\t%s\n", line)
13099
}
131-
out += "}"
132-
return out
100+
fmt.Fprint(&builder, "}")
101+
return builder.String(), nil
133102
}
134103

135-
func syzFuncToSyzlang(s SyzFunc) string {
136-
typeName := strings.TrimPrefix(s.InputStructName, "struct ")
104+
func syzFieldToSyzLang(field SyzField, constraintMap map[string]SyzConstraint,
105+
annotationMap map[string]SyzAnnotation) (string, error) {
106+
constraint, hasConstraint := constraintMap[field.Name]
107+
annotation, hasAnnotation := annotationMap[field.Name]
137108

138-
out := fmt.Sprintf("syz_kfuzztest_run$%s(", s.Name)
139-
out += fmt.Sprintf("name ptr[in, string[\"%s\"]], ", s.Name)
140-
out += fmt.Sprintf("data ptr[in, %s], ", typeName)
141-
out += "len bytesize[data])"
142-
// TODO:(ethangraham) The only other way I can think of getting this name
143-
// would involve using the "reflect" package and matching against the
144-
// KFuzzTest name, which isn't much better than hardcoding this.
145-
out += "(kfuzz_test)"
109+
var typeDesc string
110+
var err error
111+
if hasAnnotation {
112+
// Annotations override the existing type definitions.
113+
typeDesc, err = processAnnotation(field, annotation)
114+
} else {
115+
typeDesc, err = dwarfToSyzlangType(field.dwarfType, false)
116+
}
117+
if err != nil {
118+
return "", err
119+
}
146120

147-
return out
121+
if hasConstraint {
122+
constraint, err := processConstraint(constraint)
123+
if err != nil {
124+
return "", err
125+
}
126+
typeDesc += constraint
127+
}
128+
return fmt.Sprintf("%s %s", field.Name, typeDesc), nil
148129
}
149130

150-
func syzConstraintToSyzlang(c SyzConstraint) string {
131+
func processConstraint(c SyzConstraint) (string, error) {
151132
switch c.ConstraintType {
152133
case ExpectEq:
153-
return fmt.Sprintf("[%d]", c.Value1)
134+
return fmt.Sprintf("[%d]", c.Value1), nil
135+
case ExpectNe:
136+
// syzkaller does not have a built-in way to support an inequality
137+
// constraint AFAIK.
138+
return "", nil
154139
case ExpectLt:
155-
return fmt.Sprintf("[0:%d]", c.Value1-1)
140+
return fmt.Sprintf("[0:%d]", c.Value1-1), nil
156141
case ExpectLe:
157-
return fmt.Sprintf("[0:%d]", c.Value1)
142+
return fmt.Sprintf("[0:%d]", c.Value1), nil
158143
case ExpectGt:
159-
return fmt.Sprintf("[%d]", c.Value1+1)
144+
return fmt.Sprintf("[%d]", c.Value1+1), nil
160145
case ExpectGe:
161-
return fmt.Sprintf("[%d]", c.Value1)
146+
return fmt.Sprintf("[%d]", c.Value1), nil
162147
case ExpectInRange:
163-
return fmt.Sprintf("[%d:%d]", c.Value1, c.Value2)
148+
return fmt.Sprintf("[%d:%d]", c.Value1, c.Value2), nil
164149
default:
165-
return ""
150+
fmt.Printf("c = %d\n", c.ConstraintType)
151+
return "", fmt.Errorf("unsupported constraint type")
166152
}
167153
}
168154

169-
func isArray(typeName string) (bool, string) {
170-
re := regexp.MustCompile(`^\[(\d+)\]([a-zA-Z]+)$`)
171-
matches := re.FindStringSubmatch(typeName)
172-
if len(matches) == 0 {
173-
return false, ""
155+
func processAnnotation(field SyzField, annotation SyzAnnotation) (string, error) {
156+
switch annotation.Attribute {
157+
case AttributeLen:
158+
underlyingType, err := dwarfToSyzlangType(field.dwarfType, false)
159+
if err != nil {
160+
return "", err
161+
}
162+
return fmt.Sprintf("len[%s, %s]", annotation.LinkedFieldName, underlyingType), nil
163+
case AttributeString:
164+
return "ptr[in, string]", nil
165+
case AttributeArray:
166+
pointeeType, isPtr := resolvesToPtr(field.dwarfType)
167+
if !isPtr {
168+
return "", fmt.Errorf("can only annotate pointer fields are arrays")
169+
}
170+
// TODO: discards const qualifier.
171+
typeDesc, err := dwarfToSyzlangType(pointeeType, false)
172+
if err != nil {
173+
return "", err
174+
}
175+
return fmt.Sprintf("ptr[in, array[%s]]", typeDesc), nil
176+
default:
177+
return "", fmt.Errorf("unsupported attribute type")
174178
}
175-
return true, fmt.Sprintf("array[%s, %s]", dwarfToSyzlangType(matches[2]), matches[1])
176179
}
177180

178-
func dwarfToSyzlangType(typeName string) string {
179-
if after, ok := strings.CutPrefix(typeName, "struct "); ok {
180-
return after
181+
// Returns true iff `dwarfType` resolved down to a pointer. For example,
182+
// a `const *void` which isn't directly a pointer.
183+
func resolvesToPtr(dwarfType dwarf.Type) (dwarf.Type, bool) {
184+
switch t := dwarfType.(type) {
185+
case *dwarf.QualType:
186+
return resolvesToPtr(t.Type)
187+
case *dwarf.PtrType:
188+
return t.Type, true
181189
}
190+
return nil, false
191+
}
182192

183-
if after, ok := strings.CutPrefix(typeName, "*const struct"); ok {
184-
return fmt.Sprintf("ptr[in, %s]", after)
185-
} else if after, ok := strings.CutPrefix(typeName, "*struct"); ok {
186-
return fmt.Sprintf("ptr[inout, %s]", after)
187-
}
193+
func syzFuncToSyzlang(s SyzFunc) string {
194+
var builder strings.Builder
195+
typeName := strings.TrimPrefix(s.InputStructName, "struct ")
188196

189-
isArr, arr := isArray(typeName)
190-
if isArr {
191-
return arr
192-
}
197+
fmt.Fprintf(&builder, "syz_kfuzztest_run$%s(", s.Name)
198+
fmt.Fprintf(&builder, "name ptr[in, string[\"%s\"]], ", s.Name)
199+
fmt.Fprintf(&builder, "data ptr[in, %s], ", typeName)
200+
builder.WriteString("len bytesize[data])")
201+
// TODO:(ethangraham) The only other way I can think of getting this name
202+
// would involve using the "reflect" package and matching against the
203+
// KFuzzTest name, which isn't much better than hardcoding this.
204+
builder.WriteString("(kfuzz_test)")
205+
return builder.String()
206+
}
193207

194-
switch typeName {
195-
case "long unsigned int", "long int", "size_t":
196-
return "int64"
197-
case "int", "unsigned int":
198-
return "int32"
199-
case "char":
200-
return "int8"
201-
case "__u16":
202-
return "int16"
203-
case "*const char", "*const void", "*const unsigned char":
204-
return "ptr[in, array[int8]]" // const pointers are read-only
205-
case "*char", "*void":
206-
return "ptr[inout, array[int8]]"
208+
// Given a dwarf type, returns a syzlang string representation of this type.
209+
func dwarfToSyzlangType(dwarfType dwarf.Type, isConst bool) (string, error) {
210+
var dir string
211+
if isConst {
212+
dir = "in"
213+
} else {
214+
dir = "inout"
215+
}
216+
switch t := dwarfType.(type) {
217+
case *dwarf.PtrType:
218+
underlyingType, err := dwarfToSyzlangType(t.Type, false)
219+
if err != nil {
220+
return "", err
221+
}
222+
return fmt.Sprintf("ptr[%s, %s]", dir, underlyingType), nil
223+
case *dwarf.QualType:
224+
if t.Qual == "const" {
225+
return dwarfToSyzlangType(t.Type, true)
226+
} else {
227+
return "", fmt.Errorf("no support for %s qualifier", t.Qual)
228+
}
229+
case *dwarf.ArrayType:
230+
underlyingType, err := dwarfToSyzlangType(t.Type, false)
231+
if err != nil {
232+
return "", err
233+
}
234+
// If t.Count == -1 then this is a varlen array as per debug/dwarf
235+
// documentation.
236+
if t.Count == -1 {
237+
return fmt.Sprintf("array[%s]", underlyingType), nil
238+
} else {
239+
return fmt.Sprintf("array[%s, %d]", underlyingType, t.Count), nil
240+
}
241+
case *dwarf.TypedefType:
242+
return dwarfToSyzlangType(t.Type, isConst)
243+
case *dwarf.IntType, *dwarf.UintType:
244+
numBits := t.Size() * 8
245+
return fmt.Sprintf("int%d", numBits), nil
246+
case *dwarf.CharType, *dwarf.UcharType:
247+
return "int8", nil
248+
// `void` isn't a valid type by itself, so we know that it would have
249+
// been wrapped in a pointer, e.g., `void *`. For this reason, we can return
250+
// just interpret it as a byte, i.e., int8.
251+
case *dwarf.VoidType:
252+
return "int8", nil
253+
case *dwarf.StructType:
254+
return strings.TrimPrefix(t.StructName, "struct "), nil
207255
default:
208-
return typeName
256+
return "", fmt.Errorf("unsupported type %s", dwarfType.String())
209257
}
210258
}

pkg/kfuzztest/extractor.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,10 @@ func (e *Extractor) dwarfGetType(entry *dwarf.Entry) (dwarf.Type, error) {
279279
return e.dwarfData.Type(typeOffset)
280280
}
281281

282+
// extractStructs extracts input structure metadata from discovered KFuzzTest
283+
// targets (funcs).
284+
// Performs a tree-traversal as all struct types need to be defined in the
285+
// resulting description that is emitted by the builder.
282286
func (e *Extractor) extractStructs(funcs []SyzFunc) ([]SyzStruct, error) {
283287
// Set of input map names so that we can skip over entries that aren't
284288
// interesting.
@@ -311,9 +315,9 @@ func (e *Extractor) extractStructs(funcs []SyzFunc) ([]SyzStruct, error) {
311315
var visitRecur func(*dwarf.StructType, *map[string]bool)
312316
visited := make(map[string]bool)
313317
visitRecur = func(start *dwarf.StructType, visited *map[string]bool) {
314-
newStruct := SyzStruct{Name: start.StructName, Fields: make([]SyzField, 0)}
318+
newStruct := SyzStruct{dwarfType: start, Name: start.StructName, Fields: make([]SyzField, 0)}
315319
for _, child := range start.Field {
316-
newField := SyzField{Name: child.Name, TypeName: child.Type.String()}
320+
newField := SyzField{Name: child.Name, dwarfType: child.Type}
317321
newStruct.Fields = append(newStruct.Fields, newField)
318322
switch childType := child.Type.(type) {
319323
case *dwarf.StructType:
@@ -344,14 +348,14 @@ func (e *Extractor) extractStructs(funcs []SyzFunc) ([]SyzStruct, error) {
344348
if err != nil {
345349
return nil, err
346350
}
347-
// EOF
351+
// EOF.
348352
if entry == nil {
349353
break
350354
}
351355
if entry.Tag != dwarf.TagStructType {
352356
continue
353357
}
354-
// skip over unnamed structures
358+
// Skip over unnamed structures.
355359
nameField := entry.AttrField(dwarf.AttrName)
356360
if nameField == nil {
357361
continue

pkg/kfuzztest/kfuzztest.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
package kfuzztest
1111

1212
import (
13+
"debug/dwarf"
1314
"fmt"
1415
"os"
1516
"path"
@@ -19,18 +20,20 @@ import (
1920
"github.com/google/syzkaller/pkg/ast"
2021
"github.com/google/syzkaller/pkg/compiler"
2122
"github.com/google/syzkaller/pkg/kcov"
23+
"github.com/google/syzkaller/pkg/log"
2224
"github.com/google/syzkaller/prog"
2325
"github.com/google/syzkaller/sys/targets"
2426
)
2527

2628
type SyzField struct {
27-
Name string
28-
TypeName string
29+
Name string
30+
dwarfType dwarf.Type
2931
}
3032

3133
type SyzStruct struct {
32-
Name string
33-
Fields []SyzField
34+
dwarfType *dwarf.StructType
35+
Name string
36+
Fields []SyzField
3437
}
3538

3639
type SyzFunc struct {

0 commit comments

Comments
 (0)