From 8136bdad2fe7e71d4c395ef362adcbc6b0c02251 Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Thu, 28 Sep 2017 16:51:53 +0200 Subject: pkg/kd: add KD protocol decoder Very primitive decoder that only decodes amd64 exceptions. Use it in vm/gce. Now crashes contain something more or less reasonable which is caught by manager as crash: BUG: first chance exception 0x80000003 &kd.stateChange64{state:0x3030, processorLevel:0x6, processor:0x0, numProcessors:0x2, thread:0xffff9c0bd015e080, pc:0xfffff8017615c380, exception:kd.exception64{code:0x80000003, flags:0x0, record:0x0, address:0xfffff8017615c380, numParams:0x1, unused:0x0, params:[15]uint64{ 0x0, 0x40, 0xfffff801768699e0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, firstChance:0x1}, report:kd.controlReport{ dr6:0xffff0ff0, dr7:0x400, eflags:0x86, numInstr:0x10, reportFlags:0x3, instr:[16]uint8{0xcc, 0xc3, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xf, 0x1f, 0x84, 0x0, 0x0, 0x0, 0x0, 0x0}, cs:0x10, ds:0x2b, es:0x2b, fs:0x53}} --- pkg/kd/kd.go | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++ pkg/kd/kd_test.go | 41 +++++++++++++++++++++ vm/gce/gce.go | 7 +++- vm/vmimpl/merger.go | 14 ++++++++ 4 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 pkg/kd/kd.go create mode 100644 pkg/kd/kd_test.go diff --git a/pkg/kd/kd.go b/pkg/kd/kd.go new file mode 100644 index 000000000..e700d9762 --- /dev/null +++ b/pkg/kd/kd.go @@ -0,0 +1,100 @@ +// Copyright 2017 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. + +// Minimal KD protocol decoder. +// KD protocol is used by windows to talk to debuggers. Here are some links: +// https://github.com/radare/radare2/issues/1246#issuecomment-135565901 +// http://articles.sysprogs.org/kdvmware/kdcom/ +// https://doxygen.reactos.org/df/de3/windbgkd_8h_source.html +package kd + +import ( + "bytes" + "fmt" + "unsafe" +) + +var ( + dataHeader = []byte{0x30, 0x30, 0x30, 0x30} +) + +const ( + typStateChange64 = 7 +) + +type packet struct { + header uint32 + typ uint16 + size uint16 + id uint32 + csum uint32 +} + +func Decode(data []byte) (start, size int, decoded []byte) { + if len(data) < len(dataHeader) { + return + } + start = bytes.Index(data, dataHeader) + if start == -1 { + start = len(data) - len(dataHeader) - 1 + return + } + packetSize := int(unsafe.Sizeof(packet{})) + if len(data)-start < packetSize { + return // incomplete header + } + // Note: assuming little-endian machine. + pkt := (*packet)(unsafe.Pointer(&data[start])) + if len(data)-start < packetSize+int(pkt.size) { + return // incomplete data + } + size = packetSize + int(pkt.size) // skip whole packet + if pkt.typ == typStateChange64 { + if int(pkt.size) < int(unsafe.Sizeof(stateChange64{})) { + return + } + payload := (*stateChange64)(unsafe.Pointer(&data[start+packetSize])) + chance := "second" + if payload.exception.firstChance != 0 { + chance = "first" + } + decoded = []byte(fmt.Sprintf("\n\nBUG: %v chance exception 0x%x\n\n%#v\n\n", + chance, payload.exception.code, payload)) + } + return +} + +type stateChange64 struct { + state uint32 + processorLevel uint16 + processor uint16 + numProcessors uint32 + thread uint64 + pc uint64 + exception exception64 + report controlReport +} + +type exception64 struct { + code uint32 + flags uint32 + record uint64 + address uint64 + numParams uint32 + unused uint32 + params [15]uint64 + firstChance uint32 +} + +type controlReport struct { + dr6 uint64 + dr7 uint64 + eflags uint32 + numInstr uint16 + reportFlags uint16 + instr [16]byte + cs uint16 + ds uint16 + es uint16 + fs uint16 +} diff --git a/pkg/kd/kd_test.go b/pkg/kd/kd_test.go new file mode 100644 index 000000000..e35a47272 --- /dev/null +++ b/pkg/kd/kd_test.go @@ -0,0 +1,41 @@ +// Copyright 2017 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 kd + +import ( + "testing" +) + +func TestCanned(t *testing.T) { + start, size, decoded := Decode(exceptionPacket) + if start != 0 || size != len(exceptionPacket) { + t.Fatalf("bad start/size %v/%v, want %v/%v", start, size, 0, len(exceptionPacket)) + } + t.Logf("%s", decoded) +} + +var exceptionPacket = []byte{ + 0x30, 0x30, 0x30, 0x30, 0x07, 0x00, 0xF0, 0x00, 0x00, 0x08, 0x80, 0x80, + 0xE6, 0x1F, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0x22, 0xBC, + 0x85, 0x8C, 0xFF, 0xFF, 0x80, 0x33, 0x5E, 0xC5, 0x02, 0xF8, 0xFF, 0xFF, + 0x03, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x33, 0x5E, 0xC5, 0x02, 0xF8, 0xFF, 0xFF, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xE0, 0x59, 0x46, 0xC5, 0x02, 0xF8, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xF0, 0x0F, 0xFE, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x03, 0x00, 0xCC, 0xC3, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, + 0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x2B, 0x00, + 0x2B, 0x00, 0x53, 0x00, +} diff --git a/vm/gce/gce.go b/vm/gce/gce.go index 59d33cbc3..a226b080d 100644 --- a/vm/gce/gce.go +++ b/vm/gce/gce.go @@ -26,6 +26,7 @@ import ( "github.com/google/syzkaller/pkg/config" "github.com/google/syzkaller/pkg/gce" "github.com/google/syzkaller/pkg/gcs" + "github.com/google/syzkaller/pkg/kd" . "github.com/google/syzkaller/pkg/log" "github.com/google/syzkaller/pkg/osutil" "github.com/google/syzkaller/vm/vmimpl" @@ -231,7 +232,11 @@ func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command strin tee = os.Stdout } merger := vmimpl.NewOutputMerger(tee) - merger.Add("console", conRpipe) + var decoder func(data []byte) (int, int, []byte) + if inst.env.OS == "windows" { + decoder = kd.Decode + } + merger.AddDecoder("console", conRpipe, decoder) // We've started the console reading ssh command, but it has not necessary connected yet. // If we proceed to running the target command right away, we can miss part diff --git a/vm/vmimpl/merger.go b/vm/vmimpl/merger.go index 17b837602..b189a40f0 100644 --- a/vm/vmimpl/merger.go +++ b/vm/vmimpl/merger.go @@ -32,13 +32,27 @@ func (merger *OutputMerger) Wait() { } func (merger *OutputMerger) Add(name string, r io.ReadCloser) { + merger.AddDecoder(name, r, nil) +} + +func (merger *OutputMerger) AddDecoder(name string, r io.ReadCloser, + decoder func(data []byte) (start, size int, decoded []byte)) { merger.wg.Add(1) go func() { var pending []byte + var proto []byte var buf [4 << 10]byte for { n, err := r.Read(buf[:]) if n != 0 { + if decoder != nil { + proto = append(proto, buf[:n]...) + start, size, decoded := decoder(proto) + proto = proto[start+size:] + if len(decoded) != 0 { + merger.Output <- decoded // note: this can block + } + } pending = append(pending, buf[:n]...) if pos := bytes.LastIndexByte(pending, '\n'); pos != -1 { out := pending[:pos+1] -- cgit mrf-deployment