From 3da9017c17c7d2432c4b76345c4d2efbeedd2935 Mon Sep 17 00:00:00 2001 From: Joey Jiaojg Date: Wed, 26 May 2021 19:38:04 +0800 Subject: pkg/compiler: add glob type * all: add new typename dirname The current way to check files under sysfs or proc is: - define a string to represent each file - open the file - pass the fd to write / read / close The issues above are: - Need to know what file present on target device - Need to write openat for each file With dirname added, which will open one file in the directory randomly and then pass the fd to write/read/close. * all: use typename glob to match filename Fixes #481 --- docs/syscall_descriptions_syntax.md | 4 +++- pkg/compiler/testdata/all.txt | 3 +++ pkg/compiler/testdata/errors.txt | 7 ++++++- pkg/compiler/types.go | 24 +++++++++++++++++++++++- pkg/host/machine_info.go | 8 ++++++++ pkg/host/machine_info_linux.go | 14 ++++++++++++++ prog/encoding.go | 2 +- prog/hints.go | 2 +- prog/mutation.go | 6 ++++++ prog/prio.go | 2 +- prog/rand.go | 2 ++ prog/target.go | 25 +++++++++++++++++++++++++ prog/types.go | 1 + sys/linux/sys.txt | 2 ++ syz-fuzzer/fuzzer.go | 32 ++++++++++++++++++++++++-------- 15 files changed, 120 insertions(+), 14 deletions(-) diff --git a/docs/syscall_descriptions_syntax.md b/docs/syscall_descriptions_syntax.md index 76cdc90b1..4517963eb 100644 --- a/docs/syscall_descriptions_syntax.md +++ b/docs/syscall_descriptions_syntax.md @@ -9,7 +9,7 @@ arg = argname type argname = identifier type = typename [ "[" type-options "]" ] typename = "const" | "intN" | "intptr" | "flags" | "array" | "ptr" | - "string" | "strconst" | "filename" | "len" | + "string" | "strconst" | "filename" | "glob" | "len" | "bytesize" | "bytesizeN" | "bitsize" | "vma" | "proc" type-options = [type-opt ["," type-opt]] ``` @@ -42,6 +42,8 @@ rest of the type-options are type-specific: "stringnoz": a non-zero-terminated memory buffer (no pointer indirection implied), type-options: either a string value in quotes for constant strings (e.g. "foo" or `deadbeef` for hex literal), or a reference to string flags, +"glob": glob pattern to match on target files, type-options: + a zero-terminated glob pattern string in quotes (e.g. "/sys/" or `/sys/**/*` for glob match), "fmt": a string representation of an integer (not zero-terminated), type-options: format (one of "dec", "hex", "oct") and the value (a resource, int, flags, const or proc) the resulting data is always fixed-size (formatted as "%020llu", "0x%016llx" or "%023llo", respectively) diff --git a/pkg/compiler/testdata/all.txt b/pkg/compiler/testdata/all.txt index 17b7c838d..b1adf2321 100644 --- a/pkg/compiler/testdata/all.txt +++ b/pkg/compiler/testdata/all.txt @@ -252,6 +252,9 @@ foo_templ9(a ptr[in, templ_base3[int64]]) foo_templ10(a ptr[in, templ_base4[int8]]) foo_templ11(a ptr[in, templ_base5[42, int8]]) +foo_glob0(a ptr[in, glob["/sys/"]]) +foo_glob1(a ptr[in, glob["/sys/**/*"]]) + # Structs. s0 { diff --git a/pkg/compiler/testdata/errors.txt b/pkg/compiler/testdata/errors.txt index d3658d239..670cc0193 100644 --- a/pkg/compiler/testdata/errors.txt +++ b/pkg/compiler/testdata/errors.txt @@ -334,6 +334,11 @@ foo$210(a ptr[in, templ11[0, 1, int8]]) ### template templ11 needs 2 arguments foo$211(a ptr[in, templ9]) ### template templ9 needs 1 arguments instead of 0 foo$212(a ptr[in, templ11[1]]) ### template templ11 needs 2 arguments instead of 1 +foo$glob001(a ptr[in, glob[1]]) ### unexpected int 1, string arg must be a string literal or string flags +foo$glob002(a ptr[in, glob]) ### glob only accepts 1 arg, provided 0 +foo$glob003(a ptr[in, glob["/sys", 5]]) ### glob only accepts 1 arg, provided 2 +foo$glob004(a ptr[in, glob["/sys", 5, 2]]) ### wrong number of arguments for type glob, expect [literal or flags], [size], [opt] + # fmt foo$fmt0(a ptr[in, fmt]) ### wrong number of arguments for type fmt, expect format, value @@ -355,4 +360,4 @@ struct$perfielddir { f4 int32 (in, inout) ### arg/field has multiple direction attributes f5 int32 (out, inout) ### arg/field has multiple direction attributes f6 int32 (in, out, inout) ### arg/field has multiple direction attributes -} \ No newline at end of file +} diff --git a/pkg/compiler/types.go b/pkg/compiler/types.go index 038a05f24..d62c8fd48 100644 --- a/pkg/compiler/types.go +++ b/pkg/compiler/types.go @@ -598,10 +598,11 @@ func genTextType(t *ast.Type) prog.TextKind { const ( stringnoz = "stringnoz" + glob = "glob" ) var typeString = &typeDesc{ - Names: []string{"string", stringnoz}, + Names: []string{"string", glob, stringnoz}, CanBeTypedef: true, OptArgs: 2, Args: []namedArg{ @@ -612,6 +613,15 @@ var typeString = &typeDesc{ if t.Ident == stringnoz && len(args) > 1 { comp.error(args[0].Pos, "fixed-size string can't be non-zero-terminated") } + if t.Ident == glob { + pos := t.Pos + if len(args) > 0 { + pos = args[0].Pos + } + if len(args) != 1 { + comp.error(pos, "glob only accepts 1 arg, provided %v", len(args)) + } + } }, CheckConsts: func(comp *compiler, t *ast.Type, args []*ast.Type, base prog.IntTypeCommon) { if len(args) > 1 { @@ -626,6 +636,9 @@ var typeString = &typeDesc{ } }, Varlen: func(comp *compiler, t *ast.Type, args []*ast.Type) bool { + if t.Ident == glob { + return true + } return comp.stringSize(t, args) == varlenString }, ZeroSize: func(comp *compiler, t *ast.Type, args []*ast.Type) bool { @@ -645,6 +658,15 @@ var typeString = &typeDesc{ NoZ: t.Ident == stringnoz, } } + if len(args) > 0 && t.Ident == glob { + base.TypeSize = 0 + return &prog.BufferType{ + TypeCommon: base.TypeCommon, + Kind: prog.BufferGlob, + SubKind: args[0].String, + NoZ: false, + } + } subkind := "" if len(args) > 0 && args[0].Ident != "" { subkind = args[0].Ident diff --git a/pkg/host/machine_info.go b/pkg/host/machine_info.go index a804ca32a..167b7ec03 100644 --- a/pkg/host/machine_info.go +++ b/pkg/host/machine_info.go @@ -38,8 +38,16 @@ func CollectModulesInfo() ([]KernelModule, error) { return machineModulesInfo() } +func CollectGlobsInfo(globs []string) (map[string][]string, error) { + if machineModulesInfo == nil { + return nil, nil + } + return machineGlobsInfo(globs) +} + var machineInfoFuncs []machineInfoFunc var machineModulesInfo func() ([]KernelModule, error) +var machineGlobsInfo func([]string) (map[string][]string, error) type machineInfoFunc struct { name string diff --git a/pkg/host/machine_info_linux.go b/pkg/host/machine_info_linux.go index 317d34cd9..3b0971495 100644 --- a/pkg/host/machine_info_linux.go +++ b/pkg/host/machine_info_linux.go @@ -21,6 +21,7 @@ func init() { {"KVM", readKVMInfo}, } machineModulesInfo = getModulesInfo + machineGlobsInfo = getGlobsInfo } func readCPUInfo(buffer *bytes.Buffer) error { @@ -140,3 +141,16 @@ func getModulesInfo() ([]KernelModule, error) { } return modules, nil } + +func getGlobsInfo(globs []string) (map[string][]string, error) { + files := make(map[string][]string, len(globs)) + for _, glob := range globs { + matches, err := filepath.Glob(glob) + if err != nil { + return nil, err + } + files[glob] = matches + } + + return files, nil +} diff --git a/prog/encoding.go b/prog/encoding.go index 140636865..e66535e2a 100644 --- a/prog/encoding.go +++ b/prog/encoding.go @@ -853,7 +853,7 @@ func encodeData(buf *bytes.Buffer, data []byte, readable, cstr bool) { } func isReadableDataType(typ *BufferType) bool { - return typ.Kind == BufferString || typ.Kind == BufferFilename + return typ.Kind == BufferString || typ.Kind == BufferFilename || typ.Kind == BufferGlob } func isReadableData(data []byte) bool { diff --git a/prog/hints.go b/prog/hints.go index a26185010..c66242357 100644 --- a/prog/hints.go +++ b/prog/hints.go @@ -102,7 +102,7 @@ func generateHints(compMap CompMap, arg Arg, exec func()) { case BufferFilename: // This can generate escaping paths and is probably not too useful anyway. return - case BufferString: + case BufferString, BufferGlob: if len(t.Values) != 0 { // These are frequently file names or complete enumerations. // Mutating these may be useful iff we intercept strcmp diff --git a/prog/mutation.go b/prog/mutation.go index af6a458f0..9d6dc2118 100644 --- a/prog/mutation.go +++ b/prog/mutation.go @@ -349,6 +349,12 @@ func (t *BufferType) mutate(r *randGen, s *state, arg Arg, ctx ArgCtx) (calls [] } case BufferFilename: a.data = []byte(r.filename(s, t)) + case BufferGlob: + if len(t.Values) != 0 { + a.data = r.randString(s, t) + } else { + a.data = []byte(r.filename(s, t)) + } case BufferText: data := append([]byte{}, a.Data()...) a.data = r.mutateText(t.Text, data) diff --git a/prog/prio.go b/prog/prio.go index 2c3436151..4cf3daf90 100644 --- a/prog/prio.go +++ b/prog/prio.go @@ -97,7 +97,7 @@ func (target *Target) calcResourceUsage() map[string]map[int]weights { case *BufferType: switch a.Kind { case BufferBlobRand, BufferBlobRange, BufferText: - case BufferString: + case BufferString, BufferGlob: if a.SubKind != "" { noteUsage(uses, c, 2, ctx.Dir, fmt.Sprintf("str-%v", a.SubKind)) } diff --git a/prog/rand.go b/prog/rand.go index a78f7e343..7ab5068cb 100644 --- a/prog/rand.go +++ b/prog/rand.go @@ -734,6 +734,8 @@ func (a *BufferType) generate(r *randGen, s *state, dir Dir) (arg Arg, calls []* return MakeOutDataArg(a, dir, sz), nil } return MakeDataArg(a, dir, []byte(r.filename(s, a))), nil + case BufferGlob: + return MakeDataArg(a, dir, r.randString(s, a)), nil case BufferText: if dir == DirOut { return MakeOutDataArg(a, dir, uint64(r.Intn(100))), nil diff --git a/prog/target.go b/prog/target.go index f2eaf6ead..974880c22 100644 --- a/prog/target.go +++ b/prog/target.go @@ -227,6 +227,31 @@ func (target *Target) DefaultChoiceTable() *ChoiceTable { return target.defaultChoiceTable } +func (target *Target) GetGlobs() []string { + var globs []string + ForeachType(target.Syscalls, func(typ Type, ctx TypeCtx) { + switch a := typ.(type) { + case *BufferType: + if a.Kind == BufferGlob && a.SubKind != "" { + globs = append(globs, a.SubKind) + } + } + }) + + return globs +} + +func (target *Target) UpdateGlobFilesForType(globFiles map[string][]string) { + ForeachType(target.Syscalls, func(typ Type, ctx TypeCtx) { + switch a := typ.(type) { + case *BufferType: + if a.Kind == BufferGlob && a.SubKind != "" { + a.Values = globFiles[a.SubKind] + } + } + }) +} + type Gen struct { r *randGen s *state diff --git a/prog/types.go b/prog/types.go index fb680f0a1..66ce3ced0 100644 --- a/prog/types.go +++ b/prog/types.go @@ -481,6 +481,7 @@ const ( BufferString BufferFilename BufferText + BufferGlob ) type TextKind int diff --git a/sys/linux/sys.txt b/sys/linux/sys.txt index c2725bfe7..ab39e6ded 100644 --- a/sys/linux/sys.txt +++ b/sys/linux/sys.txt @@ -78,6 +78,8 @@ type signalnoptr intptr[0:65] syz_execute_func(text ptr[in, text[target]]) +openat$sysfs(fd const[AT_FDCWD], dir ptr[in, glob["/sys/**/*"]], flags flags[open_flags], mode flags[open_mode]) fd + open(file ptr[in, filename], flags flags[open_flags], mode flags[open_mode]) fd # Just so that we have something that creates fd_dir resources. open$dir(file ptr[in, filename], flags flags[open_flags], mode flags[open_mode]) fd_dir diff --git a/syz-fuzzer/fuzzer.go b/syz-fuzzer/fuzzer.go index 4e8c1bb93..9fccaf5c8 100644 --- a/syz-fuzzer/fuzzer.go +++ b/syz-fuzzer/fuzzer.go @@ -179,14 +179,7 @@ func main() { return } - machineInfo, err := host.CollectMachineInfo() - if err != nil { - log.Fatalf("failed to collect machine information: %v", err) - } - modules, err := host.CollectModulesInfo() - if err != nil { - log.Fatalf("failed to collect modules info: %v", err) - } + machineInfo, modules := collectMachineInfos(target) log.Logf(0, "dialing manager at %v", *flagManager) manager, err := rpctype.NewRPCClient(*flagManager, timeouts.Scale) @@ -298,6 +291,29 @@ func main() { fuzzer.pollLoop() } +func collectMachineInfos(target *prog.Target) ([]byte, []host.KernelModule) { + machineInfo, err := host.CollectMachineInfo() + if err != nil { + log.Fatalf("failed to collect machine information: %v", err) + } + modules, err := host.CollectModulesInfo() + if err != nil { + log.Fatalf("failed to collect modules info: %v", err) + } + + var globFiles map[string][]string + globs := target.GetGlobs() + if len(globs) != 0 { + globFiles, err = host.CollectGlobsInfo(globs) + if err != nil { + log.Fatalf("faield to collect globFiles info: %v", err) + } + target.UpdateGlobFilesForType(globFiles) + } + + return machineInfo, modules +} + // Returns gateCallback for leak checking if enabled. func (fuzzer *Fuzzer) useBugFrames(r *rpctype.ConnectRes, flagProcs int) func() { var gateCallback func() -- cgit mrf-deployment