diff options
| -rw-r--r-- | tools/syz-trace2syz/proggen/call_selector.go | 157 | ||||
| -rw-r--r-- | tools/syz-trace2syz/proggen/call_selector_test.go | 42 | ||||
| -rw-r--r-- | tools/syz-trace2syz/proggen/proggen.go | 21 | ||||
| -rw-r--r-- | tools/syz-trace2syz/proggen/proggen_test.go | 28 |
4 files changed, 224 insertions, 24 deletions
diff --git a/tools/syz-trace2syz/proggen/call_selector.go b/tools/syz-trace2syz/proggen/call_selector.go index 00f2e3ea9..989cbe6bd 100644 --- a/tools/syz-trace2syz/proggen/call_selector.go +++ b/tools/syz-trace2syz/proggen/call_selector.go @@ -1,8 +1,11 @@ package proggen import ( + "bytes" "github.com/google/syzkaller/prog" "github.com/google/syzkaller/tools/syz-trace2syz/parser" + "strconv" + "unicode" ) var discriminatorArgs = map[string][]int{ @@ -22,40 +25,70 @@ var discriminatorArgs = map[string][]int{ "sendto": {0}, "sendmsg": {0}, "getsockname": {0}, + "openat": {1}, } -type callSelector struct { +var openDiscriminatorArgs = map[string]int{ + "open": 0, + "openat": 1, + "syz_open_dev": 0, +} + +type callSelector interface { + Select(call *parser.Syscall) *prog.Syscall +} + +func newSelectors(target *prog.Target, returnCache returnCache) []callSelector { + sc := newSelectorCommon(target, returnCache) + return []callSelector{ + &defaultCallSelector{sc}, + &openCallSelector{sc}, + } +} + +type selectorCommon struct { target *prog.Target returnCache returnCache callCache map[string][]*prog.Syscall } -func newCallSelector(target *prog.Target, returnCache returnCache) *callSelector { - return &callSelector{ +func newSelectorCommon(target *prog.Target, returnCache returnCache) *selectorCommon { + return &selectorCommon{ target: target, returnCache: returnCache, callCache: make(map[string][]*prog.Syscall), } } -// Select returns the best matching descrimination for this syscall. -func (cs *callSelector) Select(call *parser.Syscall) *prog.Syscall { - match := cs.target.SyscallMap[call.CallName] - discriminators := discriminatorArgs[call.CallName] - if len(discriminators) == 0 { - return match +// matches strace file string with a constant string in openat or syz_open_dev +// if the string in openat or syz_open_dev has a # then this method will +// return the corresponding id from the strace string +func (cs *selectorCommon) matchFilename(syzFile, straceFile []byte) (bool, int) { + syzFile = bytes.Trim(syzFile, "\x00") + straceFile = bytes.Trim(straceFile, "\x00") + if len(syzFile) != len(straceFile) { + return false, -1 } - score := 0 - for _, meta := range cs.callSet(call.CallName) { - if score1 := cs.matchCall(meta, call, discriminators); score1 > score { - match, score = meta, score1 + var id []byte + dev := -1 + for i, c := range syzFile { + x := straceFile[i] + if c == x { + continue } + if c != '#' || !unicode.IsDigit(rune(x)) { + return false, -1 + } + id = append(id, x) } - return match + if len(id) > 0 { + dev, _ = strconv.Atoi(string(id)) + } + return true, dev } // callSet returns all syscalls with the given name. -func (cs *callSelector) callSet(callName string) []*prog.Syscall { +func (cs *selectorCommon) callSet(callName string) []*prog.Syscall { calls, ok := cs.callCache[callName] if ok { return calls @@ -69,9 +102,79 @@ func (cs *callSelector) callSet(callName string) []*prog.Syscall { return calls } +type openCallSelector struct { + *selectorCommon +} + +// Select returns the best matching descrimination for this syscall. +func (cs *openCallSelector) Select(call *parser.Syscall) *prog.Syscall { + if _, ok := openDiscriminatorArgs[call.CallName]; !ok { + return nil + } + for callName := range openDiscriminatorArgs { + for _, variant := range cs.callSet(callName) { + match, devID := cs.matchOpen(variant, call) + if !match { + continue + } + if call.CallName == "open" && callName == "openat" { + cwd := parser.Constant(cs.target.ConstMap["AT_FDCWD"]) + call.Args = append([]parser.IrType{cwd}, call.Args...) + return variant + } + if match && call.CallName == "open" && callName == "syz_open_dev" { + if devID < 0 { + return variant + } + args := []parser.IrType{call.Args[0], parser.Constant(uint64(devID))} + call.Args = append(args, call.Args[1:]...) + return variant + } + } + } + return nil +} + +func (cs *openCallSelector) matchOpen(meta *prog.Syscall, call *parser.Syscall) (bool, int) { + straceFileArg := call.Args[openDiscriminatorArgs[call.CallName]] + syzFileArg := meta.Args[openDiscriminatorArgs[meta.CallName]] + straceBuf := straceFileArg.(*parser.BufferType).Val + syzBuf := syzFileArg.(*prog.PtrType).Type.(*prog.BufferType) + if syzBuf.Kind != prog.BufferString { + return false, -1 + } + for _, val := range syzBuf.Values { + match, devID := cs.matchFilename([]byte(val), []byte(straceBuf)) + if match { + return match, devID + } + } + return false, -1 +} + +type defaultCallSelector struct { + *selectorCommon +} + +// Select returns the best matching descrimination for this syscall. +func (cs *defaultCallSelector) Select(call *parser.Syscall) *prog.Syscall { + var match *prog.Syscall + discriminators := discriminatorArgs[call.CallName] + if len(discriminators) == 0 { + return nil + } + score := 0 + for _, meta := range cs.callSet(call.CallName) { + if score1 := cs.matchCall(meta, call, discriminators); score1 > score { + match, score = meta, score1 + } + } + return match +} + // matchCall returns match score between meta and call. // Higher score means better match, -1 if they are not matching at all. -func (cs *callSelector) matchCall(meta *prog.Syscall, call *parser.Syscall, discriminators []int) int { +func (cs *defaultCallSelector) matchCall(meta *prog.Syscall, call *parser.Syscall, discriminators []int) int { score := 0 for _, i := range discriminators { if i >= len(meta.Args) || i >= len(call.Args) { @@ -118,6 +221,28 @@ func (cs *callSelector) matchCall(meta *prog.Syscall, call *parser.Syscall, disc if !matched { return -1 } + case *prog.PtrType: + switch r := t.Type.(type) { + case *prog.BufferType: + matched := false + buffer, ok := arg.(*parser.BufferType) + if !ok { + return -1 + } + if r.Kind != prog.BufferString { + return -1 + } + for _, val := range r.Values { + matched, _ = cs.matchFilename([]byte(val), []byte(buffer.Val)) + if matched { + score++ + break + } + } + if !matched { + return -1 + } + } } } return score diff --git a/tools/syz-trace2syz/proggen/call_selector_test.go b/tools/syz-trace2syz/proggen/call_selector_test.go new file mode 100644 index 000000000..ca00d8362 --- /dev/null +++ b/tools/syz-trace2syz/proggen/call_selector_test.go @@ -0,0 +1,42 @@ +// Copyright 2019 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +package proggen + +import "testing" + +func TestMatchFilename(t *testing.T) { + sc := selectorCommon{} + type Test struct { + file1 string + file2 string + devID int + match bool + } + tests := []Test{ + { + "/dev/zero", "/dev/zero", -1, true, + }, { + "/dev/loop#", "/dev/loop1", 1, true, + }, { + "", "a", -1, false, + }, { + "/dev/loop#/loop", "/dev/loop0/looq", -1, false, + }, { + "/dev/i2c-#\x00", "/dev/i2c-1", 1, true, + }, { + "/dev/some#/some#", "/dev/some1/some1", 11, true, + }, { + "/dev/some/some#", "/dev/some", -1, false, + }, { + "/dev/some", "/dev/some/some", -1, false, + }, + } + for _, test := range tests { + match, devID := sc.matchFilename([]byte(test.file1), []byte(test.file2)) + if test.match != match || test.devID != devID { + t.Errorf("failed to match %s and %s\nexpected: %t, %d\n\ngot: %t, %d\n", + test.file1, test.file2, test.match, test.devID, match, devID) + } + } +} diff --git a/tools/syz-trace2syz/proggen/proggen.go b/tools/syz-trace2syz/proggen/proggen.go index f1adf43c3..6d9ac48f5 100644 --- a/tools/syz-trace2syz/proggen/proggen.go +++ b/tools/syz-trace2syz/proggen/proggen.go @@ -53,7 +53,7 @@ func parseTree(tree *parser.TraceTree, pid int64, target *prog.Target, progs *[] type context struct { builder *prog.Builder target *prog.Target - callSelector *callSelector + selectors []callSelector returnCache returnCache currentStraceCall *parser.Syscall currentSyzCall *prog.Call @@ -63,10 +63,10 @@ type context struct { func genProg(trace *parser.Trace, target *prog.Target) *prog.Prog { retCache := newRCache() ctx := &context{ - builder: prog.MakeProgGen(target), - target: target, - callSelector: newCallSelector(target, retCache), - returnCache: retCache, + builder: prog.MakeProgGen(target), + target: target, + selectors: newSelectors(target, retCache), + returnCache: retCache, } for _, sCall := range trace.Calls { if sCall.Paused { @@ -100,7 +100,7 @@ func (ctx *context) genCall() *prog.Call { log.Logf(3, "parsing call: %s", ctx.currentStraceCall.CallName) straceCall := ctx.currentStraceCall ctx.currentSyzCall = new(prog.Call) - ctx.currentSyzCall.Meta = ctx.callSelector.Select(straceCall) + ctx.currentSyzCall.Meta = ctx.Select(straceCall) syzCall := ctx.currentSyzCall if ctx.currentSyzCall.Meta == nil { log.Logf(2, "skipping call: %s which has no matching description", ctx.currentStraceCall.CallName) @@ -120,6 +120,15 @@ func (ctx *context) genCall() *prog.Call { return syzCall } +func (ctx *context) Select(syscall *parser.Syscall) *prog.Syscall { + for _, selector := range ctx.selectors { + if variant := selector.Select(syscall); variant != nil { + return variant + } + } + return ctx.target.SyscallMap[syscall.CallName] +} + func (ctx *context) genResult(syzType prog.Type, straceRet int64) { if straceRet <= 0 { return diff --git a/tools/syz-trace2syz/proggen/proggen_test.go b/tools/syz-trace2syz/proggen/proggen_test.go index 5074bd194..f60495d2c 100644 --- a/tools/syz-trace2syz/proggen/proggen_test.go +++ b/tools/syz-trace2syz/proggen/proggen_test.go @@ -160,7 +160,7 @@ connect$inet(r0, &(0x7f0000000000)={0x2, 0x4594}, 0x10) open("\x2f\x64\x65\x76\x2f\x73\x6e\x64\x2f\x73\x65\x71", 0) = 3 fsetxattr(3, "\x73\x65\x63\x75\x72\x69\x74\x79\x2e\x73\x65\x6c\x69\x6e\x75\x78","\x73\x79\x73", 4, 0) = 0 `, ` -r0 = open(&(0x7f0000000000)='/dev/snd/seq\x00', 0x0, 0x0) +r0 = syz_open_dev$sndseq(&(0x7f0000000000)='/dev/snd/seq\x00', 0x0, 0x0) fsetxattr(r0, &(0x7f0000000040)=@known='security.selinux\x00', &(0x7f0000000080)='sys\x00', 0x4, 0x0) `, }, {` @@ -220,7 +220,31 @@ sendto(3, "", 0, 0, {sa_family=0xa, sin6_port="\x4e\x24", sin6_flowinfo="\x00\x0 r0 = socket$inet6_udp(0xa, 0x2, 0x0) sendto$inet6(r0, &(0x7f0000000000), 0x0, 0x0, &(0x7f0000000040)={0xa, 0x4e24}, 0x1c) `, - }, + }, {` +open("\x2f\x64\x65\x76\x2f\x7a\x65\x72\x6f", "1") = 3 +`, ` +openat$zero(0xffffffffffffff9c, &(0x7f0000000000)='/dev/zero\x00', 0x31, 0x0) +`, + }, {` +open("\x2f\x64\x65\x76\x2f\x6c\x6f\x6f\x70\x30", 0) = 3 +`, ` +syz_open_dev$loop(&(0x7f0000000000)='/dev/loop0\x00', 0x0, 0x0) +`, + }, {` +open("\x2f\x64\x65\x76\x2f\x6c\x6f\x6f\x70\x31", 0) = 3 +`, ` +syz_open_dev$loop(&(0x7f0000000000)='/dev/loop1\x00', 0x1, 0x0) +`, + }, {` +open("\x2f\x64\x65\x76\x2f\x62\x75\x73\x2f\x75\x73\x62\x2f\x30\x30\x31\x2f\x30\x30\x31", 0) = 3 +`, ` +syz_open_dev$usb(&(0x7f0000000000)='/dev/bus/usb/001/001\x00', 0xb, 0x0) +`, + }, {` +openat(0xffffffffffffff9c, "\x2f\x64\x65\x76\x2f\x7a\x65\x72\x6f", 0x31, 0) = 3 +`, ` +openat$zero(0xffffffffffffff9c, &(0x7f0000000000)='/dev/zero\x00', 0x31, 0x0) +`}, } target, err := prog.GetTarget("linux", "amd64") if err != nil { |
