diff --git a/docs/syscall_descriptions_syntax.md b/docs/syscall_descriptions_syntax.md index fe3b72a3e339..e2d1bac63b8f 100644 --- a/docs/syscall_descriptions_syntax.md +++ b/docs/syscall_descriptions_syntax.md @@ -263,6 +263,31 @@ Each resource type must be "produced" (used as an output) by at least one syscal (outside of unions and optional pointers) and "consumed" (used as an input) by at least one syscall. +This restriction helps automatically determine the dependent syscalls there's no reason +to fuzz. But in some cases one might not need such a strict validation. For example, +consider the following minimalistic syzlang description: + +``` +resource inout_resource[int32]: 0 + +test$use_resource(a ptr[inout, inout_resource_struct]) + +inout_resource_struct { + field inout_resource +} +``` + +Syzkaller would conclude that there's no way to construct `inout_resource` (which is +necessary to generate `inout_resource_struct`) and disable the `test$use_resource` call. +However, the resource is obviously not mandatory here. + +To indicate this to syzkaller, you may mark the resource as optional: + +``` +resource inout_resource[int32, opt] +``` + + ## Type Aliases Complex types that are often repeated can be given short type aliases using the diff --git a/pkg/ast/ast.go b/pkg/ast/ast.go index 2726af602568..b47a669ae02a 100644 --- a/pkg/ast/ast.go +++ b/pkg/ast/ast.go @@ -98,6 +98,7 @@ type Resource struct { Name *Ident Base *Type Values []*Int + Args []*Type } func (n *Resource) Info() (Pos, string, string) { diff --git a/pkg/ast/clone.go b/pkg/ast/clone.go index e23a9eb7009d..ddf7d8da3e84 100644 --- a/pkg/ast/clone.go +++ b/pkg/ast/clone.go @@ -59,6 +59,7 @@ func (n *Resource) Clone() Node { Name: n.Name.Clone().(*Ident), Base: n.Base.Clone().(*Type), Values: cloneInts(n.Values), + Args: cloneTypes(n.Args), } } diff --git a/pkg/ast/format.go b/pkg/ast/format.go index 4b54466c275a..15c597da8f25 100644 --- a/pkg/ast/format.go +++ b/pkg/ast/format.go @@ -95,7 +95,12 @@ func (n *Define) serialize(w io.Writer) { } func (n *Resource) serialize(w io.Writer) { - fmt.Fprintf(w, "resource %v[%v]", n.Name.Name, fmtType(n.Base)) + fmt.Fprintf(w, "resource %v[%v", n.Name.Name, fmtType(n.Base)) + for _, a := range n.Args { + fmt.Fprintf(w, ", ") + a.serialize(w) + } + fmt.Fprintf(w, "]") for i, v := range n.Values { fmt.Fprintf(w, "%v%v", comma(i, ": "), fmtInt(v)) } diff --git a/pkg/ast/parser.go b/pkg/ast/parser.go index 2f2e62055856..794df632a07a 100644 --- a/pkg/ast/parser.go +++ b/pkg/ast/parser.go @@ -242,6 +242,10 @@ func (p *parser) parseResource() *Resource { name := p.parseIdent() p.consume(tokLBrack) base := p.parseType() + var args []*Type + if p.tryConsume(tokComma) { + args = append(args, p.parseType()) + } p.consume(tokRBrack) var values []*Int if p.tryConsume(tokColon) { @@ -254,6 +258,7 @@ func (p *parser) parseResource() *Resource { Pos: pos0, Name: name, Base: base, + Args: args, Values: values, } } diff --git a/pkg/ast/testdata/all.txt b/pkg/ast/testdata/all.txt index a274b3effcf8..b8ef6e9ca161 100644 --- a/pkg/ast/testdata/all.txt +++ b/pkg/ast/testdata/all.txt @@ -30,3 +30,5 @@ condFields { f3 int16 (out, if[val[mask] & SOME_CONST & OTHER_CONST == val[mask] & CONST_X]) f4 int16 (out, if[val[flags] & SOME_CONST]) } + +resource some_resource[int32, opt] diff --git a/pkg/ast/walk.go b/pkg/ast/walk.go index 2322d32e05fa..5506de636a09 100644 --- a/pkg/ast/walk.go +++ b/pkg/ast/walk.go @@ -59,6 +59,9 @@ func (n *Resource) walk(cb func(Node)) { for _, v := range n.Values { cb(v) } + for _, a := range n.Args { + cb(a) + } } func (n *TypeDef) walk(cb func(Node)) { diff --git a/pkg/compiler/check.go b/pkg/compiler/check.go index 103b69eb5dc4..23f1a4f916de 100644 --- a/pkg/compiler/check.go +++ b/pkg/compiler/check.go @@ -304,6 +304,7 @@ func (comp *compiler) checkTypes() { switch n := decl.(type) { case *ast.Resource: comp.checkType(checkCtx{}, n.Base, checkIsResourceBase) + comp.checkResource(n) case *ast.Struct: comp.checkStruct(checkCtx{}, n) case *ast.Call: @@ -312,6 +313,13 @@ func (comp *compiler) checkTypes() { } } +func (comp *compiler) checkResource(n *ast.Resource) { + args, _ := removeOpt(n.Args) + if len(args) > 0 { + comp.error(n.Pos, "unexpected resource arguments, only opt is supported now") + } +} + func (comp *compiler) checkTypeValues() { for _, decl := range comp.desc.Nodes { switch n := decl.(type) { @@ -1104,7 +1112,7 @@ func (comp *compiler) checkTypeBasic(t *ast.Type, desc *typeDesc, flags checkFla } func (comp *compiler) checkTypeArgs(t *ast.Type, desc *typeDesc, flags checkFlags) []*ast.Type { - args, opt := removeOpt(t) + args, opt := removeOpt(t.Args) if opt != nil { if len(opt.Args) != 0 { comp.error(opt.Pos, "opt can't have arguments") diff --git a/pkg/compiler/compiler.go b/pkg/compiler/compiler.go index e5988f03ed7e..9cce7b156cdf 100644 --- a/pkg/compiler/compiler.go +++ b/pkg/compiler/compiler.go @@ -306,7 +306,7 @@ func (comp *compiler) getArgsBase(t *ast.Type, isArg bool) (*typeDesc, []*ast.Ty if desc == nil { panic(fmt.Sprintf("no type desc for %#v", *t)) } - args, opt := removeOpt(t) + args, opt := removeOpt(t.Args) com := genCommon(t.Ident, sizeUnassigned, opt != nil) base := genIntCommon(com, 0, false) if desc.NeedBase { @@ -366,8 +366,7 @@ func (comp *compiler) foreachSubType(t *ast.Type, isArg bool, } } -func removeOpt(t *ast.Type) ([]*ast.Type, *ast.Type) { - args := t.Args +func removeOpt(args []*ast.Type) ([]*ast.Type, *ast.Type) { if last := len(args) - 1; last >= 0 && args[last].Ident == "opt" { return args[:last], args[last] } diff --git a/pkg/compiler/gen.go b/pkg/compiler/gen.go index f91e2502c6da..d504b502a9e5 100644 --- a/pkg/compiler/gen.go +++ b/pkg/compiler/gen.go @@ -34,6 +34,9 @@ func (comp *compiler) genResource(n *ast.Resource) *prog.ResourceDesc { res := &prog.ResourceDesc{ Name: n.Name.Name, } + if _, opt := removeOpt(n.Args); opt != nil { + res.Optional = true + } for n != nil { res.Values = append(genIntArray(n.Values), res.Values...) res.Kind = append([]string{n.Name.Name}, res.Kind...) diff --git a/pkg/compiler/testdata/all.txt b/pkg/compiler/testdata/all.txt index d1baef2f929c..fb983b428e70 100644 --- a/pkg/compiler/testdata/all.txt +++ b/pkg/compiler/testdata/all.txt @@ -369,3 +369,11 @@ union$conditional3 [ ] conditional(a ptr[in, struct$conditional]) + +resource opt_resource[int32, opt] + +test$use_optional_resource(a ptr[inout, opt_resource_struct]) + +opt_resource_struct { + f opt_resource +} diff --git a/pkg/compiler/testdata/errors.txt b/pkg/compiler/testdata/errors.txt index 4f3186698faa..6e321671517f 100644 --- a/pkg/compiler/testdata/errors.txt +++ b/pkg/compiler/testdata/errors.txt @@ -507,3 +507,5 @@ conditional_fields_union2 [ u2 int32 ### either no fields have conditions or all except the last u3 int32 ] + +resource opt_resource[int32, custom_arg] ### unexpected resource arguments, only opt is supported now diff --git a/prog/resources.go b/prog/resources.go index d739b528efad..abf5ad1a74a8 100644 --- a/prog/resources.go +++ b/prog/resources.go @@ -151,7 +151,7 @@ func (target *Target) getInputResources(c *Syscall) []*ResourceDesc { } switch typ1 := typ.(type) { case *ResourceType: - if !ctx.Optional && !dedup[typ1.Desc] { + if !ctx.Optional && !dedup[typ1.Desc] && !typ1.Desc.Optional { dedup[typ1.Desc] = true resources = append(resources, typ1.Desc) } diff --git a/prog/types.go b/prog/types.go index 4e9d8455e680..252f163dcb98 100644 --- a/prog/types.go +++ b/prog/types.go @@ -310,10 +310,11 @@ type FlagDesc struct { } type ResourceDesc struct { - Name string - Kind []string - Values []uint64 - Ctors []ResourceCtor + Name string + Optional bool + Kind []string + Values []uint64 + Ctors []ResourceCtor } type ResourceCtor struct { diff --git a/sys/test/test.txt b/sys/test/test.txt index c59b9d3d36fc..a94b23d53c17 100644 --- a/sys/test/test.txt +++ b/sys/test/test.txt @@ -774,6 +774,14 @@ syz_use_missing { a1 syz_missing_const_struct } +resource inout_resource[fd, opt] + +test$use_resource(a ptr[inout, inout_resource_struct]) + +inout_resource_struct { + field inout_resource +} + # Hints tests. test$hint_int(a0 ptr[in, hint_ints])