prog: introduce call attributes

Add common infrastructure for syscall attributes.
Add few attributes we want, but they are not implemented for now
(don't affect behavior, this will follow).
This commit is contained in:
Dmitry Vyukov 2020-04-16 17:38:36 +02:00
parent 0781895e0f
commit 90d17ab898
12 changed files with 4730 additions and 4601 deletions

6
executor/defs.h generated
View File

@ -1,5 +1,11 @@
// AUTOGENERATED FILE
struct call_attrs_t {
uint64_t disabled;
uint64_t timeout;
uint64_t prog_timeout;
};
#if GOOS_akaros
#define GOOS "akaros"

View File

@ -172,6 +172,7 @@ typedef intptr_t(SYSCALLAPI* syscall_t)(intptr_t, intptr_t, intptr_t, intptr_t,
struct call_t {
const char* name;
int sys_nr;
call_attrs_t attrs;
syscall_t call;
};
@ -686,6 +687,9 @@ retry:
// Normal syscall.
if (call_num >= ARRAY_SIZE(syscalls))
fail("invalid command number %llu", call_num);
const call_t* call = &syscalls[call_num];
if (call->attrs.disabled)
fail("executing disabled syscall %s", call->name);
// call_extra_timeout must match timeout in pkg/csource/csource.go.
int call_extra_timeout = 0;
// TODO: find a way to tune timeout values.

9120
executor/syscalls.h generated

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,10 @@
package compiler
import (
"reflect"
"github.com/google/syzkaller/pkg/ast"
"github.com/google/syzkaller/prog"
)
type attrDesc struct {
@ -23,9 +26,12 @@ var (
structAttrs = makeAttrs(attrPacked, attrSize, attrAlign)
unionAttrs = makeAttrs(attrVarlen, attrSize)
callAttrs = make(map[string]*attrDesc)
)
func init() {
initCallAttrs()
attrSize.CheckConsts = func(comp *compiler, parent ast.Node, attr *ast.Type) {
_, typ, name := parent.Info()
if comp.structIsVarlen(name) {
@ -46,6 +52,22 @@ func init() {
}
}
func initCallAttrs() {
attrs := reflect.TypeOf(prog.SyscallAttrs{})
for i := 0; i < attrs.NumField(); i++ {
attr := attrs.Field(i)
desc := &attrDesc{Name: attr.Name}
switch attr.Type.Kind() {
case reflect.Bool:
case reflect.Uint64:
desc.HasArg = true
default:
panic("unsupported syscall attribute type")
}
callAttrs[prog.CppName(desc.Name)] = desc
}
}
func structOrUnionAttrs(n *ast.Struct) map[string]*attrDesc {
if n.IsUnion {
return unionAttrs

View File

@ -713,6 +713,7 @@ func (comp *compiler) checkCall(n *ast.Call) {
if n.Ret != nil {
comp.checkType(checkCtx{}, n.Ret, checkIsArg|checkIsRet)
}
comp.parseAttrs(callAttrs, n, n.Attrs)
}
type checkFlags int

View File

@ -77,32 +77,11 @@ func (comp *compiler) extractConsts() map[string]*ConstInfo {
if comp.target.SyscallNumbers && !strings.HasPrefix(n.CallName, "syz_") {
comp.addConst(infos, pos, comp.target.SyscallPrefix+n.CallName)
}
}
}
for _, decl := range comp.desc.Nodes {
switch decl.(type) {
case *ast.Call, *ast.Struct, *ast.Resource, *ast.TypeDef:
comp.foreachType(decl, func(t *ast.Type, desc *typeDesc,
args []*ast.Type, _ prog.IntTypeCommon) {
for i, arg := range args {
if desc.Args[i].Type.Kind == kindInt {
if arg.Ident != "" {
comp.addConst(infos, arg.Pos, arg.Ident)
}
for _, col := range arg.Colon {
if col.Ident != "" {
comp.addConst(infos, col.Pos, col.Ident)
}
}
}
for _, attr := range n.Attrs {
if callAttrs[attr.Ident].HasArg {
comp.addConst(infos, attr.Pos, attr.Args[0].Ident)
}
})
}
}
for _, decl := range comp.desc.Nodes {
switch n := decl.(type) {
}
case *ast.Struct:
for _, attr := range n.Attrs {
if structOrUnionAttrs(n)[attr.Ident].HasArg {
@ -110,17 +89,36 @@ func (comp *compiler) extractConsts() map[string]*ConstInfo {
}
}
}
switch decl.(type) {
case *ast.Call, *ast.Struct, *ast.Resource, *ast.TypeDef:
comp.extractTypeConsts(infos, decl)
}
}
comp.desc.Walk(ast.Recursive(func(n0 ast.Node) {
if n, ok := n0.(*ast.Int); ok {
comp.addConst(infos, n.Pos, n.Ident)
}
}))
return convertConstInfo(infos)
}
func (comp *compiler) extractTypeConsts(infos map[string]*constInfo, n ast.Node) {
comp.foreachType(n, func(t *ast.Type, desc *typeDesc, args []*ast.Type, _ prog.IntTypeCommon) {
for i, arg := range args {
if desc.Args[i].Type.Kind == kindInt {
if arg.Ident != "" {
comp.addConst(infos, arg.Pos, arg.Ident)
}
for _, col := range arg.Colon {
if col.Ident != "" {
comp.addConst(infos, col.Pos, col.Ident)
}
}
}
}
})
}
func (comp *compiler) addConst(infos map[string]*constInfo, pos ast.Pos, name string) {
if _, builtin := comp.builtinConsts[name]; builtin {
return
@ -220,12 +218,18 @@ func (comp *compiler) patchConsts(consts0 map[string]uint64) {
}
}
})
if n, ok := decl.(*ast.Resource); ok {
switch n := decl.(type) {
case *ast.Resource:
for _, v := range n.Values {
comp.patchIntConst(v, consts, &missing)
}
}
if n, ok := decl.(*ast.Struct); ok {
case *ast.Call:
for _, attr := range n.Attrs {
if callAttrs[attr.Ident].HasArg {
comp.patchTypeConst(attr.Args[0], consts, &missing)
}
}
case *ast.Struct:
for _, attr := range n.Attrs {
if structOrUnionAttrs(n)[attr.Ident].HasArg {
comp.patchTypeConst(attr.Args[0], consts, &missing)

View File

@ -5,6 +5,7 @@ package compiler
import (
"fmt"
"reflect"
"sort"
"github.com/google/syzkaller/pkg/ast"
@ -111,6 +112,16 @@ func (comp *compiler) genSyscall(n *ast.Call, argSizes []uint64) *prog.Syscall {
if n.Ret != nil {
ret = comp.genType(n.Ret, "ret", prog.DirOut, comp.ptrSize)
}
var attrs prog.SyscallAttrs
descAttrs := comp.parseAttrs(callAttrs, n, n.Attrs)
for desc, val := range descAttrs {
fld := reflect.ValueOf(&attrs).Elem().FieldByName(desc.Name)
if desc.HasArg {
fld.SetUint(val)
} else {
fld.SetBool(val != 0)
}
}
return &prog.Syscall{
Name: n.Name.Name,
CallName: n.CallName,
@ -118,6 +129,7 @@ func (comp *compiler) genSyscall(n *ast.Call, argSizes []uint64) *prog.Syscall {
MissingArgs: len(argSizes) - len(n.Args),
Args: comp.genFieldArray(n.Args, prog.DirIn, argSizes),
Ret: ret,
Attrs: attrs,
}
}

View File

@ -14,6 +14,9 @@ foo_9(a ptr[out, ptr[in, string]])
foo_10(a ptr[out, buffer[in]])
foo_11(a int64[1:100, 2])
foo_12(a int64[0:-1, 0x1000])
foo_13() (disabled)
foo_14() r0 (timeout[100])
foo_15() r0 (disabled, timeout[C1], prog_timeout[C2])
resource r0[intptr]

View File

@ -125,6 +125,9 @@ foo$65(a int32, b len[1]) ### unexpected int 1 for len target argument of len ty
foo$66(a int32, b len[a:1]) ### unexpected int 1 after colon, expect identifier
foo$67(x int32[1:2:3, opt]) ### unexpected ':'
foo$68(a int32[15, 2]) ### first argument of int32 needs to be a range
foo$69() (foo) ### unknown syscall foo$69 attribute foo
foo$70() ("foo") ### unexpected string "foo", expect attribute
foo$71() (42) ### unexpected int 42, expect attribute
opt { ### struct uses reserved name opt
f1 int32

View File

@ -555,6 +555,9 @@ func (r *randGen) generateCall(s *state, p *Prog, insertionPoint int) []*Call {
}
func (r *randGen) generateParticularCall(s *state, meta *Syscall) (calls []*Call) {
if meta.Attrs.Disabled {
panic(fmt.Sprintf("generating disabled call %v", meta.Name))
}
c := &Call{
Meta: meta,
Ret: MakeReturnArg(meta.Ret),

View File

@ -6,6 +6,7 @@ package prog
import (
"fmt"
"strings"
"unicode"
)
type Syscall struct {
@ -16,11 +17,29 @@ type Syscall struct {
MissingArgs int // number of trailing args that should be zero-filled
Args []Type
Ret Type
Attrs SyscallAttrs
inputResources []*ResourceDesc
outputResources []*ResourceDesc
}
// SyscallAttrs represents call attributes in syzlang.
//
// This structure is the source of truth for the all other parts of the system.
// pkg/compiler uses this structure to parse descriptions.
// syz-sysgen uses this structure to generate code for executor.
//
// Only bool's and uint64's are currently supported.
type SyscallAttrs struct {
// Never enable this system call in fuzzing.
Disabled bool
// Additional execution timeout (in ms) for the call on top of some default value.
Timeout uint64
// Additional execution timeout (in ms) for the whole program if it contains this call.
// If a program contains several such calls, the max value is used.
ProgTimeout uint64
}
// MaxArgs is maximum number of syscall arguments.
// Executor also knows about this value.
const MaxArgs = 9
@ -653,3 +672,16 @@ func ForeachType(meta *Syscall, f func(Type)) {
rec(meta.Ret)
}
}
// CppName transforms PascalStyleNames to cpp_style_names.
func CppName(name string) string {
var res []byte
for i := range name {
c := rune(name[i])
if unicode.IsUpper(c) && i != 0 && !unicode.IsUpper(rune(name[i-1])) {
res = append(res, '_')
}
res = append(res, byte(unicode.ToLower(c)))
}
return string(res)
}

View File

@ -12,6 +12,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"reflect"
"runtime"
"runtime/pprof"
"sort"
@ -37,6 +38,7 @@ type SyscallData struct {
CallName string
NR int32
NeedCall bool
Attrs []uint64
}
type ArchData struct {
@ -55,6 +57,11 @@ type OSData struct {
Archs []ArchData
}
type ExecutorData struct {
OSes []OSData
CallAttrs []string
}
func main() {
flag.Parse()
@ -64,7 +71,7 @@ func main() {
}
sort.Strings(OSList)
var oses []OSData
data := &ExecutorData{}
for _, OS := range OSList {
top := ast.ParseGlob(filepath.Join("sys", OS, "*.txt"), nil)
if top == nil {
@ -150,7 +157,7 @@ func main() {
unsupported[u]++
}
}
oses = append(oses, OSData{
data.OSes = append(data.OSes, OSData{
GOOS: OS,
Archs: syscallArchs,
})
@ -162,7 +169,12 @@ func main() {
}
}
writeExecutorSyscalls(oses)
attrs := reflect.TypeOf(prog.SyscallAttrs{})
for i := 0; i < attrs.NumField(); i++ {
data.CallAttrs = append(data.CallAttrs, prog.CppName(attrs.Field(i).Name))
}
writeExecutorSyscalls(data)
if *flagMemProfile != "" {
f, err := os.Create(*flagMemProfile)
@ -246,11 +258,33 @@ func generateExecutorSyscalls(target *targets.Target, syscalls []*prog.Syscall,
data.Shmem = 1
}
for _, c := range syscalls {
var attrVals []uint64
attrs := reflect.ValueOf(c.Attrs)
last := -1
for i := 0; i < attrs.NumField(); i++ {
attr := attrs.Field(i)
val := uint64(0)
switch attr.Type().Kind() {
case reflect.Bool:
if attr.Bool() {
val = 1
}
case reflect.Uint64:
val = attr.Uint()
default:
panic("unsupported syscall attribute type")
}
attrVals = append(attrVals, val)
if val != 0 {
last = i
}
}
data.Calls = append(data.Calls, SyscallData{
Name: c.Name,
CallName: c.CallName,
NR: int32(c.NR),
NeedCall: !target.SyscallNumbers || strings.HasPrefix(c.CallName, "syz_"),
Attrs: attrVals[:last+1],
})
}
sort.Slice(data.Calls, func(i, j int) bool {
@ -259,17 +293,17 @@ func generateExecutorSyscalls(target *targets.Target, syscalls []*prog.Syscall,
return data
}
func writeExecutorSyscalls(oses []OSData) {
sort.Slice(oses, func(i, j int) bool {
return oses[i].GOOS < oses[j].GOOS
func writeExecutorSyscalls(data *ExecutorData) {
sort.Slice(data.OSes, func(i, j int) bool {
return data.OSes[i].GOOS < data.OSes[j].GOOS
})
buf := new(bytes.Buffer)
if err := defsTempl.Execute(buf, oses); err != nil {
if err := defsTempl.Execute(buf, data); err != nil {
failf("failed to execute defs template: %v", err)
}
writeFile(filepath.FromSlash("executor/defs.h"), buf.Bytes())
buf.Reset()
if err := syscallsTempl.Execute(buf, oses); err != nil {
if err := syscallsTempl.Execute(buf, data); err != nil {
failf("failed to execute syscalls template: %v", err)
}
writeFile(filepath.FromSlash("executor/syscalls.h"), buf.Bytes())
@ -302,7 +336,11 @@ func failf(msg string, args ...interface{}) {
}
var defsTempl = template.Must(template.New("").Parse(`// AUTOGENERATED FILE
{{range $os := $}}
struct call_attrs_t { {{range $attr := $.CallAttrs}}
uint64_t {{$attr}};{{end}}
};
{{range $os := $.OSes}}
#if GOOS_{{$os.GOOS}}
#define GOOS "{{$os.GOOS}}"
{{range $arch := $os.Archs}}
@ -320,13 +358,14 @@ var defsTempl = template.Must(template.New("").Parse(`// AUTOGENERATED FILE
{{end}}
`))
// nolint: lll
var syscallsTempl = template.Must(template.New("").Parse(`// AUTOGENERATED FILE
{{range $os := $}}
{{range $os := $.OSes}}
#if GOOS_{{$os.GOOS}}
{{range $arch := $os.Archs}}
#if GOARCH_{{$arch.GOARCH}}
const call_t syscalls[] = {
{{range $c := $arch.Calls}} {"{{$c.Name}}", {{$c.NR}}{{if $c.NeedCall}}, (syscall_t){{$c.CallName}}{{end}}},
{{range $c := $arch.Calls}} {"{{$c.Name}}", {{$c.NR}}{{if or $c.Attrs $c.NeedCall}}, { {{range $attr := $c.Attrs}}{{$attr}},{{end}} }{{end}}{{if $c.NeedCall}}, (syscall_t){{$c.CallName}}{{end}}},
{{end}}
};
#endif