├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── README.md ├── exec ├── execcomm.go ├── master.go ├── memtrace.go └── slave.go ├── executor ├── clone.h ├── comm.h ├── common.h ├── common_linux.h ├── exec_sync.h ├── executor.cc └── executor_linux.h ├── executorcd └── executorcd.go ├── executord └── executord.go ├── go.mod ├── go.sum ├── manager ├── manager.go └── rpc.go ├── ns ├── analysis.go ├── analysis_test.go └── spec.go ├── old_bugs ├── multicast │ ├── .config │ ├── env.json │ └── multicast.c ├── nf_contrack │ ├── .config │ ├── env.json │ └── nf_contrack.c ├── pneigh │ ├── .config │ ├── env.json │ └── pneigh.c ├── pptp │ ├── .config │ ├── env.json │ └── pptp.c ├── priority │ ├── .config │ ├── env.json │ └── priority.c ├── sock_diag │ ├── .config │ ├── env.json │ └── sock_diag.c └── uevent │ ├── .config │ ├── env.json │ └── uevent.c ├── pgen ├── filegen.go ├── gen.go ├── memgen.go ├── pmcgen.go └── reprogen.go ├── prerequisite ├── compile │ └── install.sh ├── go │ ├── env.sh │ └── install.sh ├── setup.sh ├── syzkaller │ ├── patch.sh │ └── syzkaller.patch └── vscode_config │ └── install.sh ├── result └── result.go ├── script ├── common.sh ├── qemu │ ├── connect.sh │ └── run.sh └── test │ └── create-image.sh ├── testsuite └── image │ └── create-image.sh ├── tools ├── memtracedump │ └── memtracedump.go ├── pmcpc │ └── pmcpc.go ├── pmcrand │ └── pmcrand.go ├── predstat │ └── predstat.go └── resanalyze │ └── resanalyze.go ├── trace ├── sctrace.go └── trace.go ├── util ├── err.go └── serialize.go └── vm ├── comm └── comm.go ├── qemu ├── qemu.go ├── qmp.go ├── snapshot.go └── virtio.go ├── vm.go └── vmimpl ├── console.go ├── console_linux_386.go ├── console_linux_amd64.go ├── linux.go ├── merger.go ├── merger_test.go ├── util.go └── vmimpl.go /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | 3 | bin/ 4 | vendor/ 5 | test/ 6 | 7 | __pycache__/ 8 | 9 | executor/*.h 10 | !executor/common.h 11 | !executor/common_linux.h 12 | !executor/executor_linux.h 13 | !executor/clone.h 14 | !executor/exec_sync.h 15 | executor/android 16 | 17 | prerequisite/go/* -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "syzkaller"] 2 | path = syzkaller 3 | url = git@github.com:google/syzkaller.git 4 | branch = master 5 | [submodule "executor/libsclog"] 6 | path = executor/libsclog 7 | url = git@github.com:liucy1998/libsclog.git 8 | [submodule "testsuite/kernel-memory-acccess-tracing"] 9 | path = testsuite/kernel-memory-acccess-tracing 10 | url = git@github.com:rssys/kernel-memory-acccess-tracing.git 11 | branch = master 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BUILDOS := $(shell go env GOOS) 2 | BUILDARCH := $(shell go env GOARCH) 3 | HOSTOS ?= $(BUILDOS) 4 | HOSTARCH ?= $(BUILDARCH) 5 | TARGETOS ?= $(HOSTOS) 6 | TARGETARCH ?= $(HOSTARCH) 7 | TARGETVMARCH ?= $(TARGETARCH) 8 | 9 | GO := go 10 | GOFLAGS := "-ldflags=-s -w" 11 | GOHOSTFLAGS := $(GOFLAGS) 12 | GOTARGETFLAGS := $(GOFLAGS) 13 | 14 | LIBSCLOGFLAGS := -lrt -lselinux -lsepol 15 | 16 | .PHONY: all clean syzkaller executord executor manager 17 | 18 | all: syzkaller executord executor executorcd manager resanalyze pmcpc memtracedump pmcrand predstat 19 | 20 | syzkaller: 21 | $(MAKE) -C ./syzkaller executor 22 | 23 | executord: syzkaller 24 | mkdir -p ./bin 25 | GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) $(GO) build $(GOTARGETFLAGS) -o ./bin/executord github.com/rss/kit/executord 26 | 27 | # compile flags are copied from syzkaller Makefile 28 | executor: syzkaller 29 | cp ./syzkaller/executor/syscalls.h ./executor 30 | cp ./syzkaller/executor/defs.h ./executor 31 | cp ./syzkaller/executor/common_usb_linux.h ./executor 32 | cp ./syzkaller/executor/common_usb.h ./executor 33 | cp ./syzkaller/executor/kvm.h ./executor 34 | cp ./syzkaller/executor/common_kvm_amd64.h ./executor 35 | cp ./syzkaller/executor/kvm_amd64.S.h ./executor 36 | cp -r ./syzkaller/executor/android ./executor 37 | cp ./syzkaller/executor/cov_filter.h ./executor 38 | cp ./syzkaller/executor/test.h ./executor 39 | cp ./syzkaller/executor/test_linux.h ./executor 40 | $(CC) -o ./bin/executor ./executor/executor.cc executor/libsclog/libsclog.a \ 41 | -m64 -O2 -pthread -Wall -Werror -Wparentheses -Wunused-const-variable -Wframe-larger-than=16384 -static-pie \ 42 | -DGOOS_$(TARGETOS)=1 -DGOARCH_$(TARGETARCH)=1 \ 43 | -DHOSTGOOS_$(HOSTOS)=1 \ 44 | $(LIBSCLOGFLAGS) 45 | 46 | manager: syzkaller 47 | GOOS=$(HOSTOS) GOARCH=$(HOSTARCH) $(GO) build $(GOHOSTFLAGS) -o ./bin/manager github.com/rss/kit/manager 48 | 49 | pmcpc: syzkaller 50 | GOOS=$(HOSTOS) GOARCH=$(HOSTARCH) $(GO) build $(GOHOSTFLAGS) -o ./bin/pmcpc github.com/rss/kit/tools/pmcpc 51 | 52 | pmcrand: syzkaller 53 | GOOS=$(HOSTOS) GOARCH=$(HOSTARCH) $(GO) build $(GOHOSTFLAGS) -o ./bin/pmcrand github.com/rss/kit/tools/pmcrand 54 | 55 | resanalyze: syzkaller 56 | GOOS=$(HOSTOS) GOARCH=$(HOSTARCH) $(GO) build $(GOHOSTFLAGS) -o ./bin/resanalyze github.com/rss/kit/tools/resanalyze 57 | 58 | memtracedump: syzkaller 59 | GOOS=$(HOSTOS) GOARCH=$(HOSTARCH) $(GO) build $(GOHOSTFLAGS) -o ./bin/memtracedump github.com/rss/kit/tools/memtracedump 60 | 61 | predstat: syzkaller 62 | GOOS=$(HOSTOS) GOARCH=$(HOSTARCH) $(GO) build $(GOHOSTFLAGS) -o ./bin/predstat github.com/rss/kit/tools/predstat 63 | 64 | executorcd: syzkaller 65 | GOOS=$(HOSTOS) GOARCH=$(HOSTARCH) $(GO) build $(GOHOSTFLAGS) -o ./bin/executorcd github.com/rss/kit/executorcd 66 | 67 | clean: 68 | $(MAKE) -C ./syzkaller clean 69 | rm -rf ./bin 70 | rm ./executor/syscalls.h 71 | rm ./executor/defs.h 72 | rm ./executor/common_usb.h 73 | rm ./executor/common_usb_linux.h 74 | rm ./executor/common_kvm_amd64.h 75 | rm ./executor/kvm.h 76 | rm ./executor/kvm_amd64.S.h 77 | rm -r ./executor/android 78 | rm ./executor/cov_filter.h 79 | rm ./executor/test.h 80 | rm ./executor/test_linux.h 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kernel Isolation Tester 2 | 3 | KIT is a dynamic testing framework that discovers functional interference bugs in Linux containers. 4 | 5 | ## Getting started 6 | 7 | ### Prerequisite 8 | 9 | **Hardware**: x86-64 CPU (more cores are better); >=128GB memory; >=256GB storage (bare-metal machine recommended); 10 | 11 | **Software**: Linux systems with QEMU and KVM support (Ubuntu 22.04 recommended); 12 | 13 | KIT only needs root privilege for installing some dependencies and create VM images. 14 | 15 | ### Download 16 | 17 | You can download the code via: 18 | 19 | ```bash 20 | git clone --recurse-submodules git@github.com:rssys/kit.git 21 | ``` 22 | 23 | ### Build 24 | 25 | Run following commands to install dependencies: 26 | 27 | ```shell 28 | # Build KIT 29 | sudo apt-get install make build-essential automake autoconf gcc-multilib g++-multilib libselinux1-dev libselinux1 libsepol-dev libsepol2 30 | # Build customized gcc 31 | sudo apt-get install flex bison 32 | # Build kernel 33 | sudo apt-get install libelf-dev libssl-dev bc 34 | # Build Debian images 35 | sudo apt-get install debootstrap 36 | sudo apt-get install qemu-utils 37 | ``` 38 | 39 | KIT relies on QEMU-KVM to efficiently execute test cases. Please refer to this [link](https://help.ubuntu.com/community/KVM/Installation) for KVM installation and how to run QEMU-KVM as a non-root user. If everything is successfully configured, you should expect the output to be similar to the following when running: 40 | 41 | ```bash 42 | user@hostname:~$ qemu-system-x86_64 -nographic -enable-kvm 43 | SeaBIOS (version 1.15.0-1) 44 | 45 | 46 | iPXE (https://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+07F8B470+07ECB470 CA00 47 | 48 | 49 | 50 | Booting from Hard Disk... 51 | Boot failed: could not read the boot disk 52 | 53 | Booting from Floppy... 54 | Boot failed: could not read the boot disk 55 | 56 | 57 | ``` 58 | 59 | Run `prerequisite/setup.sh` to finalize the setup, which includes installing the go compiler, patching the syzkaller code, and building KIT and the customized gcc. 60 | 61 | ### Kernel 62 | 63 | Make sure the following configurations are within the kernel build configuration file: 64 | 65 | ```shell 66 | # For Debian images 67 | CONFIG_CONFIGFS_FS=y 68 | CONFIG_SECURITYFS=y 69 | 70 | # For host-guest communication 71 | CONFIG_VIRTIO=y 72 | CONFIG_VIRTIO_PCI=y 73 | CONFIG_VIRTIO_CONSOLE=y 74 | ``` 75 | 76 | You might also need to disable SELinux by setting `CONFIG_SECURITY_SELINUX =n`, in case it stops the booting process. 77 | 78 | ## Publications 79 | 80 | * Congyu Liu, Sishuai Gong, Pedro Fonseca. Kit: Testing OS-level Virtualization for Functional Interference Bugs. In *Proceedings of the 28th ACM International Conference on Architectural Support for Programming Languages and Operating Systems (ASPLOS)*, Vancouver, Canada, 2023 -------------------------------------------------------------------------------- /exec/execcomm.go: -------------------------------------------------------------------------------- 1 | package exec 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/gob" 7 | "fmt" 8 | "io" 9 | "time" 10 | 11 | "github.com/google/syzkaller/prog" 12 | "github.com/rss/kit/vm" 13 | "github.com/rss/kit/vm/comm" 14 | ) 15 | 16 | // TODO: need to clean the code in this file 17 | 18 | func loopbackMaster(inst *vm.Instance, timeout time.Duration) error { 19 | var err error 20 | 21 | sendbuf := make([]byte, 4) 22 | recvbuf := make([]byte, 4) 23 | for i := 0; i < CommNum; i++ { 24 | err = inst.Comm(i).SetRWDeadline(time.Now().Add(timeout)) 25 | if err != nil { 26 | return fmt.Errorf("cannot set r/w deadline: %v", err) 27 | } 28 | } 29 | defer func() { 30 | // reset deadline 31 | for i := 0; i < CommNum; i++ { 32 | err = inst.Comm(i).SetRWDeadline(time.Time{}) 33 | } 34 | }() 35 | for i := 0; i < CommNum; i++ { 36 | binary.LittleEndian.PutUint32(sendbuf, uint32(i*123+456)) 37 | _, err = inst.Comm(i).Write(sendbuf) 38 | if err != nil { 39 | return fmt.Errorf("cannot write to comm %v: %v", i, err) 40 | } 41 | _, err := io.ReadFull(inst.Comm((i+1)%CommNum), recvbuf) 42 | if err != nil { 43 | return fmt.Errorf("cannot read from comm %v: %v", (i+1)%CommNum, err) 44 | } 45 | if !bytes.Equal(sendbuf, recvbuf) { 46 | return fmt.Errorf("return bytes %v, expect %v", recvbuf, sendbuf) 47 | } 48 | } 49 | return nil 50 | } 51 | 52 | func LoopbackSlave(comms []comm.GuestComm) error { 53 | recvbuf := make([]byte, 4) 54 | for i := 0; i < CommNum; i++ { 55 | _, err := io.ReadFull(comms[i], recvbuf) 56 | if err != nil { 57 | return fmt.Errorf("cannot read from comm %v: %v", i, err) 58 | } 59 | _, err = comms[(i+1)%CommNum].Write(recvbuf) 60 | if err != nil { 61 | return fmt.Errorf("cannot write to comm %v: %v", (i+1)%CommNum, err) 62 | } 63 | } 64 | return nil 65 | } 66 | 67 | func recvFin(inst *vm.Instance) (err error) { 68 | var l int 69 | buf := make([]byte, 4) 70 | // increase to 20s to let container finish setup 71 | err = inst.Comm(OutComm).SetRWDeadline(time.Now().Add(20 * time.Second)) 72 | // reset deadline 73 | defer inst.Comm(OutComm).SetRWDeadline(time.Time{}) 74 | if err != nil { 75 | return 76 | } 77 | l, err = inst.Comm(OutComm).Read(buf) 78 | if err != nil { 79 | return 80 | } 81 | if string(buf[:l]) != "fin" { 82 | return fmt.Errorf("expect fin, get %v", string(buf)) 83 | } 84 | return nil 85 | } 86 | 87 | func SendFin(comms []comm.GuestComm) error { 88 | _, err := comms[OutComm].Write([]byte("fin")) 89 | if err != nil { 90 | return err 91 | } 92 | return nil 93 | } 94 | 95 | func SendCTest(inst *vm.Instance, c *CTest, buf []byte) (err error) { 96 | w := bytes.NewBuffer(nil) 97 | enc := gob.NewEncoder(w) 98 | err = enc.Encode(c) 99 | if err != nil { 100 | return err 101 | } 102 | data := w.Bytes() 103 | l := uint32(len(data)) 104 | binary.LittleEndian.PutUint32(buf[:4], l) 105 | copy(buf[4:], w.Bytes()) 106 | _, err = inst.Comm(InComm).Write(buf[:l+4]) 107 | if err != nil { 108 | return 109 | } 110 | return 111 | } 112 | 113 | func RecvCTest(c comm.GuestComm, buf []byte) (cp *CTest, err error) { 114 | var tmpBuf [4]byte 115 | 116 | _, err = io.ReadFull(c, tmpBuf[:4]) 117 | if err != nil { 118 | return nil, err 119 | } 120 | l := int(binary.LittleEndian.Uint32(tmpBuf[:4])) 121 | if l > len(buf) { 122 | buf = make([]byte, l) 123 | } 124 | _, err = io.ReadFull(c, buf[:l]) 125 | if err != nil { 126 | return nil, fmt.Errorf("cannot read C prog data: %v", err) 127 | } 128 | r := bytes.NewBuffer(buf[:l]) 129 | dec := gob.NewDecoder(r) 130 | cp = &CTest{} 131 | err = dec.Decode(cp) 132 | if err != nil { 133 | return nil, fmt.Errorf("cannot parse C prog data: %v", err) 134 | } 135 | return cp, nil 136 | } 137 | 138 | // func SendCTest(inst *vm.Instance, ct *CTest, buf []byte) (err error) { 139 | // err = sendCProg(inst, ct.A, buf) 140 | // if err != nil { 141 | // return err 142 | // } 143 | // err = sendCProg(inst, ct.V, buf) 144 | // if err != nil { 145 | // return err 146 | // } 147 | // inst.Comm(InComm).Write() 148 | // return nil 149 | // } 150 | 151 | // func RecvCTest(c comm.GuestComm, buf []byte) (ct *CTest, err error) { 152 | // ct = &CTest{} 153 | // ct.A, err = recvCProg(c, buf) 154 | // if err != nil { 155 | // return nil, err 156 | // } 157 | // ct.V, err = recvCProg(c, buf) 158 | // if err != nil { 159 | // return nil, err 160 | // } 161 | // return ct, nil 162 | // } 163 | 164 | func SendCTestReport(c comm.GuestComm, r *CTestReport, buf []byte) error { 165 | w := bytes.NewBuffer(nil) 166 | enc := gob.NewEncoder(w) 167 | err := enc.Encode(r) 168 | if err != nil { 169 | return err 170 | } 171 | data := w.Bytes() 172 | l := len(data) 173 | if l+4 > len(buf) { 174 | buf = make([]byte, l+4) 175 | } 176 | binary.LittleEndian.PutUint32(buf[:4], uint32(l)) 177 | copy(buf[4:], data) 178 | 179 | _, err = c.Write(buf[:l+4]) 180 | if err != nil { 181 | return err 182 | } 183 | return nil 184 | } 185 | 186 | func RecvCTestReport(inst *vm.Instance, buf []byte) (*CTestReport, error) { 187 | var tmpBuf [4]byte 188 | 189 | _, err := io.ReadFull(inst.Comm(OutComm), tmpBuf[:4]) 190 | if err != nil { 191 | return nil, err 192 | } 193 | l := int(binary.LittleEndian.Uint32(tmpBuf[:4])) 194 | if l > len(buf) { 195 | buf = make([]byte, l) 196 | } 197 | _, err = io.ReadFull(inst.Comm(OutComm), buf[:l]) 198 | if err != nil { 199 | return nil, err 200 | } 201 | r := bytes.NewBuffer(buf[:l]) 202 | dec := gob.NewDecoder(r) 203 | cr := &CTestReport{} 204 | err = dec.Decode(cr) 205 | if err != nil { 206 | return nil, fmt.Errorf("cannot decode C test report: %v", err) 207 | } 208 | return cr, nil 209 | } 210 | 211 | func sendExecData(inst *vm.Instance, e *ExecData, buf []byte) (err error) { 212 | var progLen, totalLen int 213 | var bufStart []byte 214 | 215 | progBuf := e.P.Serialize() 216 | progLen = len(progBuf) 217 | totalLen = 4 + 8 + progLen 218 | if totalLen > len(buf) { 219 | buf = make([]byte, totalLen) 220 | } 221 | bufStart = buf 222 | binary.LittleEndian.PutUint32(bufStart[:4], uint32(totalLen)) 223 | bufStart = bufStart[4:] 224 | binary.LittleEndian.PutUint64(bufStart[:8], uint64(e.Opts.Flags)) 225 | bufStart = bufStart[8:] 226 | copy(bufStart, progBuf) 227 | _, err = inst.Comm(InComm).Write(buf[:totalLen]) 228 | if err != nil { 229 | return 230 | } 231 | return 232 | } 233 | 234 | func recvExecData(c comm.GuestComm, progTarget *prog.Target, buf []byte) (e *ExecData, err error) { 235 | var tmpBuf [4]byte 236 | var bufStart []byte 237 | var totalLen int 238 | 239 | _, err = io.ReadFull(c, tmpBuf[:4]) 240 | if err != nil { 241 | return nil, err 242 | } 243 | totalLen = int(binary.LittleEndian.Uint32(tmpBuf[:4])) 244 | if totalLen > len(buf) { 245 | buf = make([]byte, totalLen) 246 | } 247 | bufStart = buf[:] 248 | totalLen -= 4 // already read 4 bytes 249 | _, err = io.ReadFull(c, bufStart[:totalLen]) 250 | if err != nil { 251 | return nil, fmt.Errorf("cannot read exec data: %v", err) 252 | } 253 | e = &ExecData{} 254 | e.Opts.Flags = ExecFlags(binary.LittleEndian.Uint64(bufStart[:8])) 255 | bufStart = bufStart[8:] 256 | e.P, err = progTarget.Deserialize(bufStart[:totalLen-8], prog.NonStrict) 257 | if err != nil { 258 | return nil, err 259 | } 260 | return 261 | } 262 | 263 | func sendTest(inst *vm.Instance, test *Test, buf []byte) (err error) { 264 | err = sendExecData(inst, test.A, buf) 265 | if err != nil { 266 | return fmt.Errorf("send attack exec: %v", err) 267 | } 268 | err = sendExecData(inst, test.V, buf) 269 | if err != nil { 270 | return fmt.Errorf("send victim exec: %v", err) 271 | } 272 | return 273 | } 274 | 275 | func RecvTest(c comm.GuestComm, progTarget *prog.Target, aBuf []byte, vBuf []byte) (t *Test, err error) { 276 | t = &Test{} 277 | t.A, err = recvExecData(c, progTarget, aBuf) 278 | if err != nil { 279 | return nil, fmt.Errorf("recv attack exec: %v", err) 280 | } 281 | t.V, err = recvExecData(c, progTarget, vBuf) 282 | if err != nil { 283 | return nil, fmt.Errorf("recv victim exec: %v", err) 284 | } 285 | return 286 | } 287 | 288 | func sendExecResult(c comm.GuestComm, execRes *ExecResult, buf []byte) (err error) { 289 | var stderrLen, stdoutLen int 290 | var bufStart []byte 291 | 292 | stderrLen = len(execRes.Stderr) 293 | stdoutLen = len(execRes.Stdout) 294 | totalLen := 4*4 + stderrLen + stdoutLen 295 | if totalLen > len(buf) { 296 | buf = make([]byte, totalLen) 297 | } 298 | bufStart = buf 299 | 300 | binary.LittleEndian.PutUint32(bufStart[:4], uint32(totalLen)) 301 | bufStart = bufStart[4:] 302 | if execRes.Hanged { 303 | binary.LittleEndian.PutUint32(bufStart[:4], uint32(1)) 304 | } else { 305 | binary.LittleEndian.PutUint32(bufStart[:4], uint32(0)) 306 | } 307 | bufStart = bufStart[4:] 308 | binary.LittleEndian.PutUint32(bufStart[:4], uint32(stderrLen)) 309 | bufStart = bufStart[4:] 310 | binary.LittleEndian.PutUint32(bufStart[:4], uint32(stdoutLen)) 311 | bufStart = bufStart[4:] 312 | copy(bufStart, execRes.Stderr) 313 | bufStart = bufStart[stderrLen:] 314 | copy(bufStart, execRes.Stdout) 315 | _, err = c.Write(buf[:totalLen]) 316 | if err != nil { 317 | return 318 | } 319 | return 320 | } 321 | 322 | func recvExecResult(inst *vm.Instance, buf []byte) (execRes *ExecResult, err error) { 323 | var tmpBuf [4]byte 324 | var bufStart []byte 325 | var stderrLen, stdoutLen, hanged uint32 326 | var totalLen int 327 | // var l int 328 | 329 | _, err = io.ReadFull(inst.Comm(OutComm), tmpBuf[:4]) 330 | if err != nil { 331 | return nil, err 332 | } 333 | totalLen = int(binary.LittleEndian.Uint32(tmpBuf[:4])) 334 | totalLen -= 4 // already read 4 bytes 335 | if totalLen > len(buf) { 336 | buf = make([]byte, totalLen) 337 | } 338 | bufStart = buf[:totalLen] 339 | _, err = io.ReadFull(inst.Comm(OutComm), bufStart) 340 | if err != nil { 341 | return nil, err 342 | } 343 | hanged = binary.LittleEndian.Uint32(bufStart[:4]) 344 | bufStart = bufStart[4:] 345 | stderrLen = binary.LittleEndian.Uint32(bufStart[:4]) 346 | bufStart = bufStart[4:] 347 | stdoutLen = binary.LittleEndian.Uint32(bufStart[:4]) 348 | bufStart = bufStart[4:] 349 | execRes = &ExecResult{} 350 | execRes.Stderr = bufStart[:stderrLen] 351 | bufStart = bufStart[stderrLen:] 352 | execRes.Stdout = bufStart[:stdoutLen] 353 | if hanged == 1 { 354 | execRes.Hanged = true 355 | } else if hanged == 0 { 356 | execRes.Hanged = false 357 | } else { 358 | err = fmt.Errorf("hanged bytes should be 0/1, get %v", hanged) 359 | } 360 | return 361 | } 362 | 363 | func SendTestResult(c []comm.GuestComm, testRes *TestResult, buf []byte) (err error) { 364 | err = sendExecResult(c[OutComm], testRes.A, buf) 365 | if err != nil { 366 | return fmt.Errorf("send attack test result: %v", err) 367 | } 368 | err = sendExecResult(c[OutComm], testRes.V, buf) 369 | if err != nil { 370 | return fmt.Errorf("send victim test result: %v", err) 371 | } 372 | return nil 373 | 374 | } 375 | 376 | func recvTestResult(inst *vm.Instance, aBuf []byte, vBuf []byte) (t *TestResult, err error) { 377 | t = &TestResult{} 378 | t.A, err = recvExecResult(inst, aBuf) 379 | if err != nil { 380 | return nil, err 381 | } 382 | t.V, err = recvExecResult(inst, vBuf) 383 | if err != nil { 384 | return nil, err 385 | } 386 | return 387 | } 388 | -------------------------------------------------------------------------------- /exec/master.go: -------------------------------------------------------------------------------- 1 | package exec 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "os" 8 | "time" 9 | 10 | "github.com/google/syzkaller/pkg/log" 11 | "github.com/google/syzkaller/prog" 12 | 13 | "github.com/rss/kit/pgen" 14 | "github.com/rss/kit/vm" 15 | ) 16 | 17 | type ExecData struct { 18 | Opts ExecOpts 19 | P *prog.Prog 20 | } 21 | 22 | type ExecResult struct { 23 | Stdout []byte 24 | Stderr []byte 25 | Hanged bool 26 | } 27 | 28 | type ExecReport struct { 29 | Info *ProgInfo 30 | Stderr []byte 31 | Hanged bool 32 | } 33 | 34 | type Test struct { 35 | A *ExecData 36 | V *ExecData 37 | } 38 | 39 | type TestResult struct { 40 | A *ExecResult 41 | V *ExecResult 42 | } 43 | 44 | type TestReport struct { 45 | A *ExecReport 46 | V *ExecReport 47 | } 48 | 49 | type CTest struct { 50 | A *pgen.CProg 51 | V *pgen.CProg 52 | SkipA bool 53 | } 54 | 55 | type CTestReport struct { 56 | ASCTrace string 57 | VSCTrace string 58 | } 59 | 60 | type stderrHandler struct { 61 | inst *vm.Instance 62 | buf []byte 63 | done chan bool 64 | waitClose bool 65 | } 66 | 67 | type ExecutorMaster struct { 68 | Index int 69 | 70 | inst *vm.Instance 71 | AExecResBuf []byte 72 | VExecResBuf []byte 73 | ExecDataBuf []byte 74 | cProg bool 75 | 76 | errHandler *stderrHandler 77 | 78 | // TODO: use RPC to communicate with executor? 79 | // TCP has states, will it work normally after VM reset? 80 | } 81 | 82 | const ( 83 | InComm = iota 84 | OutComm 85 | ErrComm 86 | 87 | CommNum 88 | ) 89 | 90 | const ( 91 | ExecDataBufSize = 64 << 10 92 | ExecResultBufSize = 2 * outputSize 93 | ) 94 | 95 | func executordCmdArgs(vmType, commType, executor string, signal, extraSignal bool, slowdown int, debug bool) string { 96 | return fmt.Sprintf("-vm=%v -comm=%v -executor=%v -signal=%v -extra_signal=%v -slowdown=%v -debug=%v", vmType, commType, executor, signal, extraSignal, slowdown, debug) 97 | } 98 | 99 | func initStderrHandler(inst *vm.Instance, debug bool) (h *stderrHandler) { 100 | h = &stderrHandler{ 101 | inst: inst, 102 | } 103 | h.buf = make([]byte, 16<<10) 104 | h.done = make(chan bool) 105 | go func() { 106 | reader := bufio.NewReader(h.inst.Comm(ErrComm)) 107 | for { 108 | s, err := reader.ReadString('\n') 109 | if err != nil { 110 | log.Logf(0, "stderr reader is closed: %v", err) 111 | if h.waitClose { 112 | log.Logf(0, "probably someone is closing stderr handler") 113 | } 114 | break 115 | } else if debug { 116 | log.Logf(0, "[executord] %v", s) 117 | } 118 | } 119 | close(h.done) 120 | }() 121 | return 122 | } 123 | 124 | func (h *stderrHandler) Close() { 125 | h.waitClose = true 126 | err := h.inst.Comm(ErrComm).SetRWDeadline(time.Now()) 127 | if err == nil { 128 | <-h.done 129 | } 130 | } 131 | 132 | func CreateSnapshot(inst *vm.Instance, vmType, commType, executord, executor string, signal, extraSignal bool, slowdown int, debug bool) error { 133 | var err error 134 | var executordGuest, executorGuest string 135 | var cmdArgs string 136 | 137 | executordGuest, err = inst.Copy(executord) 138 | if err != nil { 139 | return fmt.Errorf("cannot copy binary %v: %v", executord, err) 140 | } 141 | executorGuest, err = inst.Copy(executor) 142 | if err != nil { 143 | return fmt.Errorf("cannot copy binary %v: %v", executor, err) 144 | } 145 | cmdArgs = executordCmdArgs(vmType, commType, executorGuest, signal, extraSignal, slowdown, debug) 146 | // run executord as daemon 147 | _, _, _, _, err = inst.RawRun(fmt.Sprintf("nohup %v %v > foo.out 2> foo.err < /dev/null &", executordGuest, cmdArgs)) 148 | if err != nil { 149 | return fmt.Errorf("cannot run executord: %v", err) 150 | } 151 | err = loopbackMaster(inst, 40*time.Second) 152 | if err != nil { 153 | return fmt.Errorf("loopback test fail: %v", err) 154 | } 155 | h := initStderrHandler(inst, debug) 156 | defer h.Close() 157 | // handle err comm 158 | err = recvFin(inst) 159 | if err != nil { 160 | return fmt.Errorf("recv fail: %v\ne", err) 161 | } 162 | // sleep to let some namespace init kernel threads finish? 163 | time.Sleep(time.Second * 20) 164 | inst.SaveSnapshot() 165 | log.Logf(0, "create snapshot finished") 166 | return nil 167 | } 168 | 169 | // TODO: maybe merge this one with the above one 170 | func CreateCProgSnapshot(inst *vm.Instance, vmType, commType, executorcd, header string, debug bool) error { 171 | var cmdArgs string 172 | 173 | executorcdGuest, err := inst.Copy(executorcd) 174 | if err != nil { 175 | return fmt.Errorf("cannot copy binary %v: %v", executorcd, err) 176 | } 177 | _, err = inst.Copy(header) 178 | if err != nil { 179 | return fmt.Errorf("cannot copy header %v: %v", header, err) 180 | } 181 | cmdArgs = fmt.Sprintf("-vm=%v -comm=%v -debug=%v", vmType, commType, debug) 182 | // run executord as daemon 183 | _, _, _, _, err = inst.RawRun(fmt.Sprintf("nohup %v %v > foo.out 2> foo.err < /dev/null &", executorcdGuest, cmdArgs)) 184 | if err != nil { 185 | return fmt.Errorf("cannot run executorcd: %v", err) 186 | } 187 | err = loopbackMaster(inst, 20*time.Second) 188 | if err != nil { 189 | return fmt.Errorf("loopback test fail: %v", err) 190 | } 191 | h := initStderrHandler(inst, debug) 192 | defer h.Close() 193 | // handle err comm 194 | err = recvFin(inst) 195 | if err != nil { 196 | return fmt.Errorf("recv fail: %v\ne", err) 197 | } 198 | // sleep to let some namespace init kernel threads finish? 199 | time.Sleep(time.Second * 10) 200 | inst.SaveSnapshot() 201 | log.Logf(0, "create snapshot for executing C programs finished") 202 | return nil 203 | } 204 | 205 | func InitExecutorMaster(index int, inst *vm.Instance, cProg, debug bool) *ExecutorMaster { 206 | e := &ExecutorMaster{ 207 | Index: index, 208 | inst: inst, 209 | ExecDataBuf: make([]byte, ExecDataBufSize), 210 | AExecResBuf: make([]byte, ExecResultBufSize), 211 | VExecResBuf: make([]byte, ExecResultBufSize), 212 | cProg: cProg, 213 | } 214 | e.inst.LoadSnapshot() 215 | e.errHandler = initStderrHandler(inst, debug) 216 | return e 217 | } 218 | 219 | func (nsexec *ExecutorMaster) clearIOBuffer(index int, buf []byte) { 220 | deadline := time.Now().Add(1000 * time.Millisecond) 221 | nsexec.inst.Comm(index).SetRWDeadline(deadline) 222 | io.ReadFull(nsexec.inst.Comm(index), buf) 223 | // reset deadline 224 | nsexec.inst.Comm(index).SetRWDeadline(time.Time{}) 225 | } 226 | 227 | func genExecReport(res *ExecResult, p *prog.Prog) (rep *ExecReport, err error) { 228 | rep = &ExecReport{} 229 | rep.Info, err = parseOutput(res.Stdout, p) 230 | if err != nil { 231 | return nil, err 232 | } 233 | rep.Hanged = res.Hanged 234 | rep.Stderr = make([]byte, len(res.Stderr)) 235 | copy(rep.Stderr, res.Stderr) 236 | return 237 | } 238 | 239 | func GenReport(res *TestResult, ap, vp *prog.Prog) (rep *TestReport, err error) { 240 | rep = &TestReport{} 241 | rep.A, err = genExecReport(res.A, ap) 242 | if err != nil { 243 | return nil, fmt.Errorf("cannot generate attack program exec report: %v", err) 244 | } 245 | rep.V, err = genExecReport(res.V, vp) 246 | if err != nil { 247 | return nil, fmt.Errorf("cannot generate victim program exec report: %v", err) 248 | } 249 | return rep, nil 250 | } 251 | 252 | func (nsexec *ExecutorMaster) Run(test *Test, maxTime time.Duration) (rep *TestReport, timeOut bool, err error) { 253 | var res *TestResult 254 | rep = &TestReport{} 255 | 256 | if nsexec.cProg { 257 | err = fmt.Errorf("cannot call this function when executor is for C programs") 258 | return nil, false, err 259 | } 260 | 261 | deadline := time.Now().Add(maxTime) 262 | err = nsexec.inst.Comm(InComm).SetRWDeadline(deadline) 263 | if err != nil { 264 | err = fmt.Errorf("cannot set r/w deadline for in comm: %v", err) 265 | goto out 266 | } 267 | err = nsexec.inst.Comm(OutComm).SetRWDeadline(deadline) 268 | if err != nil { 269 | err = fmt.Errorf("cannot set r/w deadline out comm: %v", err) 270 | goto out 271 | } 272 | err = sendTest(nsexec.inst, test, nsexec.ExecDataBuf) 273 | if err != nil { 274 | if os.IsTimeout(err) { 275 | goto timeout 276 | } 277 | err = fmt.Errorf("send test data error: %v", err) 278 | goto out 279 | } 280 | res, err = recvTestResult(nsexec.inst, nsexec.AExecResBuf, nsexec.VExecResBuf) 281 | if err != nil { 282 | if os.IsTimeout(err) { 283 | goto timeout 284 | } 285 | err = fmt.Errorf("recv test data error: %v", err) 286 | goto out 287 | } 288 | 289 | rep, err = GenReport(res, test.A.P, test.V.P) 290 | if err != nil { 291 | err = fmt.Errorf("cannot generate test report: %v", err) 292 | goto out 293 | } 294 | out: 295 | // reset deadline 296 | nsexec.inst.Comm(InComm).SetRWDeadline(time.Time{}) 297 | nsexec.inst.Comm(OutComm).SetRWDeadline(time.Time{}) 298 | nsexec.inst.Comm(ErrComm).SetRWDeadline(time.Time{}) 299 | nsexec.inst.LoadSnapshot() 300 | return rep, false, err 301 | timeout: 302 | // clean garbage in fifo buffer 303 | nsexec.clearIOBuffer(InComm, nsexec.AExecResBuf) 304 | nsexec.inst.LoadSnapshot() 305 | nsexec.clearIOBuffer(OutComm, nsexec.AExecResBuf) 306 | return rep, true, nil 307 | } 308 | 309 | func (nsexec *ExecutorMaster) Close() { 310 | nsexec.errHandler.Close() 311 | nsexec.inst.Close() 312 | } 313 | 314 | func (nsexec *ExecutorMaster) RunCTest(test *CTest, maxTime time.Duration) (rep *CTestReport, timeOut bool, err error) { 315 | rep = &CTestReport{} 316 | 317 | if !nsexec.cProg { 318 | err = fmt.Errorf("cannot call this function when executor is NOT for C programs") 319 | return nil, false, err 320 | } 321 | 322 | deadline := time.Now().Add(maxTime) 323 | err = nsexec.inst.Comm(InComm).SetRWDeadline(deadline) 324 | if err != nil { 325 | err = fmt.Errorf("cannot set r/w deadline for in comm: %v", err) 326 | goto out 327 | } 328 | err = nsexec.inst.Comm(OutComm).SetRWDeadline(deadline) 329 | if err != nil { 330 | err = fmt.Errorf("cannot set r/w deadline out comm: %v", err) 331 | goto out 332 | } 333 | err = SendCTest(nsexec.inst, test, nsexec.ExecDataBuf) 334 | if err != nil { 335 | if os.IsTimeout(err) { 336 | goto timeout 337 | } 338 | err = fmt.Errorf("send C test error: %v", err) 339 | goto out 340 | } 341 | rep, err = RecvCTestReport(nsexec.inst, nsexec.AExecResBuf) 342 | if err != nil { 343 | if os.IsTimeout(err) { 344 | goto timeout 345 | } 346 | err = fmt.Errorf("recv test data error: %v", err) 347 | goto out 348 | } 349 | 350 | out: 351 | // reset deadline 352 | nsexec.inst.Comm(InComm).SetRWDeadline(time.Time{}) 353 | nsexec.inst.Comm(OutComm).SetRWDeadline(time.Time{}) 354 | nsexec.inst.Comm(ErrComm).SetRWDeadline(time.Time{}) 355 | nsexec.inst.LoadSnapshot() 356 | return rep, false, err 357 | timeout: 358 | // clean garbage in fifo buffer 359 | nsexec.clearIOBuffer(InComm, nsexec.AExecResBuf) 360 | nsexec.inst.LoadSnapshot() 361 | nsexec.clearIOBuffer(OutComm, nsexec.AExecResBuf) 362 | return rep, true, nil 363 | } 364 | -------------------------------------------------------------------------------- /exec/memtrace.go: -------------------------------------------------------------------------------- 1 | package exec 2 | 3 | import "fmt" 4 | 5 | type RawMemtracePkt struct { 6 | pc [16]uint64 7 | addr [16]uint64 8 | info [16]uint32 9 | } 10 | 11 | const ( 12 | MemtraceWrite uint32 = iota 13 | MemtraceRead 14 | MemtraceRet 15 | MemtraceCall 16 | ) 17 | 18 | type MemTrace struct { 19 | Type uint32 20 | PC uint64 21 | Addr uint64 22 | Len uint32 23 | } 24 | 25 | func (pkt *RawMemtracePkt) ToMemTraces() []*MemTrace { 26 | t := make([]*MemTrace, 0, 16) 27 | for i := 0; i < 16; i++ { 28 | m := &MemTrace{ 29 | Type: pkt.info[i] & 7, 30 | PC: pkt.pc[i], 31 | Addr: pkt.addr[i], 32 | Len: pkt.info[i] >> 3, 33 | } 34 | t = append(t, m) 35 | } 36 | return t 37 | } 38 | 39 | func (pkt *RawMemtracePkt) Deserialize(idx int) string { 40 | var rw byte 41 | if (pkt.info[idx] & 1) == 1 { 42 | rw = 'r' 43 | } else { 44 | rw = 'w' 45 | } 46 | len := pkt.info[idx] >> 1 47 | if len == 128 { 48 | // If you want to manually analyze pc (e.g. run addr2line) please disable KALSR!! 49 | return fmt.Sprintf("pc = %x, addr = %x, %c, len = %d", 50 | pkt.pc[idx], pkt.addr[idx], rw, len) 51 | } else { 52 | return fmt.Sprintf("pc = %x, addr = %x, %c, len = %d", 53 | pkt.pc[idx], pkt.addr[idx], rw, len) 54 | } 55 | } 56 | 57 | func DeserializeMemtrace(p []RawMemtracePkt, num int) string { 58 | s := "" 59 | for i := 0; i < num; i++ { 60 | s += p[0].Deserialize(i%16) + "\n" 61 | if (i % 16) == 15 { 62 | p = p[1:] 63 | } 64 | } 65 | return s 66 | } 67 | -------------------------------------------------------------------------------- /executor/clone.h: -------------------------------------------------------------------------------- 1 | #ifndef _GNU_SOURCE 2 | #define _GNU_SOURCE 3 | #endif 4 | 5 | #include 6 | 7 | 8 | #if SYZ_EXECUTOR 9 | // The slowdown multiplier is already taken into account. 10 | #define USLEEP_FORKED_CHILD (3 * syscall_timeout_ms * 1000) 11 | static long handle_clone_ret(long ret) 12 | { 13 | if (ret != 0) { 14 | #if SYZ_EXECUTOR || SYZ_HANDLE_SEGV 15 | __atomic_store_n(&clone_ongoing, 0, __ATOMIC_RELAXED); 16 | #endif 17 | return ret; 18 | } 19 | // Exit if we're in the child process - not all kernels provide the proper means 20 | // to prevent fork-bombs. 21 | // But first sleep for some time. This will hopefully foster IPC fuzzing. 22 | usleep(USLEEP_FORKED_CHILD); 23 | // Note that exit_group is a bad choice here because if we created just a thread, then 24 | // the whole process will be killed. A plain exit will work fine in any case. 25 | syscall(__NR_exit, 0); 26 | while (1) { 27 | } 28 | } 29 | #endif 30 | 31 | #if SYZ_EXECUTOR || __NR_syz_clone 32 | // syz_clone is mostly needed on kernels which do not suport clone3. 33 | static long syz_clone(volatile long flags, volatile long stack, volatile long stack_len, 34 | volatile long ptid, volatile long ctid, volatile long tls) 35 | { 36 | // ABI requires 16-byte stack alignment. 37 | long sp = (stack + stack_len) & ~15; 38 | #if SYZ_EXECUTOR || SYZ_HANDLE_SEGV 39 | __atomic_store_n(&clone_ongoing, 1, __ATOMIC_RELAXED); 40 | #endif 41 | // Clear the CLONE_VM flag. Otherwise it'll very likely corrupt syz-executor. 42 | long ret = (long)syscall(__NR_clone, flags & ~CLONE_VM, sp, ptid, ctid, tls); 43 | return handle_clone_ret(ret); 44 | } 45 | #endif 46 | 47 | #if SYZ_EXECUTOR || __NR_syz_clone3 48 | #include 49 | #include 50 | #define MAX_CLONE_ARGS_BYTES 256 51 | static long syz_clone3(volatile long a0, volatile long a1) 52 | { 53 | unsigned long copy_size = a1; 54 | if (copy_size < sizeof(uint64) || copy_size > MAX_CLONE_ARGS_BYTES) 55 | return -1; 56 | // The structure may have different sizes on different kernel versions, so copy it as raw bytes. 57 | char clone_args[MAX_CLONE_ARGS_BYTES]; 58 | memcpy(&clone_args, (void*)a0, copy_size); 59 | 60 | // As in syz_clone, clear the CLONE_VM flag. Flags are in the first 8-byte integer field. 61 | uint64* flags = (uint64*)&clone_args; 62 | *flags &= ~CLONE_VM; 63 | #if SYZ_EXECUTOR || SYZ_HANDLE_SEGV 64 | __atomic_store_n(&clone_ongoing, 1, __ATOMIC_RELAXED); 65 | #endif 66 | return handle_clone_ret((long)syscall(__NR_clone3, &clone_args, copy_size)); 67 | } 68 | #endif -------------------------------------------------------------------------------- /executor/comm.h: -------------------------------------------------------------------------------- 1 | #ifndef __EXEC_COMM_H 2 | #define __EXEC_COMM_H 3 | 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | const int kReplyFdOrignal = 3; 17 | const int kWaitFdOrignal = 4; 18 | const int kNotifyFdOrignal = 5; 19 | 20 | const int kReplyFd = 237; 21 | const int kWaitFd = 235; 22 | const int kNotifyFd = 236; 23 | 24 | static int flag_skip_prog = 0; 25 | 26 | static int init_exec_sync() { 27 | if (dup2(kReplyFdOrignal, kReplyFd) == -1) { 28 | return -1; 29 | } 30 | close(kReplyFdOrignal); 31 | if (dup2(kWaitFdOrignal, kWaitFd) == -1) { 32 | return -1; 33 | } 34 | close(kWaitFdOrignal); 35 | if (dup2(kNotifyFdOrignal, kNotifyFd) == -1) { 36 | return -1; 37 | } 38 | close(kNotifyFdOrignal); 39 | if (getenv("SKIP_PROG") != NULL) { 40 | flag_skip_prog = 1; 41 | } 42 | return 0; 43 | } 44 | 45 | static int exec_wait(struct timeval timeout) { 46 | fd_set set; 47 | int ret; 48 | char c; 49 | 50 | 51 | FD_ZERO(&set); 52 | FD_SET(kWaitFd, &set); 53 | 54 | ret = select(kWaitFd + 1, &set, NULL, NULL, &timeout); 55 | if (ret == -1 || ret == 0) { 56 | goto out; 57 | } 58 | ret = read(kWaitFd, &c, 1); 59 | if (ret != 1) { 60 | ret = -1; 61 | } else { 62 | ret = 0; 63 | } 64 | out: 65 | return ret; 66 | } 67 | 68 | 69 | static int exec_wait_nosig(uint64_t ms) { 70 | struct timespec start; 71 | uint64_t start_ms = 0; 72 | int ret = 0; 73 | while (1) { 74 | struct timeval timeout; 75 | if (clock_gettime(CLOCK_MONOTONIC, &start)) { 76 | return -1; 77 | } 78 | start_ms = (uint64_t)start.tv_sec * 1000 + (uint64_t)start.tv_nsec / 1000000; 79 | timeout = (struct timeval){ 80 | .tv_sec = (long)(ms/1000), 81 | .tv_usec = (long)(1000*(ms%1000)) 82 | }; 83 | ret = exec_wait(timeout); 84 | if (ret == -1 && errno == EINTR) { 85 | struct timespec now; 86 | uint64_t now_ms; 87 | if (clock_gettime(CLOCK_MONOTONIC, &now)) { 88 | return -1; 89 | } 90 | now_ms = (uint64_t)now.tv_sec * 1000 + (uint64_t)now.tv_nsec / 1000000; 91 | ms -= now_ms - start_ms; 92 | continue; 93 | } 94 | break; 95 | } 96 | return ret; 97 | } 98 | 99 | static int exec_notify() { 100 | char c = 'y'; 101 | if (write(kNotifyFd, &c, 1) != 1) { 102 | return -1; 103 | } 104 | return 0; 105 | } 106 | 107 | 108 | #endif -------------------------------------------------------------------------------- /executor/exec_sync.h: -------------------------------------------------------------------------------- 1 | #ifndef _EXEC_SYNC_H 2 | #define _EXEC_SYNC_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | const int kWaitFdOrignal = 5; 15 | const int kNotifyFdOrignal = 6; 16 | 17 | const int kWaitFd = 235; 18 | const int kNotifyFd = 236; 19 | 20 | #define IS_ATTACKER(procid) ((procid) == 1) 21 | #define IS_VICTIM(procid) ((procid) == 2) 22 | 23 | static int init_exec_sync() { 24 | if (dup2(kWaitFdOrignal, kWaitFd) == -1) { 25 | return -1; 26 | } 27 | close(kWaitFdOrignal); 28 | if (dup2(kNotifyFdOrignal, kNotifyFd) == -1) { 29 | return -1; 30 | } 31 | close(kNotifyFdOrignal); 32 | return 0; 33 | } 34 | 35 | static int exec_wait(struct timeval timeout) { 36 | fd_set set; 37 | int ret; 38 | char c; 39 | 40 | 41 | FD_ZERO(&set); 42 | FD_SET(kWaitFd, &set); 43 | 44 | ret = select(kWaitFd + 1, &set, NULL, NULL, &timeout); 45 | if (ret == -1 || ret == 0) { 46 | goto out; 47 | } 48 | ret = read(kWaitFd, &c, 1); 49 | if (ret != 1) { 50 | ret = -1; 51 | } else { 52 | ret = 0; 53 | } 54 | out: 55 | return ret; 56 | } 57 | 58 | 59 | static int exec_wait_nosig(uint64_t ms) { 60 | struct timespec start; 61 | uint64_t start_ms = 0; 62 | int ret = 0; 63 | while (1) { 64 | struct timeval timeout; 65 | if (clock_gettime(CLOCK_MONOTONIC, &start)) { 66 | return -1; 67 | } 68 | start_ms = (uint64_t)start.tv_sec * 1000 + (uint64_t)start.tv_nsec / 1000000; 69 | timeout = { 70 | .tv_sec = (long)(ms/1000), 71 | .tv_usec = (long)(1000*(ms%1000)) 72 | }; 73 | ret = exec_wait(timeout); 74 | if (ret == -1 && errno == EINTR) { 75 | struct timespec now; 76 | uint64_t now_ms; 77 | if (clock_gettime(CLOCK_MONOTONIC, &now)) { 78 | return -1; 79 | } 80 | now_ms = (uint64_t)now.tv_sec * 1000 + (uint64_t)now.tv_nsec / 1000000; 81 | ms -= now_ms - start_ms; 82 | continue; 83 | } 84 | break; 85 | } 86 | return ret; 87 | } 88 | 89 | static int exec_notify() { 90 | char c = 'y'; 91 | if (write(kNotifyFd, &c, 1) != 1) { 92 | return -1; 93 | } 94 | return 0; 95 | } 96 | 97 | #endif -------------------------------------------------------------------------------- /executor/executor_linux.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 syzkaller project authors. All rights reserved. 2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "clone.h" 15 | 16 | const unsigned long KCOV_TRACE_PC = 0; 17 | const unsigned long KCOV_TRACE_CMP = 1; 18 | 19 | template 20 | struct kcov_remote_arg { 21 | uint32 trace_mode; 22 | uint32 area_size; 23 | uint32 num_handles; 24 | uint32 pad; 25 | uint64 common_handle; 26 | uint64 handles[N]; 27 | }; 28 | 29 | #define KCOV_INIT_TRACE32 _IOR('c', 1, uint32) 30 | #define KCOV_INIT_TRACE64 _IOR('c', 1, uint64) 31 | #define KCOV_ENABLE _IO('c', 100) 32 | #define KCOV_DISABLE _IO('c', 101) 33 | #define KCOV_REMOTE_ENABLE _IOW('c', 102, kcov_remote_arg<0>) 34 | 35 | #define KCOV_SUBSYSTEM_COMMON (0x00ull << 56) 36 | #define KCOV_SUBSYSTEM_USB (0x01ull << 56) 37 | 38 | #define KCOV_SUBSYSTEM_MASK (0xffull << 56) 39 | #define KCOV_INSTANCE_MASK (0xffffffffull) 40 | 41 | static bool is_gvisor; 42 | 43 | static inline __u64 kcov_remote_handle(__u64 subsys, __u64 inst) 44 | { 45 | if (subsys & ~KCOV_SUBSYSTEM_MASK || inst & ~KCOV_INSTANCE_MASK) 46 | return 0; 47 | return subsys | inst; 48 | } 49 | 50 | static bool detect_kernel_bitness(); 51 | static bool detect_gvisor(); 52 | 53 | static void os_init(int argc, char** argv, char* data, size_t data_size) 54 | { 55 | prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0); 56 | is_kernel_64_bit = detect_kernel_bitness(); 57 | is_gvisor = detect_gvisor(); 58 | // Surround the main data mapping with PROT_NONE pages to make virtual address layout more consistent 59 | // across different configurations (static/non-static build) and C repros. 60 | // One observed case before: executor had a mapping above the data mapping (output region), 61 | // while C repros did not have that mapping above, as the result in one case VMA had next link, 62 | // while in the other it didn't and it caused a bug to not reproduce with the C repro. 63 | if (mmap(data - SYZ_PAGE_SIZE, SYZ_PAGE_SIZE, PROT_NONE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0) != data - SYZ_PAGE_SIZE) 64 | fail("mmap of left data PROT_NONE page failed"); 65 | if (mmap(data, data_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0) != data) 66 | fail("mmap of data segment failed"); 67 | if (mmap(data + data_size, SYZ_PAGE_SIZE, PROT_NONE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0) != data + data_size) 68 | fail("mmap of right data PROT_NONE page failed"); 69 | } 70 | 71 | static intptr_t execute_syscall(const call_t* c, intptr_t a[kMaxArgs]) 72 | { 73 | 74 | if (!strcmp(c->name, "clone")) { 75 | return syz_clone(a[0], a[1], a[2], a[3], a[4], a[5]); 76 | } else if (!strcmp(c->name, "clone3")) { 77 | return syz_clone3(a[0], a[1]); 78 | } 79 | if (c->call) 80 | return c->call(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]); 81 | return syscall(c->sys_nr, a[0], a[1], a[2], a[3], a[4], a[5]); 82 | } 83 | 84 | static void cover_open(cover_t* cov, bool extra) 85 | { 86 | int fd = open("/sys/kernel/debug/kcov", O_RDWR); 87 | if (fd == -1) 88 | fail("open of /sys/kernel/debug/kcov failed"); 89 | if (dup2(fd, cov->fd) < 0) 90 | failmsg("filed to dup cover fd", "from=%d, to=%d", fd, cov->fd); 91 | close(fd); 92 | const int kcov_init_trace = is_kernel_64_bit ? KCOV_INIT_TRACE64 : KCOV_INIT_TRACE32; 93 | const int cover_size = extra ? kExtraCoverSize : kCoverSize; 94 | if (ioctl(cov->fd, kcov_init_trace, cover_size)) 95 | fail("cover init trace write failed"); 96 | size_t mmap_alloc_size = cover_size * (is_kernel_64_bit ? 8 : 4); 97 | cov->data = (char*)mmap(NULL, mmap_alloc_size, 98 | PROT_READ | PROT_WRITE, MAP_SHARED, cov->fd, 0); 99 | if (cov->data == MAP_FAILED) 100 | fail("cover mmap failed"); 101 | cov->data_end = cov->data + mmap_alloc_size; 102 | cov->data_offset = is_kernel_64_bit ? sizeof(uint64_t) : sizeof(uint32_t); 103 | cov->pc_offset = 0; 104 | } 105 | 106 | static void cover_protect(cover_t* cov) 107 | { 108 | } 109 | 110 | static void cover_unprotect(cover_t* cov) 111 | { 112 | } 113 | 114 | static void cover_enable(cover_t* cov, bool collect_comps, bool extra) 115 | { 116 | unsigned int kcov_mode = collect_comps ? KCOV_TRACE_CMP : KCOV_TRACE_PC; 117 | // The KCOV_ENABLE call should be fatal, 118 | // but in practice ioctl fails with assorted errors (9, 14, 25), 119 | // so we use exitf. 120 | if (!extra) { 121 | if (ioctl(cov->fd, KCOV_ENABLE, kcov_mode)) 122 | exitf("cover enable write trace failed, mode=%d", kcov_mode); 123 | return; 124 | } 125 | kcov_remote_arg<1> arg = { 126 | .trace_mode = kcov_mode, 127 | // Coverage buffer size of background threads. 128 | .area_size = kExtraCoverSize, 129 | .num_handles = 1, 130 | }; 131 | arg.common_handle = kcov_remote_handle(KCOV_SUBSYSTEM_COMMON, procid + 1); 132 | arg.handles[0] = kcov_remote_handle(KCOV_SUBSYSTEM_USB, procid + 1); 133 | if (ioctl(cov->fd, KCOV_REMOTE_ENABLE, &arg)) 134 | exitf("remote cover enable write trace failed"); 135 | } 136 | 137 | static void cover_reset(cover_t* cov) 138 | { 139 | // Callers in common_linux.h don't check this flag. 140 | if (!flag_coverage) 141 | return; 142 | if (cov == 0) { 143 | if (current_thread == 0) 144 | fail("cover_reset: current_thread == 0"); 145 | cov = ¤t_thread->cov; 146 | } 147 | *(uint64*)cov->data = 0; 148 | } 149 | 150 | static void cover_collect(cover_t* cov) 151 | { 152 | if (is_kernel_64_bit) 153 | cov->size = *(uint64*)cov->data; 154 | else 155 | cov->size = *(uint32*)cov->data; 156 | } 157 | 158 | static bool use_cover_edges(uint32 pc) 159 | { 160 | return true; 161 | } 162 | 163 | static bool use_cover_edges(uint64 pc) 164 | { 165 | #if defined(__i386__) || defined(__x86_64__) 166 | if (is_gvisor) 167 | return false; // gvisor coverage is not a trace, so producing edges won't work 168 | // Text/modules range for x86_64. 169 | if (pc < 0xffffffff80000000ull || pc >= 0xffffffffff000000ull) { 170 | debug("got bad pc: 0x%llx\n", pc); 171 | doexit(0); 172 | } 173 | #endif 174 | return true; 175 | } 176 | 177 | static bool detect_kernel_bitness() 178 | { 179 | if (sizeof(void*) == 8) 180 | return true; 181 | // It turns out to be surprisingly hard to understand if the kernel underneath is 64-bits. 182 | // A common method is to look at uname.machine. But it is produced in some involved ways, 183 | // and we will need to know about all strings it returns and in the end it can be overriden 184 | // during build and lie (and there are known precedents of this). 185 | // So instead we look at size of addresses in /proc/kallsyms. 186 | bool wide = true; 187 | int fd = open("/proc/kallsyms", O_RDONLY); 188 | if (fd != -1) { 189 | char buf[16]; 190 | if (read(fd, buf, sizeof(buf)) == sizeof(buf) && 191 | (buf[8] == ' ' || buf[8] == '\t')) 192 | wide = false; 193 | close(fd); 194 | } 195 | debug("detected %d-bit kernel\n", wide ? 64 : 32); 196 | return wide; 197 | } 198 | 199 | static bool detect_gvisor() 200 | { 201 | char buf[64] = {}; 202 | // 3 stands for undeclared SYSLOG_ACTION_READ_ALL. 203 | syscall(__NR_syslog, 3, buf, sizeof(buf) - 1); 204 | // This is a first line of gvisor dmesg. 205 | return strstr(buf, "Starting gVisor"); 206 | } 207 | 208 | // One does not simply exit. 209 | // _exit can in fact fail. 210 | // syzkaller did manage to generate a seccomp filter that prohibits exit_group syscall. 211 | // Previously, we get into infinite recursion via segv_handler in such case 212 | // and corrupted output_data, which does matter in our case since it is shared 213 | // with fuzzer process. Loop infinitely instead. Parent will kill us. 214 | // But one does not simply loop either. Compilers are sure that _exit never returns, 215 | // so they remove all code after _exit as dead. Call _exit via volatile indirection. 216 | // And this does not work as well. _exit has own handling of failing exit_group 217 | // in the form of HLT instruction, it will divert control flow from our loop. 218 | // So call the syscall directly. 219 | NORETURN void doexit(int status) 220 | { 221 | volatile unsigned i; 222 | syscall(__NR_exit_group, status); 223 | for (i = 0;; i++) { 224 | } 225 | } 226 | 227 | #define SYZ_HAVE_FEATURES 1 228 | static feature_t features[] = { 229 | {"leak", setup_leak}, 230 | {"fault", setup_fault}, 231 | {"binfmt_misc", setup_binfmt_misc}, 232 | {"kcsan", setup_kcsan}, 233 | {"usb", setup_usb}, 234 | {"sysctl", setup_sysctl}, 235 | {"802154", setup_802154}, 236 | }; 237 | -------------------------------------------------------------------------------- /executorcd/executorcd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "os" 9 | "sync" 10 | "time" 11 | 12 | osexec "os/exec" 13 | 14 | "github.com/rss/kit/exec" 15 | "github.com/rss/kit/pgen" 16 | "github.com/rss/kit/vm" 17 | "github.com/rss/kit/vm/comm" 18 | ) 19 | 20 | var ( 21 | flagVM = flag.String("vm", "qemu", "vm type") 22 | flagComm = flag.String("comm", "virtio", "host-guest communication type") 23 | flagDebug = flag.Bool("debug", false, "debug") 24 | ) 25 | 26 | var comms []comm.GuestComm 27 | var dmesg *os.File 28 | 29 | func earlyErrf(format string, args ...interface{}) { 30 | s := fmt.Sprintf("<2>[executord] "+format, args...) 31 | dmesg.Write([]byte(s)) 32 | dmesg.Sync() 33 | } 34 | 35 | func earlyDebugf(format string, args ...interface{}) { 36 | if *flagDebug { 37 | earlyErrf(format, args...) 38 | } 39 | } 40 | 41 | func errf(format string, args ...interface{}) { 42 | s := fmt.Sprintf(format, args...) 43 | comms[exec.ErrComm].Write([]byte(s)) 44 | } 45 | 46 | func debugf(format string, args ...interface{}) { 47 | if *flagDebug { 48 | errf(format, args...) 49 | } 50 | } 51 | 52 | func compileAndStart(cp *pgen.CProg, waitF, notifyF *os.File, skip, debug bool) (string, error) { 53 | fPath := "./" + cp.Meta.Name 54 | binPath := fPath + ".bin" 55 | err := ioutil.WriteFile(fPath, cp.Code, 0666) 56 | if err != nil { 57 | return "", err 58 | } 59 | compArgs := []string{} 60 | compArgs = append(compArgs, cp.Meta.CompFlags...) 61 | compArgs = append(compArgs, "-o", binPath) 62 | compArgs = append(compArgs, fPath) 63 | compCmd := osexec.Command(cp.Meta.Compiler, compArgs...) 64 | compOut, err := compCmd.CombinedOutput() 65 | debugf("compiler output: %v", string(compOut)) 66 | if err != nil { 67 | return "", fmt.Errorf("cannot compile: %v", err) 68 | } 69 | 70 | // check binary 71 | // ipvsCmd := osexec.Command("ipvsadm") 72 | // ipvsOut, err := ipvsCmd.CombinedOutput() 73 | // debugf("ipvs output: %v", string(ipvsOut)) 74 | // if err != nil { 75 | // return "", fmt.Errorf("cannot run ipvsadm: %v", err) 76 | // } 77 | 78 | rp, wp, err := os.Pipe() 79 | if err != nil { 80 | return "", fmt.Errorf("cannot create pipe: %v", err) 81 | } 82 | defer wp.Close() 83 | runCmd := osexec.Command(binPath) 84 | runCmd.ExtraFiles = []*os.File{wp, waitF, notifyF} 85 | if skip { 86 | debugf("set skip_prog = 1\n") 87 | runCmd.Env = append(os.Environ(), "SKIP_PROG=1") 88 | } 89 | if debug { 90 | runCmd.Stderr = comms[exec.ErrComm] 91 | } 92 | err = runCmd.Start() 93 | if err != nil { 94 | return "", fmt.Errorf("cannot run C program: %v", err) 95 | } 96 | go func() { 97 | debugf("I am waiting on program %v...\n", cp.Meta.Name) 98 | err := runCmd.Wait() 99 | if err != nil { 100 | debugf("cannot wait process\n") 101 | } 102 | debugf("wait done, program %s exit\n", cp.Meta.Name) 103 | }() 104 | buf := make([]byte, 4096) 105 | rp.SetReadDeadline(time.Now().Add(3 * time.Second)) 106 | l, err := rp.Read(buf) 107 | if err != nil { 108 | return "", fmt.Errorf("cannot read trace: %v", err) 109 | } 110 | return string(buf[:l]), nil 111 | } 112 | 113 | func main() { 114 | var err error 115 | var resBuf []byte 116 | 117 | aExecDataBuf := make([]byte, exec.ExecDataBufSize) 118 | resBuf = make([]byte, exec.ExecResultBufSize) 119 | 120 | comms = make([]comm.GuestComm, exec.CommNum) 121 | 122 | dmesg, err = os.OpenFile("/dev/kmsg", os.O_WRONLY, 0) 123 | if err != nil { 124 | os.Exit(-1) 125 | } 126 | // printk, err := os.OpenFile("/proc/sys/kernel/printk", os.O_RDWR, 0) 127 | // if err != nil { 128 | // os.Exit(-1) 129 | // } 130 | // printk.Write([]byte("7")) 131 | // printk.Close() 132 | 133 | ioutil.WriteFile("/proc/sys/net/ipv4/vs/debug_level", []byte("9\000"), 0666) 134 | 135 | earlyDebugf("executorcd is running, args: %v", os.Args) 136 | earlyDebugf("executorcd is running, args: %v", os.Args) 137 | earlyDebugf("executorcd is running, args: %v", os.Args) 138 | earlyDebugf("executorcd is running, args: %v", os.Args) 139 | earlyDebugf("executorcd is running, args: %v", os.Args) 140 | 141 | flag.Parse() 142 | // init guest communication 143 | for i := 0; i < exec.CommNum; i++ { 144 | c, err := vm.InitGuestComm(*flagVM, *flagComm, i) 145 | if err != nil { 146 | earlyErrf("executord cannot open guest comm: %v", err) 147 | os.Exit(-1) 148 | } 149 | comms[i] = c 150 | } 151 | 152 | err = exec.LoopbackSlave(comms) 153 | if err != nil { 154 | earlyErrf("executord cannot open guest comm: %v", err) 155 | os.Exit(-1) 156 | } 157 | 158 | log.SetOutput(comms[exec.ErrComm]) 159 | log.Printf("Communication establisted!") 160 | printkData, err := ioutil.ReadFile("/proc/sys/kernel/printk") 161 | if err != nil { 162 | errf("read printk error!") 163 | } 164 | log.Printf("Printk arguments: %v", string(printkData)) 165 | aSync1, aSync2, vSync1, vSync2, err := exec.CreateSync() 166 | if err != nil { 167 | errf("create sync error: %v\n", err) 168 | os.Exit(-1) 169 | } 170 | 171 | err = exec.SendFin(comms) 172 | if err != nil { 173 | errf("cannot send finshed signal: %v\n", err) 174 | } 175 | 176 | // –––––––––––––––––––––– Take snapshot –––––––––––––––––––––––– 177 | ctest, err := exec.RecvCTest(comms[exec.InComm], aExecDataBuf) 178 | if err != nil { 179 | earlyDebugf("receive test data error: %v\n", err) 180 | os.Exit(-1) 181 | } 182 | 183 | debugf("attack prog:\n%v\nvictim prog:\n%v\nskip: %v\n", 184 | string(ctest.A.Code), 185 | string(ctest.V.Code), 186 | ctest.SkipA, 187 | ) 188 | 189 | res := &exec.CTestReport{} 190 | wg := &sync.WaitGroup{} 191 | wg.Add(2) 192 | go func() { 193 | var err error 194 | res.ASCTrace, err = compileAndStart(ctest.A, aSync1, aSync2, ctest.SkipA, *flagDebug) 195 | if err != nil { 196 | errf("run attack error: %v\n", err) 197 | os.Exit(-1) 198 | } 199 | wg.Done() 200 | }() 201 | go func() { 202 | var err error 203 | res.VSCTrace, err = compileAndStart(ctest.V, vSync1, vSync2, false, *flagDebug) 204 | if err != nil { 205 | errf("run victim error: %v\n", err) 206 | os.Exit(-1) 207 | } 208 | wg.Done() 209 | }() 210 | wg.Wait() 211 | debugf("test done\n") 212 | // dmesgCmd := osexec.Command("dmesg") 213 | // dmesgOut, err := dmesgCmd.CombinedOutput() 214 | // if err != nil { 215 | // errf("cannot run dmesg: %v\n", err) 216 | // os.Exit(-1) 217 | // } 218 | // debugf("dmesg: %v", string(dmesgOut)) 219 | err = exec.SendCTestReport(comms[exec.OutComm], res, resBuf) 220 | if err != nil { 221 | errf("send test result error: %v\n", err) 222 | os.Exit(-1) 223 | } 224 | debugf("send test result done!\n") 225 | time.Sleep(10000 * time.Second) 226 | } 227 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/rss/kit 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/google/syzkaller v0.0.0 // version is useless 7 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect 8 | golang.org/x/sys v0.0.0-20210925032602-92d5a993a665 9 | google.golang.org/protobuf v1.26.0 // indirect 10 | ) 11 | 12 | replace github.com/google/syzkaller => ./syzkaller 13 | -------------------------------------------------------------------------------- /manager/rpc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "path" 8 | "sync" 9 | "sync/atomic" 10 | "time" 11 | 12 | "github.com/google/syzkaller/pkg/rpctype" 13 | "github.com/google/syzkaller/prog" 14 | "github.com/rss/kit/pgen" 15 | "github.com/rss/kit/result" 16 | "github.com/rss/kit/util" 17 | ) 18 | 19 | const ClientHeartbeatInterval time.Duration = 10 * time.Second 20 | 21 | type ManagerServer interface { 22 | GetVMImage(int, *RPCVMImage) error 23 | GetProgNamePair(int, *RPCProgNamePairBatch) error 24 | NewTestResult(*result.TestResult, *int) error 25 | NewClientInfo(*RPCClientInfo, *int) error 26 | } 27 | 28 | type ManagerCallClient struct { 29 | mu *sync.Mutex // RPCClient's `call` is not atomic 30 | client *rpctype.RPCClient 31 | } 32 | 33 | type ManagerClient struct { 34 | progPairClient *ManagerCallClient 35 | resultClient *ManagerCallClient 36 | clientInfoClient *ManagerCallClient 37 | } 38 | 39 | type RPCVMImage struct { 40 | Image []byte 41 | Key []byte 42 | } 43 | 44 | type RPCProgNamePair struct { 45 | AMeta pgen.ProgMeta 46 | VMeta pgen.ProgMeta 47 | Interleave bool 48 | VPause uint8 49 | } 50 | 51 | type RPCProgNamePairBatch struct { 52 | Pairs []RPCProgNamePair 53 | } 54 | 55 | type RPCClientInfo struct { 56 | Name string 57 | TimeStamp time.Time 58 | Stat *ManagerStat 59 | } 60 | 61 | func (mgr *Manager) GetVMImage(p int, s *RPCVMImage) error { 62 | var err error 63 | log.Printf("reading image and key file...") 64 | s.Image, err = ioutil.ReadFile(mgr.vmenv.Image) 65 | if err != nil { 66 | return fmt.Errorf("cannot open snapshot image: %v", err) 67 | } 68 | s.Key, err = ioutil.ReadFile(mgr.vmenv.SSHKey) 69 | if err != nil { 70 | return fmt.Errorf("cannot open snapshot image: %v", err) 71 | } 72 | log.Printf("reading image and key file done!") 73 | return nil 74 | } 75 | 76 | func (mgr *Manager) GetProgNamePair(n int, pb *RPCProgNamePairBatch) error { 77 | if n <= 0 { 78 | return fmt.Errorf("n should be greater than 0") 79 | } 80 | for n >= 0 { 81 | p, ok := <-mgr.pairCh 82 | if !ok { 83 | break 84 | } 85 | pb.Pairs = append(pb.Pairs, 86 | RPCProgNamePair{ 87 | AMeta: *p.A.Meta, 88 | VMeta: *p.V.Meta, 89 | Interleave: p.Interleave, 90 | VPause: p.VPause, 91 | }) 92 | n-- 93 | } 94 | return nil 95 | } 96 | 97 | func (mgr *Manager) NewTestResult(r *result.TestResult, p *int) error { 98 | err := result.SerializeTestResult(mgr.resultTmpDir, r) 99 | if err != nil { 100 | return fmt.Errorf("cannot serialize new test results: %v", err) 101 | } 102 | return nil 103 | 104 | } 105 | 106 | func (mgr *Manager) NewClientInfo(c *RPCClientInfo, p *int) error { 107 | mgr.clientInfoMu.Lock() 108 | defer mgr.clientInfoMu.Unlock() 109 | mgr.clientInfo[c.Name] = c 110 | return nil 111 | } 112 | 113 | func NewManagerServer(addr string, mgr ManagerServer) (*rpctype.RPCServer, error) { 114 | return rpctype.NewRPCServer(addr, "Server", mgr) 115 | } 116 | 117 | func (c *ManagerCallClient) GetVMImage() ([]byte, []byte, error) { 118 | c.mu.Lock() 119 | defer c.mu.Unlock() 120 | var rpcReply RPCVMImage 121 | err := c.client.Call("Server.GetVMImage", 0, &rpcReply) 122 | if err != nil { 123 | return nil, nil, fmt.Errorf("cannot start GetVMImage rpc: %v", err) 124 | } 125 | 126 | return rpcReply.Image, rpcReply.Key, nil 127 | } 128 | 129 | func (c *ManagerCallClient) GetProgNamePair(n int) ([]RPCProgNamePair, error) { 130 | c.mu.Lock() 131 | defer c.mu.Unlock() 132 | var rpcReply RPCProgNamePairBatch 133 | err := c.client.Call("Server.GetProgNamePair", n, &rpcReply) 134 | if err != nil { 135 | return nil, fmt.Errorf("cannot start GetProgNamePair rpc: %v", err) 136 | } 137 | return rpcReply.Pairs, nil 138 | } 139 | 140 | func (c *ManagerCallClient) NewTestResult(r *result.TestResult) error { 141 | c.mu.Lock() 142 | defer c.mu.Unlock() 143 | err := c.client.Call("Server.NewTestResult", r, nil) 144 | if err != nil { 145 | return fmt.Errorf("cannot start NewTestResult rpc: %v", err) 146 | } 147 | return nil 148 | } 149 | 150 | func (c *ManagerCallClient) NewClientInfo(name string, stat *ManagerStat) error { 151 | ci := &RPCClientInfo{ 152 | Name: name, 153 | Stat: stat, 154 | TimeStamp: time.Now(), 155 | } 156 | err := c.client.Call("Server.NewClientInfo", ci, nil) 157 | if err != nil { 158 | return fmt.Errorf("cannot start NewClientInfo rpc: %v", err) 159 | } 160 | return nil 161 | } 162 | 163 | func NewManagerCallClient(addr string, timeScale time.Duration) (*ManagerCallClient, error) { 164 | var err error 165 | c := &ManagerCallClient{mu: &sync.Mutex{}} 166 | c.client, err = rpctype.NewRPCClient(addr, timeScale) 167 | if err != nil { 168 | return nil, err 169 | } 170 | return c, nil 171 | } 172 | 173 | func NewManagerClient(addr string, timeScale time.Duration) (*ManagerClient, error) { 174 | var err error 175 | c := &ManagerClient{} 176 | c.progPairClient, err = NewManagerCallClient(addr, timeScale) 177 | if err != nil { 178 | return nil, err 179 | } 180 | c.resultClient, err = NewManagerCallClient(addr, timeScale) 181 | if err != nil { 182 | return nil, err 183 | } 184 | c.clientInfoClient, err = NewManagerCallClient(addr, timeScale) 185 | if err != nil { 186 | return nil, err 187 | } 188 | return c, nil 189 | } 190 | 191 | func (c *ManagerClient) NewTestResult(r *result.TestResult) error { 192 | return c.resultClient.NewTestResult(r) 193 | } 194 | 195 | func (c *ManagerClient) GetProgNamePair(n int) ([]RPCProgNamePair, error) { 196 | return c.progPairClient.GetProgNamePair(n) 197 | } 198 | 199 | func (c *ManagerClient) GetVMImage() ([]byte, []byte, error) { 200 | // just use progPair client, this call is only invoked once 201 | return c.progPairClient.GetVMImage() 202 | } 203 | 204 | func (c *ManagerClient) NewClientInfo(name string, stat *ManagerStat) error { 205 | return c.clientInfoClient.NewClientInfo(name, stat) 206 | } 207 | 208 | type ClientTestGenerator struct { 209 | target *prog.Target 210 | progDir string 211 | c *ManagerClient 212 | namePairCache []RPCProgNamePair 213 | downloadBatch int 214 | statPair uint64 215 | } 216 | 217 | func InitClientTestGenerator(target *prog.Target, progDir string, c *ManagerClient, downloadBatch int) *ClientTestGenerator { 218 | return &ClientTestGenerator{ 219 | target: target, 220 | progDir: progDir, 221 | c: c, 222 | downloadBatch: downloadBatch, 223 | } 224 | } 225 | 226 | func (gen *ClientTestGenerator) Generate() (*pgen.ProgPair, error) { 227 | var err error 228 | if len(gen.namePairCache) == 0 { 229 | gen.namePairCache, err = gen.c.GetProgNamePair(gen.downloadBatch) 230 | if err != nil { 231 | return nil, fmt.Errorf("cannot download program pairs: %v", err) 232 | } 233 | if len(gen.namePairCache) == 0 { 234 | return nil, nil 235 | } 236 | } 237 | namePair := gen.namePairCache[0] 238 | gen.namePairCache = gen.namePairCache[1:] 239 | apPath := path.Join(gen.progDir, namePair.AMeta.Name) 240 | ap, err := util.ReadProg(apPath, gen.target, prog.NonStrict) 241 | if err != nil { 242 | return nil, fmt.Errorf("cannot read attack program: %v", err) 243 | } 244 | vpPath := path.Join(gen.progDir, namePair.VMeta.Name) 245 | vp, err := util.ReadProg(vpPath, gen.target, prog.NonStrict) 246 | if err != nil { 247 | return nil, fmt.Errorf("cannot read victim program: %v", err) 248 | } 249 | atomic.AddUint64(&gen.statPair, 1) 250 | return &pgen.ProgPair{ 251 | A: &pgen.ProgGen{ 252 | P: ap, 253 | Meta: &namePair.AMeta, 254 | }, 255 | V: &pgen.ProgGen{ 256 | P: vp, 257 | Meta: &namePair.VMeta, 258 | }, 259 | Interleave: namePair.Interleave, 260 | VPause: namePair.VPause, 261 | }, nil 262 | } 263 | 264 | func (gen *ClientTestGenerator) Log() string { 265 | nPair := atomic.LoadUint64(&gen.statPair) 266 | return fmt.Sprintf("statPair = %v", nPair) 267 | } 268 | -------------------------------------------------------------------------------- /ns/analysis.go: -------------------------------------------------------------------------------- 1 | package ns 2 | 3 | import ( 4 | "log" 5 | "runtime" 6 | 7 | "github.com/google/syzkaller/prog" 8 | _ "github.com/google/syzkaller/sys" 9 | ) 10 | 11 | type NSType int 12 | 13 | const ( 14 | NetNS = (NSType)(iota) 15 | IPCNS 16 | MntNS 17 | PidNS 18 | UserNS 19 | ) 20 | 21 | type ResourceInfoTable map[*prog.ResourceDesc]bool 22 | 23 | // call signature -> call description 24 | type FdDefCallTable map[*prog.Syscall][]*CallDesc 25 | 26 | // call signature -> call description 27 | type NonFdCallTable map[*prog.Syscall][]*CallDesc 28 | 29 | func GenResourcesInfoTable(target *prog.Target, spec map[NSType]SpecDescription) (ResourceInfoTable, FdDefCallTable, NonFdCallTable) { 30 | fdNameTable := make(ResourceInfoTable) 31 | fdDefCallTable := make(FdDefCallTable) 32 | nonFdCallTable := make(NonFdCallTable) 33 | fdNameMap := map[string]bool{} 34 | for _, desc := range spec { 35 | for _, name := range desc.FdNames { 36 | fdNameMap[name] = true 37 | } 38 | for _, callDesc := range desc.FdDefCall { 39 | if c, ok := target.SyscallMap[callDesc.CallName]; ok { 40 | if _, ok := fdDefCallTable[c]; !ok { 41 | fdDefCallTable[c] = []*CallDesc{} 42 | } 43 | fdDefCallTable[c] = append(fdDefCallTable[c], &callDesc) 44 | } 45 | } 46 | for _, callDesc := range desc.NonFdCall { 47 | if c, ok := target.SyscallMap[callDesc.CallName]; ok { 48 | if _, ok := nonFdCallTable[c]; !ok { 49 | nonFdCallTable[c] = []*CallDesc{} 50 | } 51 | nonFdCallTable[c] = append(nonFdCallTable[c], &callDesc) 52 | } 53 | } 54 | } 55 | for _, r := range target.Resources { 56 | if fdNameMap[r.Name] { 57 | fdNameTable[r] = true 58 | } 59 | } 60 | return fdNameTable, fdDefCallTable, nonFdCallTable 61 | } 62 | 63 | type RsDefUseInfo struct { 64 | RsName string 65 | RsRef *RsDefUseInfo // nil when using special values 66 | Def bool 67 | Dup bool 68 | Use bool 69 | } 70 | 71 | type CallMatchInfo struct { 72 | Name string 73 | RsInfo []*RsDefUseInfo 74 | } 75 | 76 | type ResourceTable map[*prog.ResultArg]*RsDefUseInfo 77 | type ProgAnalyzeRes map[int]*CallMatchInfo 78 | 79 | func useResourceArgs(a *prog.ResultArg, table ResourceTable) (bool, *RsDefUseInfo) { 80 | res := a 81 | for res != nil { 82 | if info, ok := table[res]; ok { 83 | return true, info 84 | } 85 | res = res.Res 86 | } 87 | return false, nil 88 | } 89 | 90 | func ResourceCallAnalysis(p *prog.Prog) ProgAnalyzeRes { 91 | m := ProgAnalyzeRes{} 92 | rsArgs := ResourceTable{} 93 | for i, call := range p.Calls { 94 | switch call.Meta.Name { 95 | case `dup`, `dup2`, `dup3`, `fcntl$dupfd`: 96 | // update resource table 97 | // Don't label this call as resource def since it is not creating 98 | // a new reousrce, instead just add a new reference to existing one 99 | a := call.Args[0].(*prog.ResultArg) 100 | if use, rsDefUseInfo := useResourceArgs(a, rsArgs); use { 101 | newRsDefUseInfo := &RsDefUseInfo{ 102 | RsName: rsDefUseInfo.RsName, 103 | RsRef: rsDefUseInfo, 104 | Dup: true, 105 | } 106 | rsArgs[call.Ret] = newRsDefUseInfo 107 | if _, ok := m[i]; !ok { 108 | m[i] = &CallMatchInfo{Name: call.Meta.Name} 109 | } 110 | m[i].RsInfo = append(m[i].RsInfo, newRsDefUseInfo) 111 | continue 112 | } 113 | } 114 | prog.ForeachArg(call, func(arg prog.Arg, ctx *prog.ArgCtx) { 115 | switch ty := arg.Type().(type) { 116 | case *prog.ResourceType: 117 | a := arg.(*prog.ResultArg) 118 | if a.Dir() == prog.DirIn { 119 | // check resource use 120 | if use, rsDefUseInfo := useResourceArgs(a, rsArgs); use { 121 | if _, ok := m[i]; !ok { 122 | m[i] = &CallMatchInfo{Name: call.Meta.Name} 123 | } 124 | // use def resource 125 | m[i].RsInfo = append(m[i].RsInfo, &RsDefUseInfo{ 126 | RsName: rsDefUseInfo.RsName, 127 | RsRef: rsDefUseInfo, 128 | Use: true, 129 | }) 130 | } else if RsTable[ty.Desc] { 131 | // use resource special value 132 | for _, sv := range ty.SpecialValues() { 133 | if a.Val == sv { 134 | if _, ok := m[i]; !ok { 135 | m[i] = &CallMatchInfo{Name: call.Meta.Name} 136 | } 137 | m[i].RsInfo = append(m[i].RsInfo, &RsDefUseInfo{ 138 | RsName: ty.Desc.Name, 139 | Use: true, 140 | }) 141 | } 142 | } 143 | } 144 | } else if a.Dir() == prog.DirOut { 145 | // check resource def 146 | if RsTable[ty.Desc] { 147 | newRsDefUseInfo := &RsDefUseInfo{ 148 | RsName: ty.Desc.Name, 149 | Def: true, 150 | } 151 | rsArgs[a] = newRsDefUseInfo 152 | if _, ok := m[i]; !ok { 153 | m[i] = &CallMatchInfo{Name: call.Meta.Name} 154 | } 155 | m[i].RsInfo = append(m[i].RsInfo, newRsDefUseInfo) 156 | } 157 | } 158 | } 159 | 160 | }) 161 | if callDescArr, ok := FdCallTable[call.Meta]; ok { 162 | for _, callDesc := range callDescArr { 163 | if callDesc.CallName == call.Meta.Name && callDesc.Checker(call) { 164 | if _, ok := m[i]; !ok { 165 | m[i] = &CallMatchInfo{Name: call.Meta.Name} 166 | } 167 | newInfo := &RsDefUseInfo{ 168 | RsName: "fd$" + callDesc.GetName(call), 169 | Def: true, 170 | } 171 | // add to arg table to trace use 172 | rsArgs[call.Ret] = newInfo 173 | m[i].RsInfo = append(m[i].RsInfo, newInfo) 174 | } 175 | } 176 | } 177 | if callDescArr, ok := NFdCallTable[call.Meta]; ok { 178 | for _, callDesc := range callDescArr { 179 | if callDesc.CallName == call.Meta.Name && callDesc.Checker(call) { 180 | if _, ok := m[i]; !ok { 181 | m[i] = &CallMatchInfo{} 182 | } 183 | m[i].Name = callDesc.GetName(call) 184 | } 185 | } 186 | } 187 | 188 | } 189 | return m 190 | } 191 | 192 | // Naive interleave algorithm: 193 | // Interleaves only when victim system call is resource related 194 | // Probably we could involve memory trace. 195 | func Interleave(vp *prog.Prog) []uint8 { 196 | vpPause := []uint8{} 197 | a := ResourceCallAnalysis(vp) 198 | for call := range a { 199 | vpPause = append(vpPause, uint8(call)) 200 | } 201 | return vpPause 202 | } 203 | 204 | var RsTable ResourceInfoTable 205 | var FdCallTable FdDefCallTable 206 | var NFdCallTable NonFdCallTable 207 | 208 | func init() { 209 | target, err := prog.GetTarget(runtime.GOOS, runtime.GOARCH) 210 | if err != nil { 211 | log.Fatalf("cannot get program target: %v", err) 212 | } 213 | RsTable, FdCallTable, NFdCallTable = GenResourcesInfoTable(target, ResourceSpec) 214 | } 215 | -------------------------------------------------------------------------------- /ns/analysis_test.go: -------------------------------------------------------------------------------- 1 | package ns 2 | 3 | import ( 4 | "runtime" 5 | "testing" 6 | 7 | "github.com/google/syzkaller/prog" 8 | _ "github.com/google/syzkaller/sys" 9 | ) 10 | 11 | func TestResourceCallAnalysis(t *testing.T) { 12 | // rsNames := map[NSType]SpecDescription{ 13 | // NetNS: { 14 | // FdNames: []string{ 15 | // "sock_unix", 16 | // "sock_alg", 17 | // "genl_tipc_family_id", 18 | // `genl_ipvs_family_id`, 19 | // }, 20 | // FdDefCall: []CallDesc{ 21 | // { 22 | // CallName: `openat$procfs`, 23 | // Checker: func(c *prog.Call) bool { 24 | // dataRes := c.Args[1].(*prog.PointerArg).Res 25 | // if dataRes == nil { 26 | // return false 27 | // } 28 | // fn := string(dataRes.(*prog.DataArg).Data()) 29 | // fmt.Printf("proc = %v\n", fn) 30 | // return strings.Contains(fn, `sysvipc/`) 31 | // }, 32 | // }, 33 | // { 34 | // CallName: `syz_open_procfs`, 35 | // Checker: func(c *prog.Call) bool { 36 | // dataRes := c.Args[1].(*prog.PointerArg).Res 37 | // if dataRes == nil { 38 | // return false 39 | // } 40 | // fn := string(dataRes.(*prog.DataArg).Data()) 41 | // fmt.Printf("proc = %v\n", fn) 42 | // return strings.Contains(fn, `net/`) 43 | // }, 44 | // }, 45 | // }, 46 | // }, 47 | // } 48 | progStrs := []string{ 49 | `r0 = socket$unix(0x1, 0x2, 0x0) 50 | r1 = openat$procfs(0xffffffffffffff9c, &(0x7f00000004c0)='/proc/asound/seq/clients\x00', 0x0, 0x0) 51 | openat$procfs(0xffffff9c, 0x0, 0x0, 0x0) 52 | r3 = syz_open_procfs(0x0, &(0x7f0000000040)='net/snmp\x00') 53 | r2 = dup2(r0, r1) 54 | connect$unix(r2, &(0x7f0000000040)=@file={0x0, './file0\x00'}, 0x6e) 55 | close(r1) 56 | close(r2) 57 | close(r3)`, 58 | `dup2(0xffffffffffffffff, 0xffffffffffffffff)`, 59 | `accept4$alg(0xffffffffffffffff, 0x0, 0x0, 0x400)`, 60 | `r0 = socket$nl_generic(0x10, 0x3, 0x10) 61 | r1 = syz_genetlink_get_family_id$tipc(&(0x7f0000000040)='TIPC\x00', 0xffffffffffffffff) 62 | sendmsg$TIPC_CMD_SET_NODE_ADDR(r0, &(0x7f0000000100)={&(0x7f0000000000), 0xc, &(0x7f00000000c0)={&(0x7f0000000140)={0x24, r1, 0x1, 0x0, 0x0, {{}, {}, {0x8}}}, 0x24}}, 0x0)`, 63 | `socketpair$unix(0x1, 0x5, 0x0, &(0x7f0000000000)={0xffffffffffffffff, 0xffffffffffffffff})`, 64 | 65 | `r0 = socket$nl_generic(0x10, 0x3, 0x10) 66 | r1 = syz_genetlink_get_family_id$ipvs(&(0x7f0000002000)='IPVS\x00', 0xffffffffffffffff) 67 | sendmsg$IPVS_CMD_GET_CONFIG(r0, &(0x7f00000021c0)={0x0, 0x0, &(0x7f0000002180)={&(0x7f0000002040)={0x14, r1, 0x421}, 0x14}}, 0x0)`, 68 | } 69 | target, err := prog.GetTarget(runtime.GOOS, runtime.GOARCH) 70 | if err != nil { 71 | t.Fatalf("cannot get target: %v", err) 72 | } 73 | // rsMap, fdCallTb, nonFdCallTb := GenResourcesInfoTable(target, rsNames) 74 | for i, pStr := range progStrs { 75 | t.Logf("Test case %d", i) 76 | p, err := target.Deserialize([]byte(pStr), prog.NonStrict) 77 | if err != nil { 78 | t.Errorf("cannot parse program: %v", err) 79 | } 80 | l := ResourceCallAnalysis(p) 81 | for call, callMatchInfo := range l { 82 | t.Logf("call %v: %v", call, callMatchInfo.Name) 83 | for _, defUseInfo := range callMatchInfo.RsInfo { 84 | t.Logf("rs = %v, def = %v, use = %v, dup = %v", 85 | defUseInfo.RsName, defUseInfo.Def, defUseInfo.Use, defUseInfo.Dup) 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /ns/spec.go: -------------------------------------------------------------------------------- 1 | package ns 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/google/syzkaller/prog" 8 | ) 9 | 10 | /* 11 | Note: 12 | - fd_fanotify/fd_inotify: mount? 13 | - fd_rdma: net? 14 | - fd_fscontext: mount? 15 | - fd_perf: ? 16 | - fd_devlink: reload operation allow device change net namespace, thus not allowed here 17 | - sock_nl_generic: ignore, there are specific netlink socket for certain device 18 | - genl_ieee802154_family_id/genl_nl802154_family_id: not namespaced 19 | - genl_nbd_family_id: network block device is namespaced? 20 | - hafnium: aarch type-1 hypervisor 21 | - isdn: legacy CAPI api 22 | - qrtr: interface communicating with services running on co-processors in Qualcomm platforms, guess not namespaced 23 | - sock creation source code checks the net must equal to init_net: 24 | - llc 25 | - netrom 26 | - nfc 27 | - rose 28 | - ax25 29 | - x25 30 | - hf (econet) 31 | - ipx 32 | - vsock does not support net ns 33 | - bt: reject net namespace supporting patch due to worring of information leakage: 34 | https://lore.kernel.org/lkml/20170814071640.289327-1-fupan.li@windriver.com/ 35 | - rds_sock net ns support is incomplete (according to developer) 36 | 37 | */ 38 | 39 | type CallCheckFunc func(*prog.Call) bool 40 | type NameFunc func(*prog.Call) string 41 | 42 | type CallDesc struct { 43 | CallName string 44 | Checker CallCheckFunc 45 | GetName NameFunc 46 | } 47 | 48 | type SpecDescription struct { 49 | FdNames []string 50 | FdDefCall []CallDesc 51 | NonFdCall []CallDesc 52 | } 53 | 54 | var defaultGetName = func(c *prog.Call) string { 55 | return c.Meta.Name 56 | } 57 | var defaultChecker = func(c *prog.Call) bool { 58 | return true 59 | } 60 | 61 | var ResourceSpec = map[NSType]SpecDescription{ 62 | UserNS: { 63 | FdNames: []string{ 64 | "uid", 65 | "gid", 66 | }, 67 | }, 68 | PidNS: { 69 | FdNames: []string{ 70 | "pid", 71 | }, 72 | }, 73 | MntNS: { 74 | FdDefCall: []CallDesc{ 75 | { 76 | CallName: `syz_open_procfs`, 77 | Checker: func(c *prog.Call) bool { 78 | dataRes := c.Args[1].(*prog.PointerArg).Res 79 | if dataRes == nil { 80 | return false 81 | } 82 | fn := string(dataRes.(*prog.DataArg).Data()) 83 | mountFiles := []string{"mounts", "mountinfo", "mountstats"} 84 | match := false 85 | for _, f := range mountFiles { 86 | if strings.Contains(fn, f) { 87 | match = true 88 | break 89 | } 90 | } 91 | return match 92 | }, 93 | GetName: func(c *prog.Call) string { 94 | name := c.Meta.Name 95 | dataRes := c.Args[1].(*prog.PointerArg).Res 96 | if dataRes == nil { 97 | return name 98 | } 99 | fn := string(dataRes.(*prog.DataArg).Data()) 100 | return fmt.Sprintf(`%v(/proc/%v)`, name, fn) 101 | }, 102 | }, 103 | }, 104 | NonFdCall: []CallDesc{ 105 | { 106 | CallName: `mount`, 107 | Checker: defaultChecker, 108 | GetName: defaultGetName, 109 | }, 110 | { 111 | CallName: `umount2`, 112 | Checker: defaultChecker, 113 | GetName: defaultGetName, 114 | }, 115 | { 116 | CallName: `fsmount`, 117 | Checker: defaultChecker, 118 | GetName: defaultGetName, 119 | }, 120 | { 121 | CallName: `move_mount`, 122 | Checker: defaultChecker, 123 | GetName: defaultGetName, 124 | }, 125 | // below fs are allowed to mount in user namespace 126 | { 127 | CallName: `mount$tmpfs`, 128 | Checker: defaultChecker, 129 | GetName: defaultGetName, 130 | }, 131 | { 132 | CallName: `mount$overlay`, 133 | Checker: defaultChecker, 134 | GetName: defaultGetName, 135 | }, 136 | { 137 | CallName: `mount$bind`, 138 | Checker: defaultChecker, 139 | GetName: defaultGetName, 140 | }, 141 | { 142 | CallName: `open`, 143 | Checker: defaultChecker, 144 | GetName: defaultGetName, 145 | }, 146 | { 147 | CallName: `open$dir`, 148 | Checker: defaultChecker, 149 | GetName: defaultGetName, 150 | }, 151 | { 152 | CallName: `openat$dir`, 153 | Checker: defaultChecker, 154 | GetName: defaultGetName, 155 | }, 156 | { 157 | CallName: `openat`, 158 | Checker: defaultChecker, 159 | GetName: defaultGetName, 160 | }, 161 | { 162 | CallName: `openat2$dir`, 163 | Checker: defaultChecker, 164 | GetName: defaultGetName, 165 | }, 166 | { 167 | CallName: `openat2`, 168 | Checker: defaultChecker, 169 | GetName: defaultGetName, 170 | }, 171 | { 172 | CallName: `creat`, 173 | Checker: defaultChecker, 174 | GetName: defaultGetName, 175 | }, 176 | }, 177 | }, 178 | IPCNS: { 179 | FdNames: []string{ 180 | "ipc_msq", 181 | "ipc_sem", 182 | "ipc_shm", 183 | "fd_mq", 184 | }, 185 | FdDefCall: []CallDesc{ 186 | { 187 | CallName: `openat$procfs`, 188 | Checker: func(c *prog.Call) bool { 189 | dataRes := c.Args[1].(*prog.PointerArg).Res 190 | if dataRes == nil { 191 | return false 192 | } 193 | fn := string(dataRes.(*prog.DataArg).Data()) 194 | return strings.Contains(fn, `sysvipc/`) 195 | }, 196 | GetName: func(c *prog.Call) string { 197 | name := c.Meta.Name 198 | dataRes := c.Args[1].(*prog.PointerArg).Res 199 | if dataRes == nil { 200 | return name 201 | } 202 | fn := string(dataRes.(*prog.DataArg).Data()) 203 | return fmt.Sprintf(`%v(/proc/%v)`, name, fn) 204 | }, 205 | }, 206 | }, 207 | }, 208 | NetNS: { 209 | FdDefCall: []CallDesc{ 210 | { 211 | CallName: `syz_open_procfs`, 212 | Checker: func(c *prog.Call) bool { 213 | dataRes := c.Args[1].(*prog.PointerArg).Res 214 | if dataRes == nil { 215 | return false 216 | } 217 | fn := string(dataRes.(*prog.DataArg).Data()) 218 | return strings.Contains(fn, `net/`) 219 | }, 220 | GetName: func(c *prog.Call) string { 221 | name := c.Meta.Name 222 | dataRes := c.Args[1].(*prog.PointerArg).Res 223 | if dataRes == nil { 224 | return name 225 | } 226 | fn := string(dataRes.(*prog.DataArg).Data()) 227 | return fmt.Sprintf(`%v(/proc/%v)`, name, fn) 228 | }, 229 | }, 230 | }, 231 | FdNames: []string{ 232 | `sock_caif`, 233 | "batadv_hard_ifindex", 234 | "batadv_mesh_ifindex", 235 | "genl_batadv_family_id", 236 | "genl_ethtool_family_id", 237 | "genl_fou_family_id", 238 | "genl_ipvs_family_id", 239 | "genl_l2tp_family_id", 240 | "genl_mptcp_family_id", 241 | "genl_smc_family_id", 242 | "genl_team_family_id", 243 | "ifindex_team", 244 | "genl_tipc_family_id", 245 | "genl_tipc2_family_id", 246 | "genl_wireguard_family_id", 247 | "wireguard_ifindex", 248 | "sock_diag", 249 | "sock_dccp", 250 | "sock_dccp6", 251 | "sock_icmp", 252 | "sock_icmp6", 253 | "sock_in", 254 | "sock_in6", 255 | "sock_l2tp", 256 | "sock_l2tp6", 257 | "sock_netlink", 258 | "sock_nl_netfilter", 259 | "sock_nl_route", 260 | "sock_nl_crypto", 261 | "sock_nl_audit", 262 | "sock_nl_xfrm", 263 | "sock_phonet", 264 | "sock_phonet_dgram", 265 | "sock_phonet_pipe", 266 | "sock_pppl2tp", 267 | "sock_pppoe", 268 | "sock_pppox", 269 | // "sock_pptp", 270 | "sock_rxrpc", 271 | "sock_sctp", 272 | "sock_sctp6", 273 | "sock_tcp", 274 | "sock_tcp6", 275 | "sock_udp", 276 | "sock_udp6", 277 | "sock_tipc", 278 | "sock_xdp", 279 | "sock_unix", 280 | "sock_rds", 281 | "sock_can_raw", 282 | "sock_can_bcm", 283 | }, 284 | }, 285 | } 286 | -------------------------------------------------------------------------------- /old_bugs/multicast/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "4e7b2f1454382b220f792a7fbcbebd0985187161", 3 | "kernel": { 4 | "commit": "42ca0203fd59aa9be7b241be1fbc3bef1f903f9c", 5 | "config": "./.config", 6 | "gcc": "gcc-4.9" 7 | }, 8 | "image": { 9 | "release": "wheezy", 10 | "fs": "ext3", 11 | "mirror": "http://archive.debian.org/debian" 12 | } 13 | } -------------------------------------------------------------------------------- /old_bugs/multicast/multicast.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | // #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \ 26 | } while (0) 27 | // https://backreference.org/2010/03/26/tuntap-interface-tutorial/ 28 | // alloc tun/tap device 29 | int tun_alloc(char *dev, int flags) { 30 | 31 | struct ifreq ifr; 32 | int fd, err; 33 | char *clonedev = "/dev/net/tun"; 34 | 35 | /* Arguments taken by the function: 36 | * 37 | * char *dev: the name of an interface (or '\0'). MUST have enough 38 | * space to hold the interface name if '\0' is passed 39 | * int flags: interface flags (eg, IFF_TUN etc.) 40 | */ 41 | 42 | /* open the clone device */ 43 | if( (fd = open(clonedev, O_RDWR)) < 0 ) { 44 | errExit("open fd"); 45 | } 46 | 47 | /* preparation of the struct ifr, of type "struct ifreq" */ 48 | memset(&ifr, 0, sizeof(ifr)); 49 | 50 | ifr.ifr_flags = flags; /* IFF_TUN or IFF_TAP, plus maybe IFF_NO_PI */ 51 | 52 | if (*dev) { 53 | /* if a device name was specified, put it in the structure; otherwise, 54 | * the kernel will try to allocate the "next" device of the 55 | * specified type */ 56 | strncpy(ifr.ifr_name, dev, IFNAMSIZ); 57 | } 58 | 59 | /* try to create the device */ 60 | if( (err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0 ) { 61 | errExit("tun ioctl"); 62 | } 63 | 64 | /* if the operation was successful, write back the name of the 65 | * interface to the variable "dev", so the caller can know 66 | * it. Note that the caller MUST reserve space in *dev (see calling 67 | * code below) */ 68 | strcpy(dev, ifr.ifr_name); 69 | 70 | /* this is the special file descriptor that the caller will use to talk 71 | * with the virtual interface */ 72 | return fd; 73 | } 74 | 75 | int open_netlink() 76 | { 77 | struct sockaddr_nl saddr; 78 | 79 | int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); 80 | 81 | if (sock < 0) { 82 | perror("Failed to open netlink socket"); 83 | return -1; 84 | } 85 | 86 | memset(&saddr, 0, sizeof(saddr)); 87 | 88 | return sock; 89 | } 90 | 91 | #define NLMSG_TAIL(nmsg) \ 92 | ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) 93 | 94 | /* Add new data to rtattr */ 95 | int rtattr_add(struct nlmsghdr *n, int maxlen, int type, const void *data, int alen) 96 | { 97 | int len = RTA_LENGTH(alen); 98 | struct rtattr *rta; 99 | 100 | if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) { 101 | fprintf(stderr, "rtattr_add error: message exceeded bound of %d\n", maxlen); 102 | return -1; 103 | } 104 | 105 | rta = NLMSG_TAIL(n); 106 | rta->rta_type = type; 107 | rta->rta_len = len; 108 | 109 | if (alen) { 110 | memcpy(RTA_DATA(rta), data, alen); 111 | } 112 | 113 | n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); 114 | 115 | return 0; 116 | } 117 | 118 | void set_flag(const char *ifn, short int flag) { 119 | int s = open_netlink(); 120 | struct { 121 | struct nlmsghdr n; 122 | struct ifinfomsg r; 123 | } nl_request; 124 | memset(&nl_request, 0, sizeof(nl_request)); 125 | nl_request.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); 126 | nl_request.n.nlmsg_flags = NLM_F_REQUEST; 127 | nl_request.n.nlmsg_type = RTM_NEWLINK; 128 | nl_request.r.ifi_flags = flag; 129 | nl_request.r.ifi_index = if_nametoindex(ifn); 130 | nl_request.r.ifi_change = flag; 131 | if (send(s, &nl_request, nl_request.n.nlmsg_len, 0) < 0) { 132 | errExit("send"); 133 | } 134 | } 135 | 136 | void set_ipv4(const char *ifn, const char *ip, int prefixlen) { 137 | int s = open_netlink(); 138 | if (s < 0) { 139 | errExit("socket"); 140 | } 141 | struct { 142 | struct nlmsghdr n; 143 | struct ifaddrmsg r; 144 | char buf[256]; 145 | } nl_request; 146 | memset(&nl_request, 0, sizeof(nl_request)); 147 | nl_request.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); 148 | nl_request.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL; 149 | nl_request.n.nlmsg_type = RTM_NEWADDR; 150 | nl_request.r.ifa_family = AF_INET; 151 | nl_request.r.ifa_index = if_nametoindex(ifn); 152 | nl_request.r.ifa_prefixlen = prefixlen; 153 | int ip_addr = inet_addr(ip); 154 | rtattr_add(&nl_request.n, sizeof(nl_request), IFA_LOCAL, &ip_addr, 4); 155 | rtattr_add(&nl_request.n, sizeof(nl_request), IFA_ADDRESS, &ip_addr, 4); 156 | if (send(s, &nl_request, nl_request.n.nlmsg_len, 0) < 0) { 157 | errExit("send"); 158 | } 159 | } 160 | 161 | unsigned short checksum(unsigned short* buff, int _16bitword) 162 | { 163 | unsigned long sum; 164 | for (sum = 0; _16bitword > 0; _16bitword--) 165 | sum += *(buff)++; 166 | sum = ((sum >> 16) + (sum & 0xFFFF)); 167 | sum += (sum>>16); 168 | return (unsigned short)(~sum); 169 | } 170 | 171 | static bool write_file(const char* file, const char* what, ...) 172 | { 173 | char buf[1024]; 174 | va_list args; 175 | va_start(args, what); 176 | vsnprintf(buf, sizeof(buf), what, args); 177 | va_end(args); 178 | buf[sizeof(buf) - 1] = 0; 179 | int len = strlen(buf); 180 | 181 | int fd = open(file, O_WRONLY | O_CLOEXEC); 182 | if (fd == -1) 183 | return false; 184 | if (write(fd, buf, len) != len) { 185 | int err = errno; 186 | close(fd); 187 | errno = err; 188 | return false; 189 | } 190 | close(fd); 191 | return true; 192 | } 193 | 194 | int main() { 195 | char x; 196 | int pipe_fd[2], pipe_fd1[2]; 197 | if (pipe(pipe_fd) < 0) { 198 | errExit("pipe"); 199 | } 200 | if (pipe(pipe_fd1) < 0) { 201 | errExit("pipe1"); 202 | } 203 | // set value here since kernel incorrectly refer to init_net 204 | if (write_file("/proc/sys/net/ipv4/icmp_echo_ignore_broadcasts", "0") == false) { 205 | errExit("disable ignoring icmp multicast"); 206 | } 207 | // system("mount -t sysfs sysfs /sys"); 208 | 209 | int pid = fork(); 210 | if (pid < 0) { 211 | errExit("fork"); 212 | } 213 | if (pid == 0) { 214 | close(pipe_fd[1]); 215 | close(pipe_fd1[0]); 216 | char tun_name[IFNAMSIZ]; 217 | strcpy(tun_name, "tun0"); 218 | int tunfd = tun_alloc(tun_name, IFF_TUN | IFF_NO_PI | IFF_TUN_EXCL); 219 | set_ipv4(tun_name, "172.16.1.1", 24); 220 | set_flag(tun_name, IFF_UP); 221 | if (write(pipe_fd1[1], &x, 1) < 0) { 222 | errExit("write"); 223 | } 224 | if (read(pipe_fd[0], &x, 1) < 0) { 225 | errExit("read"); 226 | } 227 | char buffer[100] = {0}; 228 | int nread = read(tunfd,buffer,sizeof(buffer)); 229 | printf("len = %d\n", nread); 230 | return 0; 231 | } 232 | close(pipe_fd[0]); 233 | close(pipe_fd1[1]); 234 | // wait for init_net set tun device 235 | if (read(pipe_fd1[0], &x, 1) < 0) { 236 | errExit("read"); 237 | } 238 | 239 | // start detector 240 | if (unshare(CLONE_NEWNET | CLONE_NEWNS) < 0) { 241 | errExit("unshare"); 242 | } 243 | // system("ip tuntap add dev tun0 mode tun"); 244 | // system("ip addr add 172.16.1.1/24 dev tun0"); 245 | // system("ip link set tun0 up"); 246 | char tun_name[IFNAMSIZ]; 247 | strcpy(tun_name, "tun0"); 248 | int tunfd = tun_alloc(tun_name, IFF_TUN | IFF_NO_PI | IFF_TUN_EXCL); 249 | set_ipv4(tun_name, "172.16.1.1", 24); 250 | set_flag(tun_name, IFF_UP); 251 | 252 | // https://www.opensourceforu.com/2015/03/a-guide-to-using-raw-sockets/ 253 | char sendbuff[64] = {0}; 254 | int total_len = 0; 255 | struct iphdr *iph = (struct iphdr*)(sendbuff); 256 | iph->ihl = 5; 257 | iph->version = 4; 258 | iph->tos = 16; 259 | iph->id = htons(10201); 260 | iph->ttl = 64; 261 | iph->protocol = 1; // ICMP 262 | iph->saddr = inet_addr("172.16.1.2"); 263 | iph->daddr = inet_addr("224.0.0.1"); 264 | total_len += sizeof(struct iphdr); 265 | struct icmphdr *icmph = (struct icmphdr *)(sendbuff + sizeof(struct iphdr)); 266 | icmph->type = ICMP_ECHO; 267 | icmph->un.echo.id = 1234; 268 | icmph->un.echo.sequence = 5678; 269 | icmph->checksum = checksum((unsigned short *)icmph, (sizeof(struct icmphdr)/2)); 270 | total_len += sizeof(struct icmphdr); 271 | iph->tot_len = htons(total_len); 272 | iph->check = checksum((unsigned short*)(iph), (sizeof(struct iphdr)/2)); 273 | if (write(tunfd, sendbuff, total_len) < 0) { 274 | errExit("write tun"); 275 | } 276 | if (write(pipe_fd[1], &x, 1) < 0) { 277 | errExit("write"); 278 | } 279 | wait(NULL); 280 | return 0; 281 | } -------------------------------------------------------------------------------- /old_bugs/nf_contrack/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "e77e6ff502ea3d193872b5b9033bfd9717b36447", 3 | "kernel": { 4 | "commit": "d2fbdf76b85bcdfe57b8ef2ba09d20e8ada79abd", 5 | "config": "./.config", 6 | "gcc": "gcc-4.9" 7 | }, 8 | "image": { 9 | "release": "wheezy", 10 | "fs": "ext3", 11 | "mirror": "http://archive.debian.org/debian" 12 | } 13 | } -------------------------------------------------------------------------------- /old_bugs/nf_contrack/nf_contrack.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \ 19 | } while (0) 20 | 21 | int main() { 22 | char x; 23 | int pipe_fd[2], pipe_fd1[2]; 24 | if (pipe(pipe_fd) < 0) { 25 | errExit("pipe"); 26 | } 27 | if (pipe(pipe_fd1) < 0) { 28 | errExit("pipe1"); 29 | } 30 | 31 | int pid = fork(); 32 | if (pid < 0) { 33 | errExit("fork"); 34 | } 35 | if (pid == 0) { 36 | close(pipe_fd[1]); 37 | close(pipe_fd1[0]); 38 | unshare(CLONE_NEWNET); 39 | if (read(pipe_fd[0], &x, 1) < 0) { 40 | errExit("read"); 41 | } 42 | system("cat /proc/net/nf_conntrack"); 43 | return 0; 44 | } 45 | close(pipe_fd[0]); 46 | close(pipe_fd1[1]); 47 | int s; 48 | if ((s=socket(AF_INET, SOCK_STREAM, 0))< 0) { 49 | errExit("socket"); 50 | } 51 | struct sockaddr_in addr_bind = { 52 | .sin_family=AF_INET, 53 | .sin_addr=inet_addr("127.0.0.1"), 54 | .sin_port=htons(12341) 55 | }; 56 | struct sockaddr_in addr_to = {0}; 57 | addr_to.sin_family = AF_INET; 58 | addr_to.sin_port = htons(3000); 59 | addr_to.sin_addr.s_addr = inet_addr("127.0.0.1"); 60 | int pid1 = fork(); 61 | if (pid1 < 0) { 62 | errExit("fork"); 63 | } 64 | if (pid1 == 0) { 65 | if ((s=socket(AF_INET, SOCK_STREAM, 0))< 0) { 66 | errExit("socket"); 67 | } 68 | if (bind(s, &addr_to, sizeof(addr_to)) < 0) { 69 | errExit("bind"); 70 | } 71 | listen(s, 10); 72 | struct sockaddr_in addr_in = {0}; 73 | int len; 74 | accept(s, &addr_in, &len); 75 | return 0; 76 | } 77 | // if (bind(s, &addr_bind, sizeof(addr_bind)) < 0) { 78 | // errExit("bind shit"); 79 | // } 80 | sleep(1); 81 | if (connect(s, &addr_to, sizeof(struct sockaddr_in)) < 0) { 82 | errExit("connect"); 83 | } 84 | if (write(pipe_fd[1], &x, 1) < 0) { 85 | errExit("write"); 86 | } 87 | wait(NULL); 88 | return 0; 89 | } -------------------------------------------------------------------------------- /old_bugs/pneigh/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "df07a94cf50eb73d09bf2350c3fe2598e4cbeee1", 3 | "kernel": { 4 | "commit": "42ca0203fd59aa9be7b241be1fbc3bef1f903f9c", 5 | "config": "./.config", 6 | "gcc": "gcc-4.9" 7 | }, 8 | "image": { 9 | "release": "wheezy", 10 | "fs": "ext3", 11 | "mirror": "http://archive.debian.org/debian" 12 | } 13 | } -------------------------------------------------------------------------------- /old_bugs/pneigh/pneigh.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \ 20 | } while (0) 21 | int open_netlink() 22 | { 23 | struct sockaddr_nl saddr; 24 | 25 | int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); 26 | 27 | if (sock < 0) { 28 | perror("Failed to open netlink socket"); 29 | return -1; 30 | } 31 | 32 | memset(&saddr, 0, sizeof(saddr)); 33 | 34 | return sock; 35 | } 36 | 37 | #define NLMSG_TAIL(nmsg) \ 38 | ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) 39 | 40 | /* Add new data to rtattr */ 41 | int rtattr_add(struct nlmsghdr *n, int maxlen, int type, const void *data, int alen) 42 | { 43 | int len = RTA_LENGTH(alen); 44 | struct rtattr *rta; 45 | 46 | if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) { 47 | fprintf(stderr, "rtattr_add error: message exceeded bound of %d\n", maxlen); 48 | return -1; 49 | } 50 | 51 | rta = NLMSG_TAIL(n); 52 | rta->rta_type = type; 53 | rta->rta_len = len; 54 | 55 | if (alen) { 56 | memcpy(RTA_DATA(rta), data, alen); 57 | } 58 | 59 | n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); 60 | 61 | return 0; 62 | } 63 | 64 | void add_pneigh(int s, const char * ifn, const char *ip) { 65 | struct { 66 | struct nlmsghdr n; 67 | struct ndmsg r; 68 | char buf[4096]; 69 | } nl_request; 70 | 71 | nl_request.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)); 72 | nl_request.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL; 73 | nl_request.n.nlmsg_type = RTM_NEWNEIGH; 74 | nl_request.r.ndm_family = AF_INET; 75 | nl_request.r.ndm_ifindex = if_nametoindex(ifn); 76 | nl_request.r.ndm_flags = NTF_PROXY; 77 | nl_request.r.ndm_state = NUD_PERMANENT; 78 | 79 | unsigned int ip_addr; 80 | inet_pton(AF_INET, ip, &ip_addr); 81 | rtattr_add(&nl_request.n, sizeof(nl_request), NDA_DST, &ip_addr, 4); 82 | if(send(s, &nl_request, nl_request.n.nlmsg_len, 0) < 0) { 83 | errExit("send"); 84 | } 85 | } 86 | 87 | void bring_up(const char *ifn) { 88 | 89 | int s = socket(AF_INET, SOCK_DGRAM, 0); 90 | if (s < 0) { 91 | errExit("socket"); 92 | } 93 | struct ifreq ifr = {0}; 94 | strncpy(ifr.ifr_name, ifn, IFNAMSIZ); 95 | ifr.ifr_flags |= IFF_UP; 96 | if (ioctl(s, SIOCSIFFLAGS, &ifr) < 0) { 97 | char errmsg[30] = {0}; 98 | snprintf(errmsg, sizeof(errmsg), "bring up %s", ifn); 99 | errExit(errmsg); 100 | } 101 | close(s); 102 | } 103 | 104 | int main() { 105 | int s; 106 | char x; 107 | int pipe_fd[2]; 108 | if (pipe(pipe_fd) < 0) { 109 | errExit("pipe"); 110 | } 111 | close(s); 112 | int pid = fork(); 113 | if (pid < 0) { 114 | errExit("fork"); 115 | } 116 | if (pid == 0) { 117 | // init net ns watchdog 118 | close(pipe_fd[1]); 119 | if (read(pipe_fd[0], &x, 1) < 0) { 120 | errExit("read"); 121 | } 122 | if ((s = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE)) < 0) { 123 | errExit("rtnetlink socket"); 124 | } 125 | add_pneigh(s, "lo", "127.0.0.2"); 126 | system("cat /proc/net/arp"); 127 | return 0; 128 | } 129 | close(pipe_fd[0]); 130 | unshare(CLONE_NEWNET); 131 | bring_up("lo"); 132 | if ((s = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE)) < 0) { 133 | errExit("rtnetlink socket"); 134 | } 135 | add_pneigh(s, "lo", "127.0.0.2"); 136 | if (write(pipe_fd[1], &x, 1) < 0) { 137 | errExit("write"); 138 | } 139 | wait(NULL); 140 | return 0; 141 | } -------------------------------------------------------------------------------- /old_bugs/pptp/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "08252b32311c3fa84219ad794d640af7399b5485", 3 | "kernel": { 4 | "commit": "920d087e44c228be6270e07fdb59043380a4bb00", 5 | "config": "./.config", 6 | "gcc": "gcc-4.9" 7 | }, 8 | "image": { 9 | "release": "wheezy", 10 | "fs": "ext3", 11 | "mirror": "http://archive.debian.org/debian" 12 | } 13 | } -------------------------------------------------------------------------------- /old_bugs/pptp/pptp.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \ 26 | } while (0) 27 | #define NL_MAX_PAYLOAD 8192 28 | 29 | int pipe_fd[2]; 30 | char x; 31 | int main() { 32 | system("cat /proc/net/dev"); 33 | if (pipe(pipe_fd) < 0) { 34 | errExit("pipe"); 35 | } 36 | int pid = fork(); 37 | if (pid < 0) { 38 | errExit("fork"); 39 | } 40 | if (pid == 0) { 41 | // init net ns watchdog 42 | close(pipe_fd[1]); 43 | if (read(pipe_fd[0], &x, 1) < 0) { 44 | errExit("read"); 45 | } 46 | system("cat /proc/net/dev"); 47 | return 0; 48 | } 49 | close(pipe_fd[0]); 50 | 51 | // container 52 | if (unshare(CLONE_NEWNET) < 0) { 53 | errExit("unshare"); 54 | } 55 | 56 | // bring up lo device 57 | int s = socket(AF_INET, SOCK_DGRAM, 0); 58 | if (s < 0) { 59 | errExit("socket"); 60 | } 61 | struct ifreq ifr = {0}; 62 | strncpy(ifr.ifr_name, "lo", IFNAMSIZ); 63 | ifr.ifr_flags |= IFF_UP; 64 | if (ioctl(s, SIOCSIFFLAGS, &ifr) < 0) { 65 | errExit("bring lo up"); 66 | } 67 | close(s); 68 | 69 | 70 | int f = socket(AF_PPPOX, SOCK_STREAM, PX_PROTO_PPTP); 71 | if (f < 0) { 72 | errExit("socket"); 73 | } 74 | struct sockaddr_pppox s_addr = { 75 | .sa_family=AF_PPPOX, 76 | .sa_protocol=PX_PROTO_PPTP, 77 | .sa_addr.pptp={ 78 | .call_id=0, 79 | .sin_addr.s_addr=inet_addr("127.0.0.1") 80 | } 81 | }; 82 | struct sockaddr_pppox d_addr = { 83 | .sa_family=AF_PPPOX, 84 | .sa_protocol=PX_PROTO_PPTP, 85 | .sa_addr.pptp={ 86 | .call_id=0, 87 | .sin_addr.s_addr=inet_addr("127.0.0.1") 88 | } 89 | }; 90 | int ret; 91 | printf("f = %d\n", f); 92 | ret = bind(f, (struct sockaddr *)&s_addr, sizeof(s_addr)); 93 | if (ret < 0) { 94 | errExit("bind"); 95 | } 96 | 97 | // * look up the route 98 | // * create a PPP channel and notifying PPP generic layer by calling `ppp_register_channel` 99 | // * set PPP channel's mtu, hdrlen, etc. 100 | // * set PPTP(PPPOX) sock's dst_addr 101 | ret = connect(f, (struct sockaddr *)&d_addr, sizeof(d_addr)); 102 | if (ret < 0) { 103 | errExit("connect"); 104 | } 105 | 106 | /* get PPP channel index */ 107 | int channel_idx; 108 | if (ioctl(f, PPPIOCGCHAN, &channel_idx) < 0) { 109 | errExit("pppox ioctl channel index"); 110 | } 111 | printf("channel = %d\n", channel_idx); 112 | 113 | // PPP doc: https://www.kernel.org/doc/Documentation/networking/ppp_generic.txt 114 | // section: Interface to pppd 115 | 116 | // create a PPP instance 117 | int ppp_dev_fd = open("/dev/ppp", O_RDWR); 118 | if (ppp_dev_fd < 0) { 119 | errExit("open /dev/ppp"); 120 | } 121 | 122 | // attach to the PPP channel 123 | if (ioctl(ppp_dev_fd, PPPIOCATTCHAN, &channel_idx) < 0) { 124 | errExit("attach to channel"); 125 | } 126 | 127 | // write data to PPP channel 128 | char *data = "abcd"; 129 | ret = write(ppp_dev_fd, data, sizeof(data)); 130 | printf("write ret = %d\n", ret); 131 | 132 | // notify detector 133 | if (write(pipe_fd[1], &x, 1) < 0) { 134 | errExit("write"); 135 | } 136 | 137 | wait(NULL); 138 | return 0; 139 | } -------------------------------------------------------------------------------- /old_bugs/priority/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "8639b46139b0e4ea3b1ab1c274e410ee327f1d89", 3 | "kernel": { 4 | "commit": "8b91a318e422a0201a7bd7ac2626eecec6b27ae1", 5 | "config": "./.config", 6 | "gcc": "gcc-4.9" 7 | }, 8 | "image": { 9 | "release": "wheezy", 10 | "fs": "ext3", 11 | "mirror": "http://archive.debian.org/debian" 12 | } 13 | } -------------------------------------------------------------------------------- /old_bugs/priority/priority.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | // #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \ 17 | } while (0) 18 | 19 | int main() { 20 | char x; 21 | int pipe_fd[2], pipe_fd1[2]; 22 | if (pipe(pipe_fd) < 0) { 23 | errExit("pipe"); 24 | } 25 | if (pipe(pipe_fd1) < 0) { 26 | errExit("pipe1"); 27 | } 28 | 29 | int pid = fork(); 30 | if (pid < 0) { 31 | errExit("fork"); 32 | } 33 | if (pid == 0) { 34 | close(pipe_fd[1]); 35 | close(pipe_fd1[0]); 36 | if (read(pipe_fd[0], &x, 1) < 0) { 37 | errExit("read"); 38 | } 39 | printf("p = %d\n", getpriority(PRIO_USER, 0)); 40 | return 0; 41 | } 42 | close(pipe_fd[0]); 43 | close(pipe_fd1[1]); 44 | unshare(CLONE_NEWPID); 45 | setpriority(PRIO_USER, 0, -11); 46 | if (write(pipe_fd[1], &x, 1) < 0) { 47 | errExit("write"); 48 | } 49 | wait(NULL); 50 | return 0; 51 | } -------------------------------------------------------------------------------- /old_bugs/sock_diag/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "0f5da659d8f1810f44de14acf2c80cd6499623a0", 3 | "kernel": { 4 | "commit": "3eb8feeb1708c7dbfd2e97df92a2a407c116606e", 5 | "config": "./.config", 6 | "gcc": "gcc-10" 7 | }, 8 | "image": { 9 | "release": "stretch", 10 | "fs": "ext4", 11 | "mirror": "http://ftp2.cn.debian.org/debian/" 12 | } 13 | } -------------------------------------------------------------------------------- /old_bugs/sock_diag/sock_diag.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | // #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \ 28 | } while (0) 29 | #define NLMSG_TAIL(nmsg) \ 30 | ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) 31 | 32 | // https://man7.org/linux/man-pages/man7/sock_diag.7.html 33 | static int 34 | print_diag(const struct unix_diag_msg *diag, unsigned int len) 35 | { 36 | if (len < NLMSG_LENGTH(sizeof(*diag))) { 37 | fputs("short response\n", stderr); 38 | return -1; 39 | } 40 | if (diag->udiag_family != AF_UNIX) { 41 | fprintf(stderr, "unexpected family %u\n", diag->udiag_family); 42 | return -1; 43 | } 44 | 45 | unsigned int rta_len = len - NLMSG_LENGTH(sizeof(*diag)); 46 | unsigned int peer = 0; 47 | size_t path_len = 0; 48 | char path[sizeof(((struct sockaddr_un *) 0)->sun_path) + 1]; 49 | 50 | for (struct rtattr *attr = (struct rtattr *) (diag + 1); 51 | RTA_OK(attr, rta_len); attr = RTA_NEXT(attr, rta_len)) { 52 | switch (attr->rta_type) { 53 | case UNIX_DIAG_NAME: 54 | if (!path_len) { 55 | path_len = RTA_PAYLOAD(attr); 56 | if (path_len > sizeof(path) - 1) 57 | path_len = sizeof(path) - 1; 58 | memcpy(path, RTA_DATA(attr), path_len); 59 | path[path_len] = '\0'; 60 | } 61 | break; 62 | 63 | case UNIX_DIAG_PEER: 64 | if (RTA_PAYLOAD(attr) >= sizeof(peer)) 65 | peer = *(unsigned int *) RTA_DATA(attr); 66 | break; 67 | } 68 | } 69 | 70 | printf("inode=%u", diag->udiag_ino); 71 | 72 | if (peer) 73 | printf(", peer=%u", peer); 74 | 75 | if (path_len) 76 | printf(", name=%s%s", *path ? "" : "@", 77 | *path ? path : path + 1); 78 | 79 | putchar('\n'); 80 | return 0; 81 | } 82 | 83 | int main() { 84 | char x; 85 | int pipe_fd[2], pipe_fd1[2]; 86 | if (pipe(pipe_fd) < 0) { 87 | errExit("pipe"); 88 | } 89 | if (pipe(pipe_fd1) < 0) { 90 | errExit("pipe1"); 91 | } 92 | 93 | int s; 94 | if ((s=socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { 95 | errExit("socket"); 96 | } 97 | struct sockaddr_un addr = {0}; 98 | addr.sun_family = AF_UNIX; 99 | strcpy(addr.sun_path, "./un"); 100 | if (bind(s, &addr, sizeof(addr)) < 0) { 101 | errExit("bind"); 102 | } 103 | struct stat file_stat; 104 | if (fstat(s, &file_stat) < 0) { 105 | errExit("fstat"); 106 | } 107 | int ino = file_stat.st_ino; 108 | int pid = fork(); 109 | if (pid < 0) { 110 | errExit("fork"); 111 | } 112 | if (pid == 0) { 113 | unshare(CLONE_NEWNET); 114 | close(pipe_fd[1]); 115 | close(pipe_fd1[0]); 116 | int s; 117 | if ((s = socket(AF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG)) < 0) { 118 | errExit("sock"); 119 | } 120 | struct { 121 | struct nlmsghdr n; 122 | struct unix_diag_req r; 123 | } nl_request = { 124 | .n = { 125 | .nlmsg_len=sizeof(nl_request), 126 | .nlmsg_type=SOCK_DIAG_BY_FAMILY, 127 | .nlmsg_flags=NLM_F_REQUEST 128 | }, 129 | .r = { 130 | .sdiag_family=AF_UNIX, 131 | .sdiag_protocol=0, 132 | .udiag_states=-1, 133 | .udiag_ino=ino, 134 | .udiag_show=UDIAG_SHOW_NAME, 135 | .pad=0, 136 | .udiag_cookie = {~0U, ~0U} 137 | } 138 | }; 139 | if (send(s, &nl_request, nl_request.n.nlmsg_len, 0) < 0) { 140 | errExit("send"); 141 | } 142 | 143 | char buf[1024] = {0}; 144 | int ret = recv(s, buf, sizeof(buf), 0); 145 | 146 | struct nlmsghdr *h = (struct nlmsghdr *)buf; 147 | if (!NLMSG_OK(h, ret)) { 148 | errExit("response not ok"); 149 | } 150 | for (; NLMSG_OK(h, ret); h = NLMSG_NEXT(h, ret)) { 151 | if (h->nlmsg_type == NLMSG_DONE) { 152 | return 0; 153 | } 154 | 155 | if (h->nlmsg_type == NLMSG_ERROR) { 156 | const struct nlmsgerr *err = NLMSG_DATA(h); 157 | 158 | if (h->nlmsg_len < NLMSG_LENGTH(sizeof(*err))) { 159 | fputs("NLMSG_ERROR\n", stderr); 160 | } else { 161 | errno = -err->error; 162 | perror("NLMSG_ERROR"); 163 | } 164 | 165 | return -1; 166 | } 167 | 168 | if (h->nlmsg_type != SOCK_DIAG_BY_FAMILY) { 169 | fprintf(stderr, "unexpected nlmsg_type %u\n", 170 | (unsigned) h->nlmsg_type); 171 | return -1; 172 | } 173 | 174 | if (print_diag(NLMSG_DATA(h), h->nlmsg_len)) 175 | return -1; 176 | } 177 | 178 | return 0; 179 | } 180 | close(pipe_fd[0]); 181 | close(pipe_fd1[1]); 182 | wait(NULL); 183 | return 0; 184 | } -------------------------------------------------------------------------------- /old_bugs/uevent/env.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "82ef3d5d5f3ffd757c960693c4fe7a0051211849", 3 | "kernel": { 4 | "commit": "949db153b6466c6f7cad5a427ecea94985927311", 5 | "config": "./.config", 6 | "gcc": "gcc-4.9" 7 | }, 8 | "image": { 9 | "release": "wheezy", 10 | "fs": "ext3", 11 | "mirror": "http://archive.debian.org/debian" 12 | } 13 | } -------------------------------------------------------------------------------- /old_bugs/uevent/uevent.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | // #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \ 26 | } while (0) 27 | // https://backreference.org/2010/03/26/tuntap-interface-tutorial/ 28 | // alloc tun/tap device 29 | int tun_alloc(char *dev, int flags) { 30 | 31 | struct ifreq ifr; 32 | int fd, err; 33 | char *clonedev = "/dev/net/tun"; 34 | 35 | /* Arguments taken by the function: 36 | * 37 | * char *dev: the name of an interface (or '\0'). MUST have enough 38 | * space to hold the interface name if '\0' is passed 39 | * int flags: interface flags (eg, IFF_TUN etc.) 40 | */ 41 | 42 | /* open the clone device */ 43 | if( (fd = open(clonedev, O_RDWR)) < 0 ) { 44 | errExit("open fd"); 45 | } 46 | 47 | /* preparation of the struct ifr, of type "struct ifreq" */ 48 | memset(&ifr, 0, sizeof(ifr)); 49 | 50 | ifr.ifr_flags = flags; /* IFF_TUN or IFF_TAP, plus maybe IFF_NO_PI */ 51 | 52 | if (*dev) { 53 | /* if a device name was specified, put it in the structure; otherwise, 54 | * the kernel will try to allocate the "next" device of the 55 | * specified type */ 56 | strncpy(ifr.ifr_name, dev, IFNAMSIZ); 57 | } 58 | 59 | /* try to create the device */ 60 | if( (err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0 ) { 61 | errExit("tun ioctl"); 62 | } 63 | 64 | /* if the operation was successful, write back the name of the 65 | * interface to the variable "dev", so the caller can know 66 | * it. Note that the caller MUST reserve space in *dev (see calling 67 | * code below) */ 68 | strcpy(dev, ifr.ifr_name); 69 | 70 | /* this is the special file descriptor that the caller will use to talk 71 | * with the virtual interface */ 72 | return fd; 73 | } 74 | 75 | int open_netlink() 76 | { 77 | struct sockaddr_nl saddr; 78 | 79 | int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); 80 | 81 | if (sock < 0) { 82 | perror("Failed to open netlink socket"); 83 | return -1; 84 | } 85 | 86 | memset(&saddr, 0, sizeof(saddr)); 87 | 88 | return sock; 89 | } 90 | 91 | #define NLMSG_TAIL(nmsg) \ 92 | ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) 93 | 94 | /* Add new data to rtattr */ 95 | int rtattr_add(struct nlmsghdr *n, int maxlen, int type, const void *data, int alen) 96 | { 97 | int len = RTA_LENGTH(alen); 98 | struct rtattr *rta; 99 | 100 | if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) { 101 | fprintf(stderr, "rtattr_add error: message exceeded bound of %d\n", maxlen); 102 | return -1; 103 | } 104 | 105 | rta = NLMSG_TAIL(n); 106 | rta->rta_type = type; 107 | rta->rta_len = len; 108 | 109 | if (alen) { 110 | memcpy(RTA_DATA(rta), data, alen); 111 | } 112 | 113 | n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); 114 | 115 | return 0; 116 | } 117 | 118 | void set_flag(const char *ifn, short int flag) { 119 | int s = open_netlink(); 120 | struct { 121 | struct nlmsghdr n; 122 | struct ifinfomsg r; 123 | } nl_request; 124 | memset(&nl_request, 0, sizeof(nl_request)); 125 | nl_request.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); 126 | nl_request.n.nlmsg_flags = NLM_F_REQUEST; 127 | nl_request.n.nlmsg_type = RTM_NEWLINK; 128 | nl_request.r.ifi_flags = flag; 129 | nl_request.r.ifi_index = if_nametoindex(ifn); 130 | nl_request.r.ifi_change = flag; 131 | if (send(s, &nl_request, nl_request.n.nlmsg_len, 0) < 0) { 132 | errExit("send"); 133 | } 134 | } 135 | 136 | void set_ipv4(const char *ifn, const char *ip, int prefixlen) { 137 | int s = open_netlink(); 138 | if (s < 0) { 139 | errExit("socket"); 140 | } 141 | struct { 142 | struct nlmsghdr n; 143 | struct ifaddrmsg r; 144 | char buf[256]; 145 | } nl_request; 146 | memset(&nl_request, 0, sizeof(nl_request)); 147 | nl_request.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); 148 | nl_request.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL; 149 | nl_request.n.nlmsg_type = RTM_NEWADDR; 150 | nl_request.r.ifa_family = AF_INET; 151 | nl_request.r.ifa_index = if_nametoindex(ifn); 152 | nl_request.r.ifa_prefixlen = prefixlen; 153 | int ip_addr = inet_addr(ip); 154 | rtattr_add(&nl_request.n, sizeof(nl_request), IFA_LOCAL, &ip_addr, 4); 155 | rtattr_add(&nl_request.n, sizeof(nl_request), IFA_ADDRESS, &ip_addr, 4); 156 | if (send(s, &nl_request, nl_request.n.nlmsg_len, 0) < 0) { 157 | errExit("send"); 158 | } 159 | } 160 | 161 | unsigned short checksum(unsigned short* buff, int _16bitword) 162 | { 163 | unsigned long sum; 164 | for (sum = 0; _16bitword > 0; _16bitword--) 165 | sum += *(buff)++; 166 | sum = ((sum >> 16) + (sum & 0xFFFF)); 167 | sum += (sum>>16); 168 | return (unsigned short)(~sum); 169 | } 170 | 171 | static bool write_file(const char* file, const char* what, ...) 172 | { 173 | char buf[1024]; 174 | va_list args; 175 | va_start(args, what); 176 | vsnprintf(buf, sizeof(buf), what, args); 177 | va_end(args); 178 | buf[sizeof(buf) - 1] = 0; 179 | int len = strlen(buf); 180 | 181 | int fd = open(file, O_WRONLY | O_CLOEXEC); 182 | if (fd == -1) 183 | return false; 184 | if (write(fd, buf, len) != len) { 185 | int err = errno; 186 | close(fd); 187 | errno = err; 188 | return false; 189 | } 190 | close(fd); 191 | return true; 192 | } 193 | 194 | int main() { 195 | char x; 196 | int pipe_fd[2], pipe_fd1[2]; 197 | if (pipe(pipe_fd) < 0) { 198 | errExit("pipe"); 199 | } 200 | if (pipe(pipe_fd1) < 0) { 201 | errExit("pipe1"); 202 | } 203 | // set value here since kernel incorrectly refer to init_net 204 | if (write_file("/proc/sys/net/ipv4/icmp_echo_ignore_broadcasts", "0") == false) { 205 | errExit("disable ignoring icmp multicast"); 206 | } 207 | // system("mount -t sysfs sysfs /sys"); 208 | 209 | int pid = fork(); 210 | if (pid < 0) { 211 | errExit("fork"); 212 | } 213 | if (pid == 0) { 214 | close(pipe_fd[1]); 215 | close(pipe_fd1[0]); 216 | int nl_socket; 217 | struct sockaddr_nl src_addr; 218 | char msg[4096]; 219 | int ret; 220 | 221 | // Prepare source address 222 | memset(&src_addr, 0, sizeof(src_addr)); 223 | src_addr.nl_family = AF_NETLINK; 224 | src_addr.nl_pid = getpid(); 225 | src_addr.nl_groups = -1; 226 | 227 | nl_socket = socket(AF_NETLINK, (SOCK_DGRAM | SOCK_CLOEXEC), NETLINK_KOBJECT_UEVENT); 228 | if (nl_socket < 0) { 229 | errExit("create netlink socket"); 230 | } 231 | 232 | ret = bind(nl_socket, (struct sockaddr*) &src_addr, sizeof(src_addr)); 233 | if (ret < 0) { 234 | errExit("bind netlink"); 235 | } 236 | 237 | printf("Waiting for events now...\n"); 238 | while(1) { 239 | int r = recv(nl_socket, msg, sizeof(msg), MSG_DONTWAIT); 240 | if (r < 0) { 241 | continue; 242 | } 243 | printf("length:%i\n msg:%s", r, msg); 244 | if (write(pipe_fd1[1], &x, 1) < 0) { 245 | errExit("write"); 246 | } 247 | break; 248 | } 249 | return 0; 250 | } 251 | close(pipe_fd[0]); 252 | close(pipe_fd1[1]); 253 | 254 | sleep(1); 255 | if (unshare(CLONE_NEWNET | CLONE_NEWNS) < 0) { 256 | errExit("unshare"); 257 | } 258 | 259 | char tun_name[IFNAMSIZ]; 260 | strcpy(tun_name, "tun0"); 261 | int tunfd = tun_alloc(tun_name, IFF_TUN | IFF_NO_PI | IFF_TUN_EXCL); 262 | set_ipv4(tun_name, "172.16.1.1", 24); 263 | set_flag(tun_name, IFF_UP); 264 | // wait for init_net receive uevent 265 | if (read(pipe_fd1[0], &x, 1) < 0) { 266 | errExit("read"); 267 | } 268 | 269 | wait(NULL); 270 | return 0; 271 | } -------------------------------------------------------------------------------- /pgen/filegen.go: -------------------------------------------------------------------------------- 1 | package pgen 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "path" 8 | 9 | "github.com/google/syzkaller/prog" 10 | ) 11 | 12 | type FileProgGenerator struct { 13 | dir string 14 | files []os.FileInfo 15 | progTarget *prog.Target 16 | progCache []*prog.Prog 17 | progCacheName string 18 | } 19 | 20 | func InitFileProgGenerator(progTarget *prog.Target, dir string) (*FileProgGenerator, error) { 21 | var g *FileProgGenerator 22 | var err error 23 | 24 | g = &FileProgGenerator{ 25 | dir: dir, 26 | progTarget: progTarget, 27 | } 28 | g.files, err = ioutil.ReadDir(dir) 29 | if err != nil { 30 | return nil, fmt.Errorf("cannot list directory: %v", err) 31 | } 32 | return g, nil 33 | } 34 | 35 | func (g *FileProgGenerator) Generate() (*ProgGen, error) { 36 | var err error 37 | var data []byte 38 | var logs []*prog.LogEntry 39 | 40 | if len(g.progCache) != 0 { 41 | p := g.progCache[0] 42 | g.progCache = g.progCache[1:] 43 | return &ProgGen{P: p, Meta: &ProgMeta{Name: g.progCacheName}}, nil 44 | } 45 | readFile: 46 | if len(g.files) == 0 { 47 | return nil, nil 48 | } 49 | name := g.files[0].Name() 50 | progPath := path.Join(g.dir, name) 51 | g.files = g.files[1:] 52 | data, err = ioutil.ReadFile(progPath) 53 | if err != nil { 54 | return nil, fmt.Errorf("cannot open program file: %v", err) 55 | } 56 | logs = g.progTarget.ParseLog(data) 57 | if len(logs) == 0 { 58 | goto readFile 59 | } 60 | for i := 1; i < len(logs); i++ { 61 | g.progCache = append(g.progCache, logs[i].P) 62 | } 63 | if len(logs) > 1 { 64 | g.progCacheName = name 65 | } 66 | return &ProgGen{P: logs[0].P, Meta: &ProgMeta{Name: name}}, nil 67 | } 68 | -------------------------------------------------------------------------------- /pgen/gen.go: -------------------------------------------------------------------------------- 1 | package pgen 2 | 3 | import "github.com/google/syzkaller/prog" 4 | 5 | type ProgMeta struct { 6 | Name string 7 | TestIdx int 8 | ClsIdx int 9 | ClsMemIdx int 10 | ClsMemProgIdx int 11 | PredPC uint64 12 | PredAddr uint64 13 | PredAddrLen uint8 14 | } 15 | 16 | type CProg struct { 17 | Code []byte 18 | Meta *CProgMeta 19 | } 20 | 21 | type CProgMeta struct { 22 | Name string `json:"name"` 23 | Compiler string `json:"compiler"` 24 | CompFlags []string `json:"comp_flags"` 25 | } 26 | 27 | type ProgGen struct { 28 | P *prog.Prog 29 | Meta *ProgMeta 30 | CP *CProg 31 | } 32 | type ProgPair struct { 33 | A *ProgGen 34 | V *ProgGen 35 | // currently deprecated 36 | Interleave bool 37 | VPause uint8 38 | } 39 | type ProgGenerator interface { 40 | Generate() (*ProgGen, error) 41 | } 42 | type ProgPairGenerator interface { 43 | Generate() (*ProgPair, error) 44 | Log() string 45 | } 46 | 47 | func ProgramCheck(p *prog.Prog, s map[*prog.Syscall]bool) bool { 48 | for _, c := range p.Calls { 49 | if !s[c.Meta] { 50 | return false 51 | } 52 | } 53 | return true 54 | } 55 | -------------------------------------------------------------------------------- /pgen/memgen.go: -------------------------------------------------------------------------------- 1 | package pgen 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "path" 9 | "strconv" 10 | "sync" 11 | 12 | "github.com/google/syzkaller/prog" 13 | ) 14 | 15 | type MemTestInfo struct { 16 | Idx int `json:"idx"` 17 | CallStack uint64 `json:"call_stack"` 18 | PC uint64 `json:"pc,omitempty"` 19 | Progs []int `json:"progs"` 20 | Addr uint64 `json:"addr"` 21 | Width uint8 `json:"width"` 22 | Read bool `json:"read"` 23 | } 24 | 25 | type MemPair struct { 26 | WMemIdx int `json:"w_mem_idx"` 27 | RMemIdx int `json:"r_mem_idx"` 28 | Score float64 `json:"score"` 29 | } 30 | 31 | type ClsTest struct { 32 | Tests []MemPair `json:"tests"` 33 | Score float64 `json:"score"` 34 | } 35 | 36 | type TestPred struct { 37 | Mem []*MemTestInfo `json:"mem"` 38 | Cls []ClsTest `json:"cls"` 39 | Score float64 `json:"score"` 40 | } 41 | 42 | const ( 43 | NoInterleave int = iota 44 | Interleaving 45 | ) 46 | 47 | type ClsTestGenerator struct { 48 | gen *TestGenerator 49 | mem []*MemTestInfo 50 | tests []MemPair 51 | progDir string 52 | target *prog.Target 53 | testIdx int 54 | aProgIdx int 55 | vProgIdx int 56 | score float64 57 | } 58 | 59 | type TestGenerator struct { 60 | clsIdx int 61 | gens []*ClsTestGenerator 62 | nextGens []*ClsTestGenerator 63 | mu sync.Mutex 64 | done map[int]map[int]bool // global map of done pairs 65 | statTest int // number of tests (include interleave) generated 66 | } 67 | 68 | func (gen *TestGenerator) Generate() (*ProgPair, error) { 69 | for { 70 | if len(gen.gens) == 0 { 71 | return nil, nil 72 | } 73 | if gen.clsIdx == len(gen.gens) { 74 | gen.mu.Lock() 75 | gen.gens = gen.nextGens 76 | gen.nextGens = []*ClsTestGenerator{} 77 | gen.clsIdx = 0 78 | gen.mu.Unlock() 79 | continue 80 | } 81 | curGen := gen.gens[gen.clsIdx] 82 | pair, err := curGen.Generate() 83 | if pair == nil && err == nil { 84 | gen.mu.Lock() 85 | gen.clsIdx++ 86 | gen.mu.Unlock() 87 | continue 88 | } 89 | pair.A.Meta.ClsIdx = gen.clsIdx 90 | pair.V.Meta.ClsIdx = gen.clsIdx 91 | gen.mu.Lock() 92 | gen.nextGens = append(gen.nextGens, curGen) 93 | gen.clsIdx++ 94 | gen.mu.Unlock() 95 | return pair, err 96 | } 97 | } 98 | 99 | func (gen *TestGenerator) Log() string { 100 | gen.mu.Lock() 101 | defer gen.mu.Unlock() 102 | if len(gen.gens) == 0 { 103 | return "empty" 104 | } 105 | i := gen.clsIdx 106 | if i == len(gen.gens) { 107 | i-- 108 | } 109 | s := fmt.Sprintf("statTest=%v, numCls=%v, clsIdx=%v, clsScore=%v, ", 110 | gen.statTest, len(gen.gens), i, gen.gens[i].score) 111 | c := gen.gens[i] 112 | return s + c.Log() 113 | } 114 | 115 | func (gen *ClsTestGenerator) Generate() (*ProgPair, error) { 116 | for { 117 | var pair *ProgPair 118 | if gen.testIdx == len(gen.tests) { 119 | return nil, nil 120 | } 121 | test := gen.tests[gen.testIdx] 122 | if gen.aProgIdx == len(gen.mem[test.WMemIdx].Progs) { 123 | gen.testIdx++ 124 | gen.aProgIdx = 0 125 | gen.vProgIdx = 0 126 | continue 127 | } 128 | if gen.vProgIdx == len(gen.mem[test.RMemIdx].Progs) { 129 | gen.aProgIdx++ 130 | gen.vProgIdx = 0 131 | continue 132 | } 133 | a := gen.mem[test.WMemIdx].Progs[gen.aProgIdx] 134 | v := gen.mem[test.RMemIdx].Progs[gen.vProgIdx] 135 | if _, ok := gen.gen.done[a]; ok { 136 | if gen.gen.done[a][v] { 137 | gen.vProgIdx++ 138 | continue 139 | } 140 | } 141 | aName := strconv.FormatInt(int64(a), 10) 142 | aPath := path.Join(gen.progDir, aName) 143 | aProgBytes, err := ioutil.ReadFile(aPath) 144 | if err != nil { 145 | return nil, err 146 | } 147 | pair = &ProgPair{ 148 | A: &ProgGen{}, 149 | V: &ProgGen{}, 150 | Interleave: false, 151 | VPause: 0, 152 | } 153 | pair.A.P, err = gen.target.Deserialize(aProgBytes, prog.NonStrict) 154 | if err != nil { 155 | gen.aProgIdx++ 156 | gen.vProgIdx = 0 157 | continue 158 | } 159 | pair.A.Meta = &ProgMeta{ 160 | Name: aName, 161 | TestIdx: gen.gen.statTest, 162 | ClsMemIdx: gen.testIdx, 163 | ClsMemProgIdx: gen.aProgIdx, 164 | PredPC: gen.mem[test.WMemIdx].PC, 165 | PredAddr: gen.mem[test.WMemIdx].Addr, 166 | PredAddrLen: 1 << gen.mem[test.WMemIdx].Width, 167 | } 168 | 169 | vName := strconv.FormatInt(int64(v), 10) 170 | vPath := path.Join(gen.progDir, vName) 171 | vProgBytes, err := ioutil.ReadFile(vPath) 172 | if err != nil { 173 | return nil, err 174 | } 175 | pair.V.P, err = gen.target.Deserialize(vProgBytes, prog.NonStrict) 176 | if err != nil { 177 | gen.vProgIdx++ 178 | continue 179 | } 180 | pair.V.Meta = &ProgMeta{ 181 | Name: vName, 182 | TestIdx: gen.gen.statTest, 183 | ClsMemIdx: gen.testIdx, 184 | ClsMemProgIdx: gen.vProgIdx, 185 | PredPC: gen.mem[test.RMemIdx].PC, 186 | PredAddr: gen.mem[test.RMemIdx].Addr, 187 | PredAddrLen: 1 << gen.mem[test.RMemIdx].Width, 188 | } 189 | 190 | if _, ok := gen.gen.done[a]; !ok { 191 | gen.gen.done[a] = map[int]bool{} 192 | } 193 | gen.gen.done[a][v] = true 194 | gen.vProgIdx++ 195 | gen.gen.statTest++ 196 | return pair, nil 197 | } 198 | } 199 | 200 | func InitTestGenerator(target *prog.Target, progDir string, predPath string) (*TestGenerator, error) { 201 | gen := &TestGenerator{ 202 | clsIdx: 0, 203 | done: map[int]map[int]bool{}, 204 | } 205 | 206 | f, err := os.Open(predPath) 207 | if err != nil { 208 | return nil, fmt.Errorf("cannot open prediction file: %v", err) 209 | } 210 | dec := json.NewDecoder(f) 211 | pred := &TestPred{} 212 | err = dec.Decode(pred) 213 | if err != nil { 214 | return nil, fmt.Errorf("cannot decode prediction file: %v", err) 215 | } 216 | for _, rgTest := range pred.Cls { 217 | rgen := &ClsTestGenerator{ 218 | gen: gen, 219 | target: target, 220 | progDir: progDir, 221 | testIdx: 0, 222 | aProgIdx: 0, 223 | vProgIdx: 0, 224 | tests: rgTest.Tests, 225 | mem: pred.Mem, 226 | score: rgTest.Score, 227 | } 228 | gen.gens = append(gen.gens, rgen) 229 | } 230 | 231 | return gen, nil 232 | } 233 | 234 | func (gen *ClsTestGenerator) Log() string { 235 | if len(gen.tests) == 0 { 236 | return "empty tests..." 237 | } 238 | if gen.testIdx == len(gen.tests) { 239 | return "done" 240 | } 241 | test := gen.tests[gen.testIdx] 242 | return fmt.Sprintf("testIdx=%v, testScore=%.6f", 243 | gen.testIdx, test.Score) 244 | } 245 | -------------------------------------------------------------------------------- /pgen/pmcgen.go: -------------------------------------------------------------------------------- 1 | package pgen 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | "path" 9 | "strconv" 10 | 11 | "github.com/google/syzkaller/prog" 12 | ) 13 | 14 | type PmcPredPCEntry struct { 15 | WPC uint64 `json:"w_pc"` 16 | RPC uint64 `json:"r_pc"` 17 | WProgs []int `json:"w_progs"` 18 | RProgs []int `json:"r_progs"` 19 | Cnt int `json:"cnt"` 20 | } 21 | 22 | type PmcPredPC struct { 23 | Entries []PmcPredPCEntry `json:"entries"` 24 | } 25 | 26 | type PmcPred map[int]map[int]bool 27 | 28 | type pmcPredPCEntryGenCtx struct { 29 | wProgsIdx int 30 | rProgsIdx int 31 | } 32 | 33 | type pmcPredPCGenCtx struct { 34 | pred *PmcPredPC 35 | visited map[int]map[int]bool 36 | entryCtx []pmcPredPCEntryGenCtx 37 | entryCand []int 38 | entryCandIdx int 39 | entryCandNext []int 40 | } 41 | 42 | type PmcPredPCGenerator struct { 43 | target *prog.Target 44 | pred *PmcPredPC 45 | profileDir string 46 | programDir string 47 | genCtx pmcPredPCGenCtx 48 | } 49 | 50 | func (ctx *pmcPredPCGenCtx) nextEntry() { 51 | if ctx.entryCandIdx == len(ctx.entryCand)-1 { 52 | ctx.entryCandIdx = 0 53 | ctx.entryCand = ctx.entryCandNext 54 | ctx.entryCandNext = make([]int, 0) 55 | } else { 56 | ctx.entryCandIdx++ 57 | } 58 | } 59 | 60 | func (ctx *pmcPredPCGenCtx) generate() (w int, r int, done bool) { 61 | for { 62 | if len(ctx.entryCand) == 0 { 63 | return -1, -1, true 64 | } 65 | entryIdx := ctx.entryCand[ctx.entryCandIdx] 66 | entry := &ctx.pred.Entries[entryIdx] 67 | entryCtx := &ctx.entryCtx[entryIdx] 68 | vis := false 69 | done := false 70 | wIdx := entryCtx.wProgsIdx 71 | rIdx := entryCtx.rProgsIdx 72 | w = entry.WProgs[wIdx] 73 | r = entry.RProgs[rIdx] 74 | if _, ok := ctx.visited[w][r]; ok { 75 | vis = true 76 | } 77 | if rIdx == len(entry.RProgs)-1 { 78 | if wIdx == len(entry.WProgs)-1 { 79 | done = true 80 | } else { 81 | entryCtx.rProgsIdx = 0 82 | entryCtx.wProgsIdx++ 83 | } 84 | } else { 85 | entryCtx.rProgsIdx++ 86 | } 87 | if vis { 88 | // try to generate another pair in current entry 89 | if done { 90 | ctx.nextEntry() 91 | continue 92 | } else { 93 | continue 94 | } 95 | } else { 96 | // return pair and move to next entry 97 | if _, ok := ctx.visited[w]; !ok { 98 | ctx.visited[w] = make(map[int]bool) 99 | } 100 | ctx.visited[w][r] = true 101 | ctx.nextEntry() 102 | if !done { 103 | ctx.entryCandNext = append(ctx.entryCandNext, entryIdx) 104 | } 105 | return w, r, false 106 | } 107 | } 108 | } 109 | 110 | func InitPmcPredPCGenerator(target *prog.Target, profileDir, programDir string, predsPath string) (*PmcPredPCGenerator, error) { 111 | gen := &PmcPredPCGenerator{profileDir: profileDir, programDir: programDir, target: target} 112 | f, err := os.Open(predsPath) 113 | if err != nil { 114 | log.Fatalf("cannot open prediction file: %v", err) 115 | } 116 | dec := json.NewDecoder(f) 117 | pred := &PmcPredPC{} 118 | err = dec.Decode(pred) 119 | gen.pred = pred 120 | gen.genCtx = pmcPredPCGenCtx{ 121 | pred: pred, 122 | visited: make(map[int]map[int]bool), 123 | entryCandIdx: 0, 124 | } 125 | for i := 0; i < len(pred.Entries); i++ { 126 | gen.genCtx.entryCand = append(gen.genCtx.entryCand, i) 127 | gen.genCtx.entryCtx = append(gen.genCtx.entryCtx, pmcPredPCEntryGenCtx{wProgsIdx: 0, rProgsIdx: 0}) 128 | } 129 | if err != nil { 130 | log.Fatalf("cannot decode prediction file: %v", err) 131 | } 132 | return gen, nil 133 | } 134 | 135 | func (gen *PmcPredPCGenerator) Generate() (*ProgPair, error) { 136 | for { 137 | pair := &ProgPair{ 138 | A: &ProgGen{}, 139 | V: &ProgGen{}, 140 | } 141 | a, v, done := gen.genCtx.generate() 142 | if done { 143 | return nil, nil 144 | } 145 | aName := strconv.FormatInt(int64(a), 10) 146 | aPath := path.Join(gen.programDir, aName) 147 | aProgBytes, err := ioutil.ReadFile(aPath) 148 | if err != nil { 149 | return nil, err 150 | } 151 | pair.A.P, err = gen.target.Deserialize(aProgBytes, prog.NonStrict) 152 | if err != nil { 153 | continue 154 | } 155 | pair.A.Meta = &ProgMeta{Name: aName} 156 | 157 | vName := strconv.FormatInt(int64(v), 10) 158 | vPath := path.Join(gen.programDir, vName) 159 | vProgBytes, err := ioutil.ReadFile(vPath) 160 | if err != nil { 161 | return nil, err 162 | } 163 | pair.V.P, err = gen.target.Deserialize(vProgBytes, prog.NonStrict) 164 | if err != nil { 165 | continue 166 | } 167 | pair.V.Meta = &ProgMeta{Name: vName} 168 | return pair, nil 169 | } 170 | } 171 | 172 | func (gen *PmcPredPCGenerator) Log() string { 173 | return "" 174 | } 175 | -------------------------------------------------------------------------------- /pgen/reprogen.go: -------------------------------------------------------------------------------- 1 | package pgen 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "path" 9 | "sync/atomic" 10 | ) 11 | 12 | type ReproProg struct { 13 | CPMeta *CProgMeta `json:"cp_meta"` 14 | } 15 | 16 | type ReproTest struct { 17 | A *ReproProg `json:"a"` 18 | V *ReproProg `json:"v"` 19 | } 20 | 21 | type ReproConfig struct { 22 | Tests []ReproTest `json:"tests"` 23 | SetupCmd string `json:"setup_cmd"` 24 | } 25 | 26 | type ReproGenerator struct { 27 | config *ReproConfig 28 | progDir string 29 | testIdx uint32 30 | } 31 | 32 | func InitReproTestGenerator(progDir, configPath string) (*ReproGenerator, error) { 33 | gen := &ReproGenerator{testIdx: 0, progDir: progDir} 34 | f, err := os.Open(configPath) 35 | if err != nil { 36 | return nil, fmt.Errorf("cannot open repro config file: %v", err) 37 | } 38 | dec := json.NewDecoder(f) 39 | cfg := &ReproConfig{} 40 | err = dec.Decode(cfg) 41 | if err != nil { 42 | return nil, fmt.Errorf("cannot decode repro config file: %v", err) 43 | } 44 | gen.config = cfg 45 | return gen, nil 46 | } 47 | 48 | func (gen *ReproGenerator) Generate() (*ProgPair, error) { 49 | if int(gen.testIdx) == len(gen.config.Tests) { 50 | return nil, nil 51 | } 52 | test := gen.config.Tests[gen.testIdx] 53 | gen.testIdx++ 54 | acp, err := ioutil.ReadFile(path.Join(gen.progDir, test.A.CPMeta.Name)) 55 | if err != nil { 56 | return nil, fmt.Errorf("cannot read sender C program: %v", err) 57 | } 58 | vcp, err := ioutil.ReadFile(path.Join(gen.progDir, test.V.CPMeta.Name)) 59 | if err != nil { 60 | return nil, fmt.Errorf("cannot read receiver C program: %v", err) 61 | } 62 | p := &ProgPair{ 63 | A: &ProgGen{ 64 | CP: &CProg{Code: acp, Meta: test.A.CPMeta}, 65 | }, 66 | V: &ProgGen{ 67 | CP: &CProg{Code: vcp, Meta: test.V.CPMeta}, 68 | }, 69 | } 70 | // Ensure gob works well 71 | if p.A.CP.Meta.CompFlags == nil { 72 | p.A.CP.Meta.CompFlags = []string{} 73 | } 74 | if p.V.CP.Meta.CompFlags == nil { 75 | p.V.CP.Meta.CompFlags = []string{} 76 | } 77 | return p, nil 78 | } 79 | 80 | func (gen *ReproGenerator) Log() string { 81 | return fmt.Sprintf("repro, testIdx=%v", atomic.LoadUint32(&gen.testIdx)) 82 | } 83 | -------------------------------------------------------------------------------- /prerequisite/compile/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sudo apt-get install make gcc g++ automake autoconf gcc-multilib g++-multilib libselinux1-dev libselinux1 libsepol-dev libsepol2 -------------------------------------------------------------------------------- /prerequisite/go/env.sh: -------------------------------------------------------------------------------- 1 | export GOPATH=$MAIN_HOME/prerequisite/go/gopath/ 2 | export GOROOT=$MAIN_HOME/prerequisite/go/goroot/ 3 | export PATH=$GOPATH/bin:$PATH 4 | export PATH=$GOROOT/bin:$PATH 5 | -------------------------------------------------------------------------------- /prerequisite/go/install.sh: -------------------------------------------------------------------------------- 1 | # have a stricter check later 2 | if [ -f "$GOROOT/bin/go" ]; then 3 | echo "Go runtime is already installed" 4 | exit 0 5 | fi 6 | pushd $MAIN_HOME/prerequisite/go > /dev/null 7 | wget https://dl.google.com/go/go1.14.2.linux-amd64.tar.gz 8 | tar -xf go1.14.2.linux-amd64.tar.gz 9 | mv go $GOROOT 10 | mkdir $GOPATH 11 | rm go1.14.2.linux-amd64.tar.gz -------------------------------------------------------------------------------- /prerequisite/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CUR_DIR="$(readlink -f $(dirname "$0"))" 4 | source $CUR_DIR/../script/common.sh 5 | 6 | # Install Go. 7 | $MAIN_HOME/prerequisite/go/install.sh 8 | RES=$? 9 | if [ $RES -ne 0 ]; then 10 | echo "Failed to install Go!" 11 | fi 12 | 13 | # Patch Syzkaller. 14 | $MAIN_HOME/prerequisite/syzkaller/patch.sh 15 | 16 | # Build 17 | pushd $MAIN_HOME/executor/libsclog > /dev/null 18 | ./build.sh 19 | popd 20 | pushd $MAIN_HOME > /dev/null 21 | make 22 | pushd $MAIN_HOME/testsuite/kernel-memory-acccess-tracing/gcc > /dev/null 23 | ./contrib/download_prerequisites 24 | mkdir build 25 | cd build 26 | $PWD/../configure --prefix=$CC_MT --enable-languages=c,c++ 27 | make -j`nproc` 28 | make install -------------------------------------------------------------------------------- /prerequisite/syzkaller/patch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd $MAIN_HOME/syzkaller 4 | git apply $MAIN_HOME/prerequisite/syzkaller/syzkaller.patch -------------------------------------------------------------------------------- /prerequisite/syzkaller/syzkaller.patch: -------------------------------------------------------------------------------- 1 | diff --git a/prog/prog.go b/prog/prog.go 2 | index 068198ffe..29d816f97 100644 3 | --- a/prog/prog.go 4 | +++ b/prog/prog.go 5 | @@ -416,6 +416,10 @@ func (p *Prog) removeCall(idx int) { 6 | p.Calls = p.Calls[:len(p.Calls)-1] 7 | } 8 | 9 | +func (p *Prog) RemoveCall(idx int) { 10 | + p.removeCall(idx) 11 | +} 12 | + 13 | func (p *Prog) sanitizeFix() { 14 | if err := p.sanitize(true); err != nil { 15 | panic(err) 16 | -------------------------------------------------------------------------------- /prerequisite/vscode_config/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Refer to git contrib/vscode/init.sh 3 | 4 | die () { 5 | echo "$*" >&2 6 | exit 1 7 | } 8 | 9 | CUR_DIR=$(readlink -f $(dirname $BASH_SOURCE)) 10 | source $CUR_DIR/../../script/common.sh 11 | 12 | cd $MAIN_HOME 13 | mkdir -p .vscode 14 | # General settings 15 | echo 16 | cat <.vscode/settings.json 17 | { 18 | "go.goroot": "$GOROOT", 19 | "go.gopath": "$GOPATH", 20 | "terminal.integrated.env.linux": { 21 | "PATH": "$PATH" 22 | }, 23 | } 24 | EOF -------------------------------------------------------------------------------- /result/result.go: -------------------------------------------------------------------------------- 1 | package result 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "path" 8 | "time" 9 | 10 | "github.com/google/syzkaller/prog" 11 | "github.com/rss/kit/pgen" 12 | "github.com/rss/kit/trace" 13 | ) 14 | 15 | type TestResult struct { 16 | TimeStamp string `json:"time_stamp"` 17 | TestIdx int `json:"test_idx"` 18 | ClsIdx int `json:"cls_idx"` 19 | ClsMemIdx int `json:"cls_mem_idx"` 20 | ClsMemAProgIdx int `json:"cls_mem_a_prog_idx"` 21 | ClsMemVProgIdx int `json:"cls_mem_v_prog_idx"` 22 | APredPC string `json:"a_pred_pc"` 23 | APredAddr string `json:"a_pred_addr"` 24 | APredAddrLen uint8 `json:"a_pred_addr_len"` 25 | VPredPC string `json:"v_pred_pc"` 26 | VPredAddr string `json:"v_pred_addr"` 27 | VPredAddrLen uint8 `json:"v_pred_addr_len"` 28 | Interleave bool `json:"interleave"` 29 | VPause uint8 `json:"v_pause"` 30 | ADiff []*trace.SCTraceDiff `json:"adiff"` 31 | VDiff []*trace.SCTraceDiff `json:"vdiff"` 32 | Timeout bool `json:"timeout"` 33 | AProgHanged bool `json:"a_prog_hanged"` 34 | VProgHanged bool `json:"v_prog_hanged"` 35 | AProgName string `json:"a_prog_name"` 36 | VProgName string `json:"v_prog_name"` 37 | AProg string `json:"a_prog"` 38 | AProgMini string `json:"a_prog_mini"` 39 | ACallDiag []int `json:"a_call_diag"` 40 | VCallDiag []int `json:"v_call_diag"` 41 | VProg string `json:"v_prog"` 42 | AExecFlag uint64 `json:"a_exec_flag"` 43 | VExecFlag uint64 `json:"v_exec_flag"` 44 | AProgMiniSCTrace string `json:"a_prog_mini_sctrace"` 45 | AProgSCTrace string `json:"a_prog_sctrace"` 46 | VProgSCTrace string `json:"v_prog_sctrace"` 47 | VProgPrevSCTrace string `json:"v_prog_prev_sctrace"` 48 | AProgPrevSCTrace string `json:"a_prog_prev_sctrace"` 49 | } 50 | 51 | const TimeStampFormat string = "2006/01/02 15:04:05" 52 | 53 | func NewTestResult(test *pgen.ProgPair, aProgMini *prog.Prog, aCallDiag, vCallDiag []int, testIdx, clsIdx, clsMemIdx, clsMemAProgIdx, clsMemVProgIdx int, aPredPC, aPredAddr, vPredPC, vPredAddr uint64, aPredAddrLen, vPredAddrLen uint8, interleave bool, vPause uint8, aExecFlag, vExecFlag uint64, timeout, aProgHanged, vProgHanged bool, adiff, vdiff []*trace.SCTraceDiff, aProgSCTrace, AProgMiniSCTrace, vProgSCTrace, aProgPrevSCTrace, vProgPrevSCTrace *trace.ProgSCTrace) *TestResult { 54 | res := &TestResult{ 55 | TimeStamp: time.Now().Format(TimeStampFormat), 56 | ACallDiag: aCallDiag, 57 | VCallDiag: vCallDiag, 58 | TestIdx: testIdx, 59 | ClsIdx: clsIdx, 60 | ClsMemIdx: clsMemIdx, 61 | ClsMemAProgIdx: clsMemAProgIdx, 62 | ClsMemVProgIdx: clsMemVProgIdx, 63 | APredPC: fmt.Sprintf("0x%x", aPredPC), 64 | APredAddr: fmt.Sprintf("0x%x", aPredAddr), 65 | VPredPC: fmt.Sprintf("0x%x", vPredPC), 66 | VPredAddr: fmt.Sprintf("0x%x", vPredAddr), 67 | APredAddrLen: aPredAddrLen, 68 | VPredAddrLen: vPredAddrLen, 69 | Interleave: interleave, 70 | VPause: vPause, 71 | ADiff: adiff, 72 | VDiff: vdiff, 73 | Timeout: timeout, 74 | AProgHanged: aProgHanged, 75 | VProgHanged: vProgHanged, 76 | AProgName: test.A.Meta.Name, 77 | VProgName: test.V.Meta.Name, 78 | AProg: string(test.A.P.Serialize()), 79 | VProg: string(test.V.P.Serialize()), 80 | AExecFlag: aExecFlag, 81 | VExecFlag: vExecFlag, 82 | } 83 | if aProgMini != nil { 84 | res.AProgMini = string(aProgMini.Serialize()) 85 | } 86 | if aProgSCTrace != nil { 87 | res.AProgSCTrace = string(aProgSCTrace.DeterminSerialze()) 88 | } 89 | if AProgMiniSCTrace != nil { 90 | res.AProgMiniSCTrace = string(AProgMiniSCTrace.RawSerialze()) 91 | } 92 | if vProgSCTrace != nil { 93 | res.VProgSCTrace = string(vProgSCTrace.DeterminSerialze()) 94 | } 95 | if aProgPrevSCTrace != nil { 96 | res.AProgPrevSCTrace = string(aProgPrevSCTrace.DeterminSerialze()) 97 | } 98 | if vProgPrevSCTrace != nil { 99 | res.VProgPrevSCTrace = string(vProgPrevSCTrace.DeterminSerialze()) 100 | } 101 | return res 102 | } 103 | 104 | func SerializeTestResult(resultTmpDir string, res *TestResult) error { 105 | resStr, err := json.MarshalIndent(res, "", "\t") 106 | if err != nil { 107 | return fmt.Errorf("cannot serialize results: %v", err) 108 | } 109 | var resPath string 110 | if res.Interleave { 111 | resPath = path.Join(resultTmpDir, fmt.Sprintf("%v_%v_%v.json", res.AProgName, res.VProgName, res.VPause)) 112 | } else { 113 | resPath = path.Join(resultTmpDir, fmt.Sprintf("%v_%v.json", res.AProgName, res.VProgName)) 114 | } 115 | err = ioutil.WriteFile(resPath, resStr, 0666) 116 | if err != nil { 117 | return fmt.Errorf("cannot write results: %v", err) 118 | } 119 | return nil 120 | } 121 | 122 | func LoadResult(path string) (*TestResult, error) { 123 | res := &TestResult{} 124 | data, err := ioutil.ReadFile(path) 125 | if err != nil { 126 | return nil, fmt.Errorf("cannot read from file: %v", err) 127 | } 128 | err = json.Unmarshal(data, res) 129 | if err != nil { 130 | return nil, fmt.Errorf("cannot parse file: %v", err) 131 | } 132 | return res, nil 133 | } 134 | -------------------------------------------------------------------------------- /script/common.sh: -------------------------------------------------------------------------------- 1 | export MAIN_HOME=$(readlink -f $(dirname $BASH_SOURCE)/../) 2 | export GOPATH=$MAIN_HOME/prerequisite/go/gopath/ 3 | export GOROOT=$MAIN_HOME/prerequisite/go/goroot/ 4 | export PATH=$GOPATH/bin:$PATH 5 | export PATH=$GOROOT/bin:$PATH 6 | export CC_MT=$MAIN_HOME/testsuite/kernel-memory-acccess-tracing/gcc-memtrace -------------------------------------------------------------------------------- /script/qemu/connect.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ssh -p 10021 -i $1 root@localhost -------------------------------------------------------------------------------- /script/qemu/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | qemu-system-x86_64 \ 3 | -m 2G \ 4 | -smp 1 \ 5 | -kernel $1/arch/x86/boot/bzImage \ 6 | -append "console=ttyS0 root=/dev/sda earlyprintk=serial net.ifnames=0 nokaslr" \ 7 | -drive file=$2,format=qcow2 \ 8 | -net user,host=10.0.2.10,hostfwd=tcp:127.0.0.1:10021-:22 \ 9 | -net nic,model=e1000 \ 10 | -nographic \ 11 | -enable-kvm \ 12 | -pidfile vm.pid \ 13 | -snapshot \ 14 | 2>&1 | tee vm.log2 15 | # -s -S \ -------------------------------------------------------------------------------- /script/test/create-image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # To-do: consider add a stricter check; maybe at a 3 | # certain point we want to create several images 4 | if [ -f "$MAIN_HOME/testsuite/image/vm.qcow2" ]; then 5 | echo "VM image already created" 6 | exit 0 7 | fi 8 | IMAGE_DIR=$MAIN_HOME/testsuite/image/ 9 | pushd $IMAGE_DIR > /dev/null 10 | ./create-image.sh 11 | qemu-img convert -f raw -O qcow2 stretch.img vm.qcow2 12 | 13 | popd 14 | -------------------------------------------------------------------------------- /testsuite/image/create-image.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright 2016 syzkaller project authors. All rights reserved. 3 | # Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 4 | 5 | # create-image.sh creates a minimal Debian Linux image suitable for syzkaller. 6 | 7 | set -eux 8 | 9 | # Create a minimal Debian distribution in a directory. 10 | DIR=chroot 11 | PREINSTALL_PKGS=vim,openssh-server,curl,tar,gcc,libc6-dev,time,strace,sudo,less,psmisc,selinux-utils,policycoreutils,checkpolicy,selinux-policy-default,firmware-atheros,debian-ports-archive-keyring 12 | 13 | # If ADD_PACKAGE is not defined as an external environment variable, use our default packages 14 | if [ -z ${ADD_PACKAGE+x} ]; then 15 | ADD_PACKAGE="make,sysbench,git,vim,tmux,usbutils,tcpdump" 16 | fi 17 | 18 | # Variables affected by options 19 | ARCH=$(uname -m) 20 | RELEASE=stretch 21 | FEATURE=minimal 22 | SEEK=2047 23 | PERF=false 24 | 25 | # Display help function 26 | display_help() { 27 | echo "Usage: $0 [option...] " >&2 28 | echo 29 | echo " -a, --arch Set architecture" 30 | echo " -d, --distribution Set on which debian distribution to create" 31 | echo " -f, --feature Check what packages to install in the image, options are minimal, full" 32 | echo " -s, --seek Image size (MB), default 2048 (2G)" 33 | echo " -h, --help Display help message" 34 | echo " -p, --add-perf Add perf support with this option enabled. Please set envrionment variable \$KERNEL at first" 35 | echo 36 | } 37 | 38 | while true; do 39 | if [ $# -eq 0 ];then 40 | echo $# 41 | break 42 | fi 43 | case "$1" in 44 | -h | --help) 45 | display_help 46 | exit 0 47 | ;; 48 | -a | --arch) 49 | ARCH=$2 50 | shift 2 51 | ;; 52 | -d | --distribution) 53 | RELEASE=$2 54 | shift 2 55 | ;; 56 | -f | --feature) 57 | FEATURE=$2 58 | shift 2 59 | ;; 60 | -s | --seek) 61 | SEEK=$(($2 - 1)) 62 | shift 2 63 | ;; 64 | -p | --add-perf) 65 | PERF=true 66 | shift 1 67 | ;; 68 | -*) 69 | echo "Error: Unknown option: $1" >&2 70 | exit 1 71 | ;; 72 | *) # No more options 73 | break 74 | ;; 75 | esac 76 | done 77 | 78 | # Handle cases where qemu and Debian use different arch names 79 | case "$ARCH" in 80 | ppc64le) 81 | DEBARCH=ppc64el 82 | ;; 83 | aarch64) 84 | DEBARCH=arm64 85 | ;; 86 | arm) 87 | DEBARCH=armel 88 | ;; 89 | x86_64) 90 | DEBARCH=amd64 91 | ;; 92 | *) 93 | DEBARCH=$ARCH 94 | ;; 95 | esac 96 | 97 | # Foreign architecture 98 | 99 | FOREIGN=false 100 | if [ $ARCH != $(uname -m) ]; then 101 | # i386 on an x86_64 host is exempted, as we can run i386 binaries natively 102 | if [ $ARCH != "i386" -o $(uname -m) != "x86_64" ]; then 103 | FOREIGN=true 104 | fi 105 | fi 106 | 107 | if [ $FOREIGN = "true" ]; then 108 | # Check for according qemu static binary 109 | if ! which qemu-$ARCH-static; then 110 | echo "Please install qemu static binary for architecture $ARCH (package 'qemu-user-static' on Debian/Ubuntu/Fedora)" 111 | exit 1 112 | fi 113 | # Check for according binfmt entry 114 | if [ ! -r /proc/sys/fs/binfmt_misc/qemu-$ARCH ]; then 115 | echo "binfmt entry /proc/sys/fs/binfmt_misc/qemu-$ARCH does not exist" 116 | exit 1 117 | fi 118 | fi 119 | 120 | # Double check KERNEL when PERF is enabled 121 | if [ $PERF = "true" ] && [ -z ${KERNEL+x} ]; then 122 | echo "Please set KERNEL environment variable when PERF is enabled" 123 | exit 1 124 | fi 125 | 126 | # If full feature is chosen, install more packages 127 | if [ $FEATURE = "full" ]; then 128 | PREINSTALL_PKGS=$PREINSTALL_PKGS","$ADD_PACKAGE 129 | fi 130 | 131 | sudo rm -rf $DIR 132 | sudo mkdir -p $DIR 133 | sudo chmod 0755 $DIR 134 | 135 | # 1. debootstrap stage 136 | 137 | DEBOOTSTRAP_PARAMS="--arch=$DEBARCH --include=$PREINSTALL_PKGS --components=main,contrib,non-free $RELEASE $DIR" 138 | if [ $FOREIGN = "true" ]; then 139 | DEBOOTSTRAP_PARAMS="--foreign $DEBOOTSTRAP_PARAMS" 140 | fi 141 | 142 | # riscv64 is hosted in the debian-ports repository 143 | # debian-ports doesn't include non-free, so we exclude firmware-atheros 144 | if [ $DEBARCH == "riscv64" ]; then 145 | DEBOOTSTRAP_PARAMS="--keyring /usr/share/keyrings/debian-ports-archive-keyring.gpg --exclude firmware-atheros $DEBOOTSTRAP_PARAMS http://deb.debian.org/debian-ports" 146 | fi 147 | sudo debootstrap $DEBOOTSTRAP_PARAMS 148 | 149 | # 2. debootstrap stage: only necessary if target != host architecture 150 | 151 | if [ $FOREIGN = "true" ]; then 152 | sudo cp $(which qemu-$ARCH-static) $DIR/$(which qemu-$ARCH-static) 153 | sudo chroot $DIR /bin/bash -c "/debootstrap/debootstrap --second-stage" 154 | fi 155 | 156 | # Set some defaults and enable promtless ssh to the machine for root. 157 | sudo sed -i '/^root/ { s/:x:/::/ }' $DIR/etc/passwd 158 | echo 'T0:23:respawn:/sbin/getty -L ttyS0 115200 vt100' | sudo tee -a $DIR/etc/inittab 159 | printf 'auto lo\niface lo inet loopback\nauto eth0\niface eth0 inet dhcp\n' | sudo tee -a $DIR/etc/network/interfaces 160 | echo '/dev/root / ext4 defaults 0 0' | sudo tee -a $DIR/etc/fstab 161 | echo 'debugfs /sys/kernel/debug debugfs defaults 0 0' | sudo tee -a $DIR/etc/fstab 162 | echo 'securityfs /sys/kernel/security securityfs defaults 0 0' | sudo tee -a $DIR/etc/fstab 163 | echo 'configfs /sys/kernel/config/ configfs defaults 0 0' | sudo tee -a $DIR/etc/fstab 164 | echo 'binfmt_misc /proc/sys/fs/binfmt_misc binfmt_misc defaults 0 0' | sudo tee -a $DIR/etc/fstab 165 | echo -en "127.0.0.1\tlocalhost\n" | sudo tee $DIR/etc/hosts 166 | echo "nameserver 8.8.8.8" | sudo tee -a $DIR/etc/resolve.conf 167 | echo "syzkaller" | sudo tee $DIR/etc/hostname 168 | ssh-keygen -f $RELEASE.id_rsa -t rsa -N '' 169 | sudo mkdir -p $DIR/root/.ssh/ 170 | cat $RELEASE.id_rsa.pub | sudo tee $DIR/root/.ssh/authorized_keys 171 | 172 | # Add perf support 173 | if [ $PERF = "true" ]; then 174 | cp -r $KERNEL $DIR/tmp/ 175 | BASENAME=$(basename $KERNEL) 176 | sudo chroot $DIR /bin/bash -c "apt-get update; apt-get install -y flex bison python-dev libelf-dev libunwind8-dev libaudit-dev libslang2-dev libperl-dev binutils-dev liblzma-dev libnuma-dev" 177 | sudo chroot $DIR /bin/bash -c "cd /tmp/$BASENAME/tools/perf/; make" 178 | sudo chroot $DIR /bin/bash -c "cp /tmp/$BASENAME/tools/perf/perf /usr/bin/" 179 | rm -r $DIR/tmp/$BASENAME 180 | fi 181 | 182 | # Add udev rules for custom drivers. 183 | # Create a /dev/vim2m symlink for the device managed by the vim2m driver 184 | # Create udev rule to create virtio serial symlink 185 | echo -e 'ATTR{name}=="vim2m", SYMLINK+="vim2m"\nKERNEL=="vport*", ATTR{name}=="?*", SYMLINK+="virtio-ports/$attr{name}"' | sudo tee -a $DIR/etc/udev/rules.d/50-udev-default.rules 186 | 187 | # Build a disk image 188 | dd if=/dev/zero of=$RELEASE.img bs=1M seek=$SEEK count=1 189 | sudo mkfs.ext4 -F $RELEASE.img 190 | sudo mkdir -p ./mnt 191 | sudo mount -o loop $RELEASE.img ./mnt 192 | sudo cp -a $DIR/. ./mnt/. 193 | sudo umount ./mnt 194 | qemu-img convert -f raw -O qcow2 stretch.img stretch.qcow2 -------------------------------------------------------------------------------- /tools/memtracedump/memtracedump.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "path" 9 | "strings" 10 | 11 | "github.com/rss/kit/exec" 12 | "github.com/rss/kit/trace" 13 | ) 14 | 15 | var ( 16 | flagTrace = flag.String("trace", "", "trace path, exclude .json") 17 | flagCallIdx = flag.Int("call_idx", -1, "call index") 18 | flagOut = flag.String("out", "", "output path") 19 | ) 20 | 21 | func memtraceToString(t *exec.MemTrace) string { 22 | s := "" 23 | switch t.Type { 24 | case exec.MemtraceWrite: 25 | s = fmt.Sprintf("w, pc = 0x%x, addr = 0x%x, len = %d", t.PC, t.Addr, t.Len) 26 | case exec.MemtraceRead: 27 | s = fmt.Sprintf("r, pc = 0x%x, addr = 0x%x, len = %d", t.PC, t.Addr, t.Len) 28 | case exec.MemtraceRet: 29 | s = fmt.Sprintf("ret, pc = 0x%x", t.PC) 30 | case exec.MemtraceCall: 31 | s = fmt.Sprintf("call, pc = 0x%x", t.PC) 32 | default: 33 | log.Fatalf("invalid memory trace type %v", t.Type) 34 | } 35 | return s 36 | } 37 | 38 | func main() { 39 | flag.Parse() 40 | trName := path.Base(*flagTrace) 41 | trDir := path.Dir(*flagTrace) 42 | trInfo, err := trace.LoadTraceInfoByTraceName(trDir, trName) 43 | if err != nil { 44 | log.Fatalf("cannot open trace: %v", err) 45 | } 46 | memTr, err := trace.LoadMemtrace(trInfo, trDir) 47 | if err != nil { 48 | log.Fatalf("cannot load memory trace: %v", err) 49 | } 50 | callIdx := *flagCallIdx 51 | if len(memTr) <= callIdx { 52 | log.Fatalf("call index %v >= %v", callIdx, len(memTr)) 53 | } 54 | of, err := os.OpenFile(*flagOut, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) 55 | if err != nil { 56 | log.Fatalf("cannot create file: %v", err) 57 | } 58 | arr := []string{} 59 | for _, t := range memTr[callIdx] { 60 | arr = append(arr, memtraceToString(t)) 61 | } 62 | of.WriteString(strings.Join(arr, "\n")) 63 | of.Close() 64 | } 65 | -------------------------------------------------------------------------------- /tools/pmcrand/pmcrand.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "log" 7 | "math/rand" 8 | "os" 9 | "strconv" 10 | "strings" 11 | "time" 12 | 13 | "github.com/rss/kit/pgen" 14 | "github.com/rss/kit/trace" 15 | ) 16 | 17 | var ( 18 | flagTraceDir = flag.String("trace_dir", "", "trace direcotry") 19 | flagRandPath = flag.String("rand_path", "", "random prediction path") 20 | flagNum = flag.Int("num", 500000, "number of test cases for full random ") 21 | ) 22 | 23 | func GetAllProgs(traceDir string) map[int]bool { 24 | traceNames, err := trace.AllTraceInfoNames(traceDir) 25 | if err != nil { 26 | log.Fatalf("cannot get trace names: %v", err) 27 | } 28 | allProg := map[int]bool{} 29 | for _, name_prefix := range traceNames { 30 | name := strings.Split(name_prefix, `_`)[0] 31 | progNum, err := strconv.Atoi(name) 32 | if err != nil { 33 | log.Fatalf("cannot convert program name %v to integer: %v", name, err) 34 | } 35 | allProg[progNum] = true 36 | } 37 | return allProg 38 | } 39 | 40 | func genMemTestInfoNew(idx int, addr, pc uint64, width uint8, read bool, progMap map[int]bool) *pgen.MemTestInfo { 41 | progs := []int{} 42 | for p := range progMap { 43 | progs = append(progs, p) 44 | } 45 | return &pgen.MemTestInfo{ 46 | Addr: addr, 47 | PC: pc, 48 | Width: width, 49 | Read: read, 50 | Progs: progs, 51 | Idx: idx, 52 | } 53 | } 54 | 55 | func genMemRelGenFullRand(allProg map[int]bool, num int) *pgen.TestPred { 56 | gen := &pgen.TestPred{ 57 | Mem: []*pgen.MemTestInfo{}, 58 | Cls: []pgen.ClsTest{}, 59 | } 60 | nProgs := len(allProg) 61 | for p := range allProg { 62 | pm := map[int]bool{p: true} 63 | gen.Mem = append(gen.Mem, genMemTestInfoNew(len(gen.Mem), 0, 0, 1, true, pm)) 64 | } 65 | gen.Cls = []pgen.ClsTest{ 66 | {}, 67 | } 68 | log.Printf("random: total test programs: %v", (uint64)(nProgs)) 69 | log.Printf("random: restrict test cases to: %v", num) 70 | vis := map[int]map[int]bool{} 71 | cnt := 0 72 | rand.Seed(time.Now().UnixNano()) 73 | for { 74 | aIdx := rand.Int() % nProgs 75 | vIdx := rand.Int() % nProgs 76 | if vis[aIdx][vIdx] { 77 | continue 78 | } 79 | if _, ok := vis[aIdx]; !ok { 80 | vis[aIdx] = map[int]bool{} 81 | } 82 | vis[aIdx][vIdx] = true 83 | gen.Cls[0].Tests = append(gen.Cls[0].Tests, pgen.MemPair{ 84 | WMemIdx: aIdx, 85 | RMemIdx: vIdx, 86 | }) 87 | cnt++ 88 | if cnt%10000 == 0 { 89 | log.Printf("Full rand: generate %v test cases...", cnt) 90 | } 91 | if cnt == num { 92 | break 93 | } 94 | } 95 | return gen 96 | } 97 | 98 | func serialize(gen *pgen.TestPred, path string) error { 99 | f, err := os.Create(path) 100 | if err != nil { 101 | return err 102 | } 103 | enc := json.NewEncoder(f) 104 | enc.SetIndent(" ", "\t") 105 | err = enc.Encode(gen) 106 | return err 107 | } 108 | func main() { 109 | flag.Parse() 110 | traceDir := *flagTraceDir 111 | numTests := *flagNum 112 | 113 | allProg := GetAllProgs(traceDir) 114 | genRand := genMemRelGenFullRand(allProg, numTests) 115 | 116 | err := serialize(genRand, *flagRandPath) 117 | if err != nil { 118 | log.Fatalf("cannot encode full rand predictions: %v", err) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /tools/predstat/predstat.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "log" 7 | "os" 8 | "sync" 9 | "sync/atomic" 10 | "time" 11 | 12 | "github.com/rss/kit/pgen" 13 | ) 14 | 15 | var ( 16 | flagPred = flag.String("pred", "", "pred path") 17 | flagThread = flag.Int("thread", 1, "number of threads (default 1)") 18 | ) 19 | 20 | func syncSetMap(m []map[int]map[int]bool, mu []sync.Mutex, s int, rr []int) int { 21 | mIdx := s % len(mu) 22 | mu[mIdx].Lock() 23 | defer mu[mIdx].Unlock() 24 | cnt := 0 25 | for _, r := range rr { 26 | if _, ok := m[mIdx][s]; ok { 27 | if m[mIdx][s][r] { 28 | continue 29 | } 30 | m[mIdx][s][r] = true 31 | } else { 32 | m[mIdx][s] = make(map[int]bool) 33 | m[mIdx][s][r] = true 34 | } 35 | cnt++ 36 | } 37 | return cnt 38 | } 39 | 40 | func memPairTestCaseCounter(p *pgen.TestPred, mpChan chan pgen.MemPair, m []map[int]map[int]bool, mu []sync.Mutex, tcCnt, mpDone *uint64) { 41 | for { 42 | mp, ok := <-mpChan 43 | if !ok { 44 | return 45 | } 46 | for _, wp := range p.Mem[mp.WMemIdx].Progs { 47 | cnt := syncSetMap(m, mu, wp, p.Mem[mp.RMemIdx].Progs) 48 | atomic.AddUint64(tcCnt, uint64(cnt)) 49 | } 50 | atomic.AddUint64(mpDone, 1) 51 | } 52 | } 53 | 54 | func main() { 55 | flag.Parse() 56 | f, err := os.Open(*flagPred) 57 | if err != nil { 58 | log.Fatalf("cannot open prediction file: %v", err) 59 | } 60 | defer f.Close() 61 | dec := json.NewDecoder(f) 62 | pred := &pgen.TestPred{} 63 | err = dec.Decode(pred) 64 | if err != nil { 65 | log.Fatalf("cannot decode prediction file: %v", err) 66 | } 67 | m := []map[int]map[int]bool{} 68 | mu := []sync.Mutex{} 69 | // more memory for less contention 70 | partitionRatio := 1000 71 | for i := 0; i < *flagThread*partitionRatio; i++ { 72 | m = append(m, map[int]map[int]bool{}) 73 | mu = append(mu, sync.Mutex{}) 74 | } 75 | 76 | mpDone := uint64(0) 77 | tcCnt := uint64(0) 78 | totalMP := 0 79 | for _, cls := range pred.Cls { 80 | totalMP += len(cls.Tests) 81 | } 82 | go func() { 83 | for { 84 | <-time.After(5 * time.Second) 85 | mpDoneL := atomic.LoadUint64(&mpDone) 86 | mpDoneRatio := (float64(mpDoneL) / float64(totalMP)) * 100.0 87 | log.Printf("processed %v/%v (%.2f%%) memory pairs, total test cases = %v...", 88 | atomic.LoadUint64(&mpDone), 89 | totalMP, 90 | mpDoneRatio, 91 | atomic.LoadUint64(&tcCnt), 92 | ) 93 | } 94 | }() 95 | mpChan := make(chan pgen.MemPair) 96 | for i := 0; i < *flagThread; i++ { 97 | go memPairTestCaseCounter(pred, mpChan, m, mu, &tcCnt, &mpDone) 98 | } 99 | for _, cls := range pred.Cls { 100 | for _, mp := range cls.Tests { 101 | mpChan <- mp 102 | } 103 | } 104 | log.Printf("total test cases = %v...", 105 | atomic.LoadUint64(&tcCnt), 106 | ) 107 | close(mpChan) 108 | } 109 | -------------------------------------------------------------------------------- /tools/resanalyze/resanalyze.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "os" 10 | "path" 11 | "runtime" 12 | "sort" 13 | "strings" 14 | "time" 15 | 16 | "github.com/google/syzkaller/prog" 17 | "github.com/rss/kit/ns" 18 | "github.com/rss/kit/result" 19 | "github.com/rss/kit/trace" 20 | ) 21 | 22 | var ( 23 | flagProgDir = flag.String("prog_dir", "", "program directory") 24 | flagResultDir = flag.String("result_dir", "", "result directory") 25 | flagResultCluster = flag.String("result_cluster", "", "result cluster path") 26 | ) 27 | 28 | // r_call -> s_call -> diff node path -> diff value -> result name 29 | type ClusterMap map[string]map[string]map[string]map[string][]ResultInfo 30 | 31 | type ResultInfo struct { 32 | TimeStamp string `json:"time_stamp"` 33 | Name string `json:"name"` 34 | } 35 | 36 | type ClusterResult struct { 37 | CallClusters []*RCallClusterResult `json:"r_call_clusters"` 38 | } 39 | 40 | type RCallClusterResult struct { 41 | RCallName string `json:"r_call_name"` 42 | Size int `json:"size"` 43 | SCallClusters []*SCallClusterResult `json:"s_call_clusters"` 44 | } 45 | 46 | type SCallClusterResult struct { 47 | SCallName string `json:"s_call_name"` 48 | Size int `json:"size"` 49 | NodePathClusters []*NodePathClusterResult `json:"node_path_clusters"` 50 | } 51 | 52 | type NodePathClusterResult struct { 53 | NodePath string `json:"node_path"` 54 | DiffValClusters []*DiffVallClusterResult `json:"diff_val_clusters"` 55 | } 56 | 57 | type DiffVallClusterResult struct { 58 | DiffVal string `json:"diff_val"` 59 | Names []ResultInfo `json:"names"` 60 | } 61 | 62 | func (c ClusterMap) Serialize() ClusterResult { 63 | clusterRes := ClusterResult{} 64 | for call, sCallMap := range c { 65 | rCallRes := RCallClusterResult{RCallName: call, Size: 0} 66 | for sCall, ndMap := range sCallMap { 67 | sCallRes := SCallClusterResult{SCallName: sCall, Size: 0} 68 | for nodePath, nodePathMap := range ndMap { 69 | nodePathRes := NodePathClusterResult{NodePath: nodePath} 70 | for diffVal, names := range nodePathMap { 71 | diffValRes := DiffVallClusterResult{DiffVal: diffVal, Names: names} 72 | sort.Slice(diffValRes.Names, func(i, j int) bool { 73 | return diffValRes.Names[i].TimeStamp < diffValRes.Names[j].TimeStamp 74 | }) 75 | nodePathRes.DiffValClusters = append(nodePathRes.DiffValClusters, &diffValRes) 76 | rCallRes.Size += len(names) 77 | sCallRes.Size += len(names) 78 | } 79 | sCallRes.NodePathClusters = append(sCallRes.NodePathClusters, &nodePathRes) 80 | } 81 | rCallRes.SCallClusters = append(rCallRes.SCallClusters, &sCallRes) 82 | } 83 | clusterRes.CallClusters = append(clusterRes.CallClusters, &rCallRes) 84 | } 85 | return clusterRes 86 | } 87 | 88 | var total_rep = 0 89 | 90 | func (c ClusterMap) Add(target *prog.Target, progDir string, res *result.TestResult, resName string) error { 91 | if res.Timeout || res.VProgHanged { 92 | return nil 93 | } 94 | if len(res.ACallDiag) == 0 { 95 | return nil 96 | } 97 | aProgBytes, err := ioutil.ReadFile(path.Join(progDir, res.AProgName)) 98 | if err != nil { 99 | return fmt.Errorf("cannot read program file: %v", err) 100 | } 101 | aProg, err := target.Deserialize(aProgBytes, prog.NonStrict) 102 | if err != nil { 103 | return fmt.Errorf("cannot deserailze program file: %v", err) 104 | } 105 | vProgBytes, err := ioutil.ReadFile(path.Join(progDir, res.VProgName)) 106 | if err != nil { 107 | return fmt.Errorf("cannot read program file: %v", err) 108 | } 109 | vProg, err := target.Deserialize(vProgBytes, prog.NonStrict) 110 | if err != nil { 111 | return fmt.Errorf("cannot deserailze program file: %v", err) 112 | } 113 | 114 | // Only keep the first interfered receiver system call since the rest 115 | // interfered calls are usually the secondary results of the first one, 116 | // e.g., file descritor number shift. 117 | res.VDiff = res.VDiff[:1] 118 | 119 | va := ns.ResourceCallAnalysis(vProg) 120 | aa := ns.ResourceCallAnalysis(aProg) 121 | 122 | diffs := []*trace.SCTraceDiff{} 123 | aCalls := []string{} 124 | vCalls := []string{} 125 | 126 | tmpMap := map[int]int{} 127 | for i, aCallIdx := range res.ACallDiag { 128 | vCallIdx := res.VCallDiag[i] 129 | if _, ok := tmpMap[vCallIdx]; !ok { 130 | tmpMap[vCallIdx] = -1 131 | } 132 | if tmpMap[vCallIdx] < aCallIdx { 133 | tmpMap[vCallIdx] = aCallIdx 134 | } 135 | } 136 | newACalls := []int{} 137 | newVCalls := []int{} 138 | for vc, ac := range tmpMap { 139 | newVCalls = append(newVCalls, vc) 140 | newACalls = append(newACalls, ac) 141 | } 142 | 143 | for i, aCallIdx := range newACalls { 144 | aCall := "" 145 | vCall := "" 146 | vCallIdx := newVCalls[i] 147 | callMatchInfo, ok := va[vCallIdx] 148 | if !ok { 149 | continue 150 | } 151 | maRsMap := map[string]bool{} 152 | for _, defUse := range callMatchInfo.RsInfo { 153 | maRsMap[defUse.RsName] = true 154 | } 155 | maRsArr := []string{} 156 | for rs := range maRsMap { 157 | maRsArr = append(maRsArr, rs) 158 | } 159 | sort.Strings(maRsArr) 160 | maRsName := strings.Join(maRsArr, "|") 161 | vCall = callMatchInfo.Name + ":" + maRsName 162 | if callMatchInfo, ok := aa[aCallIdx]; ok { 163 | maRsMap := map[string]bool{} 164 | for _, defUse := range callMatchInfo.RsInfo { 165 | maRsMap[defUse.RsName] = true 166 | } 167 | maRsArr := []string{} 168 | for rs := range maRsMap { 169 | maRsArr = append(maRsArr, rs) 170 | } 171 | sort.Strings(maRsArr) 172 | maRsName := strings.Join(maRsArr, "|") 173 | aCall = callMatchInfo.Name + ":" + maRsName 174 | } else { 175 | aCall = aProg.Calls[aCallIdx].Meta.Name 176 | } 177 | for _, diff := range res.VDiff { 178 | if diff.CallIdx != vCallIdx { 179 | continue 180 | } 181 | diffs = append(diffs, diff) 182 | aCalls = append(aCalls, aCall) 183 | vCalls = append(vCalls, vCall) 184 | } 185 | 186 | } 187 | 188 | for i, diff := range diffs { 189 | vCall := vCalls[i] 190 | aCall := aCalls[i] 191 | nodePath := diff.NodePath 192 | diffVal := diff.T 193 | if _, ok := c[vCall][aCall][nodePath][diffVal]; !ok { 194 | if _, ok := c[vCall][aCall][nodePath]; !ok { 195 | if _, ok := c[vCall][aCall]; !ok { 196 | if _, ok := c[vCall]; !ok { 197 | c[vCall] = make(map[string]map[string]map[string][]ResultInfo) 198 | } 199 | c[vCall][aCall] = make(map[string]map[string][]ResultInfo) 200 | } 201 | c[vCall][aCall][nodePath] = make(map[string][]ResultInfo) 202 | } 203 | c[vCall][aCall][nodePath][diffVal] = []ResultInfo{} 204 | } 205 | c[vCall][aCall][nodePath][diffVal] = append(c[vCall][aCall][nodePath][diffVal], ResultInfo{Name: resName, TimeStamp: res.TimeStamp}) 206 | total_rep++ 207 | } 208 | return nil 209 | 210 | } 211 | 212 | func main() { 213 | flag.Parse() 214 | cluster := ClusterMap{} 215 | files, err := ioutil.ReadDir(*flagResultDir) 216 | if err != nil { 217 | log.Fatalf("cannot list directory: %v", err) 218 | } 219 | target, err := prog.GetTarget(runtime.GOOS, runtime.GOARCH) 220 | if err != nil { 221 | log.Fatalf("cannot get target: %v", err) 222 | } 223 | printEvery := 50 224 | // read all results 225 | resArr := []*result.TestResult{} 226 | resNameArr := []string{} 227 | filteredResArr := []*result.TestResult{} 228 | filteredResNameArr := []string{} 229 | earliestTs := time.Now() 230 | for i, fi := range files { 231 | var res result.TestResult 232 | resultPath := path.Join(*flagResultDir, fi.Name()) 233 | resultBytes, err := ioutil.ReadFile(resultPath) 234 | if err != nil { 235 | log.Fatalf("cannot read result file: %v", err) 236 | } 237 | err = json.Unmarshal(resultBytes, &res) 238 | if err != nil { 239 | log.Fatalf("cannot unmarshal result file: %v", err) 240 | } 241 | resArr = append(resArr, &res) 242 | resNameArr = append(resNameArr, fi.Name()) 243 | ts, err := time.Parse(result.TimeStampFormat, res.TimeStamp) 244 | if err != nil { 245 | log.Fatalf("cannot parse time stamp: %v", err) 246 | } 247 | if ts.Before(earliestTs) { 248 | earliestTs = ts 249 | } 250 | if i%printEvery == 0 { 251 | log.Printf("reading %%%v(%v/%v) files...", 100.0*float64(i)/float64(len(files)), i, len(files)) 252 | } 253 | } 254 | // time filter 255 | for i, res := range resArr { 256 | filteredResArr = append(filteredResArr, res) 257 | filteredResNameArr = append(filteredResNameArr, resNameArr[i]) 258 | if i%printEvery == 0 { 259 | log.Printf("filtering %%%v(%v/%v) results...", 100.0*float64(i)/float64(len(resArr)), i, len(resArr)) 260 | } 261 | } 262 | // processing 263 | for i, res := range filteredResArr { 264 | err := cluster.Add(target, *flagProgDir, res, filteredResNameArr[i]) 265 | if err != nil { 266 | log.Fatalf("cannot add result file to cluster: %v", err) 267 | } 268 | if i%printEvery == 0 { 269 | log.Printf("process %%%v(%v/%v) results...", 100.0*float64(i)/float64(len(filteredResArr)), i, len(filteredResArr)) 270 | } 271 | } 272 | r_cls_cnt := 0 273 | rs_cls_cnt := 0 274 | for _, c := range cluster { 275 | for range c { 276 | rs_cls_cnt++ 277 | } 278 | r_cls_cnt++ 279 | } 280 | log.Printf("receiver cluster: %v", r_cls_cnt) 281 | log.Printf("receiver-sender cluster: %v", rs_cls_cnt) 282 | log.Printf("total report: %v", total_rep) 283 | clusterRes := cluster.Serialize() 284 | f, err := os.Create(*flagResultCluster) 285 | if err != nil { 286 | log.Fatalf("cannot create result cluster file: %v", err) 287 | 288 | } 289 | defer f.Close() 290 | enc := json.NewEncoder(f) 291 | enc.SetIndent(" ", "\t") 292 | err = enc.Encode(clusterRes) 293 | if err != nil { 294 | log.Fatalf("cannot create encode result cluster: %v", err) 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /trace/trace.go: -------------------------------------------------------------------------------- 1 | package trace 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "path" 9 | "reflect" 10 | "strings" 11 | "unsafe" 12 | 13 | "github.com/rss/kit/exec" 14 | ) 15 | 16 | // Provide paths in info json file so other non-golang programs 17 | // can read information from it. 18 | 19 | type TraceInfo struct { 20 | Name string `json:"name"` 21 | Attack bool `json:"attack"` 22 | ScTraceName string `json:"sctrace_name"` 23 | MemTraceName []string `json:"memtrace_name"` 24 | MemTraceNum []int `json:"memtrace_num"` 25 | ExecFlags exec.ExecFlags `json:"exec_flags"` 26 | Timeout bool `json:"timeout"` 27 | Hanged bool `json:"hanged"` 28 | } 29 | 30 | func NewTraceInfo(name string, attack, timeout, hanged bool) *TraceInfo { 31 | return &TraceInfo{Name: name, Attack: attack, Timeout: timeout, Hanged: hanged} 32 | } 33 | 34 | func TraceName(name string, attack bool) (string, error) { 35 | if len(name) == 0 { 36 | return "", fmt.Errorf("info doesn't have name") 37 | } 38 | if attack { 39 | return name + "_A", nil 40 | } else { 41 | return name + "_V", nil 42 | } 43 | } 44 | 45 | func SaveMemtrace(info *TraceInfo, dir string, calls []exec.CallInfo) error { 46 | 47 | base, err := TraceName(info.Name, info.Attack) 48 | if err != nil { 49 | return fmt.Errorf("cannot get trace name: %v", err) 50 | } 51 | memBase := path.Join(dir, base+".mem") 52 | for i := 0; i < len(calls); i++ { 53 | mem := memBase + fmt.Sprintf(".%v", i) 54 | info.MemTraceNum = append(info.MemTraceNum, calls[i].MemtraceNum) 55 | info.MemTraceName = append(info.MemTraceName, path.Base(mem)) 56 | err := ioutil.WriteFile(mem, calls[i].MemtraceBuf, 0666) 57 | if err != nil { 58 | return fmt.Errorf("cannot save memtrace: %v", err) 59 | } 60 | } 61 | return nil 62 | } 63 | 64 | func LoadMemtrace(info *TraceInfo, dir string) ([][]*exec.MemTrace, error) { 65 | t := [][]*exec.MemTrace{} 66 | base, err := TraceName(info.Name, info.Attack) 67 | if err != nil { 68 | return nil, fmt.Errorf("cannot get trace name: %v", err) 69 | } 70 | memBase := path.Join(dir, base+".mem") 71 | for i, traceNum := range info.MemTraceNum { 72 | var pkts []exec.RawMemtracePkt 73 | var pkt exec.RawMemtracePkt 74 | 75 | t = append(t, []*exec.MemTrace{}) 76 | if traceNum == 0 { 77 | continue 78 | } 79 | memPath := memBase + fmt.Sprintf(".%v", i) 80 | buf, err := ioutil.ReadFile(memPath) 81 | if err != nil { 82 | return nil, fmt.Errorf("cannot read trace: %v", err) 83 | } 84 | pktSize := int(unsafe.Sizeof(pkt)) 85 | if (len(buf) % pktSize) != 0 { 86 | return nil, fmt.Errorf("memtrace file size %v is not multiple of %v", len(buf), pktSize) 87 | } 88 | hdr := (*reflect.SliceHeader)((unsafe.Pointer(&pkts))) 89 | hdr.Data = uintptr(unsafe.Pointer(&buf[0])) 90 | hdr.Len = len(buf) / pktSize 91 | hdr.Cap = hdr.Len 92 | cnt := 0 93 | for j := 0; j < len(pkts); j++ { 94 | m := pkts[j].ToMemTraces() 95 | n := 16 96 | if traceNum-cnt < 16 { 97 | n = traceNum - cnt 98 | } 99 | t[i] = append(t[i], m[:n]...) 100 | cnt += n 101 | } 102 | } 103 | return t, nil 104 | } 105 | 106 | func SaveSCTrace(info *TraceInfo, dir string, scTrace [][]string) error { 107 | 108 | base, err := TraceName(info.Name, info.Attack) 109 | if err != nil { 110 | return fmt.Errorf("cannot get trace name: %v", err) 111 | } 112 | scPath := path.Join(dir, base+".sc") 113 | f, err := os.Create(scPath) 114 | if err != nil { 115 | return fmt.Errorf("cannot create sc file: %v", err) 116 | } 117 | defer f.Close() 118 | info.ScTraceName = base + ".sc" 119 | enc := json.NewEncoder(f) 120 | err = enc.Encode(scTrace) 121 | if err != nil { 122 | return fmt.Errorf("cannot encode sc: %v", err) 123 | } 124 | return nil 125 | } 126 | 127 | func LoadSCTrace(info *TraceInfo, dir string) ([][]string, error) { 128 | scPath := path.Join(dir, info.ScTraceName) 129 | f, err := os.Open(scPath) 130 | if err != nil { 131 | return nil, fmt.Errorf("cannot load sc file: %v", err) 132 | } 133 | defer f.Close() 134 | dec := json.NewDecoder(f) 135 | sc := [][]string{} 136 | err = dec.Decode(&sc) 137 | if err != nil { 138 | return nil, fmt.Errorf("cannot decode sc: %v", err) 139 | } 140 | return sc, nil 141 | } 142 | 143 | func SaveTraceInfo(info *TraceInfo, dir string) error { 144 | 145 | base, err := TraceName(info.Name, info.Attack) 146 | if err != nil { 147 | return fmt.Errorf("cannot get trace name: %v", err) 148 | } 149 | infoPath := path.Join(dir, base+".json") 150 | infStr, err := json.MarshalIndent(info, "", "\t") 151 | if err != nil { 152 | return fmt.Errorf("cannot convert info to json: %v", err) 153 | } 154 | err = ioutil.WriteFile(infoPath, []byte(infStr), 0666) 155 | if err != nil { 156 | return fmt.Errorf("cannot save info: %v", err) 157 | } 158 | return nil 159 | } 160 | 161 | func LoadTraceInfoByProgName(dir, progName string, attack bool) (*TraceInfo, error) { 162 | traceName, err := TraceName(progName, attack) 163 | if err != nil { 164 | return nil, fmt.Errorf("cannot get trace name: %v", err) 165 | } 166 | info, err := LoadTraceInfoByTraceName(dir, traceName) 167 | if err != nil { 168 | return nil, err 169 | } 170 | return info, nil 171 | } 172 | 173 | func LoadTraceInfoByTraceName(dir, traceName string) (*TraceInfo, error) { 174 | infoPath := path.Join(dir, traceName+".json") 175 | info := &TraceInfo{} 176 | infoBytes, err := ioutil.ReadFile(infoPath) 177 | if err != nil { 178 | return nil, fmt.Errorf("read info file: %v", err) 179 | } 180 | err = json.Unmarshal(infoBytes, info) 181 | if err != nil { 182 | return nil, fmt.Errorf("parse json: %v", err) 183 | } 184 | return info, nil 185 | } 186 | 187 | func AllTraceInfoNames(dir string) ([]string, error) { 188 | traceFiles, err := ioutil.ReadDir(dir) 189 | if err != nil { 190 | return nil, fmt.Errorf("open trace dir: %v", err) 191 | } 192 | names := make([]string, 0, len(traceFiles)/3) 193 | for _, f := range traceFiles { 194 | l := strings.Split(f.Name(), ".") 195 | name := l[0] 196 | ty := l[1] 197 | if ty == "json" { 198 | names = append(names, name) 199 | } 200 | } 201 | return names, nil 202 | } 203 | -------------------------------------------------------------------------------- /util/err.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "syscall" 5 | 6 | "github.com/google/syzkaller/pkg/log" 7 | ) 8 | 9 | // gracefully terminate the whole program and report error, 10 | // ONLY use after SIGINT handler is registered !!! 11 | func GFatalf(msg string, args ...interface{}) { 12 | log.Logf(0, "[FATAL] "+msg, args...) 13 | syscall.Kill(syscall.Getpid(), syscall.SIGINT) 14 | select {} 15 | } 16 | -------------------------------------------------------------------------------- /util/serialize.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/gob" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | 10 | "github.com/google/syzkaller/prog" 11 | ) 12 | 13 | func ToGobFile(e interface{}, p string) error { 14 | f, err := os.Create(p) 15 | if err != nil { 16 | return fmt.Errorf("cannot create file: %v", err) 17 | } 18 | defer f.Close() 19 | enc := gob.NewEncoder(f) 20 | err = enc.Encode(e) 21 | if err != nil { 22 | return fmt.Errorf("cannot encode: %v", err) 23 | } 24 | return nil 25 | } 26 | 27 | func FromGobFile(d interface{}, p string) error { 28 | f, err := os.Open(p) 29 | if err != nil { 30 | return fmt.Errorf("cannot open file: %v", err) 31 | } 32 | defer f.Close() 33 | dec := gob.NewDecoder(f) 34 | err = dec.Decode(d) 35 | if err != nil { 36 | return fmt.Errorf("cannot decode: %v", err) 37 | } 38 | return nil 39 | } 40 | 41 | func ToJsonFile(e interface{}, p string) error { 42 | f, err := os.Create(p) 43 | if err != nil { 44 | return fmt.Errorf("cannot create file: %v", err) 45 | } 46 | defer f.Close() 47 | enc := json.NewEncoder(f) 48 | enc.SetIndent(" ", "\t") 49 | err = enc.Encode(e) 50 | if err != nil { 51 | return fmt.Errorf("cannot encode: %v", err) 52 | } 53 | return nil 54 | } 55 | 56 | func FromJsonFile(d interface{}, p string) error { 57 | f, err := os.Open(p) 58 | if err != nil { 59 | return fmt.Errorf("cannot open file: %v", err) 60 | } 61 | defer f.Close() 62 | dec := json.NewDecoder(f) 63 | err = dec.Decode(d) 64 | if err != nil { 65 | return fmt.Errorf("cannot decode: %v", err) 66 | } 67 | return nil 68 | } 69 | 70 | func ReadProg(progPath string, target *prog.Target, mode prog.DeserializeMode) (*prog.Prog, error) { 71 | data, err := ioutil.ReadFile(progPath) 72 | if err != nil { 73 | return nil, fmt.Errorf("cannot open program file: %v", err) 74 | } 75 | p, err := target.Deserialize(data, mode) 76 | if err != nil { 77 | return nil, fmt.Errorf("cannot deserialize program: %v", err) 78 | } 79 | return p, nil 80 | } 81 | -------------------------------------------------------------------------------- /vm/comm/comm.go: -------------------------------------------------------------------------------- 1 | package comm 2 | 3 | import ( 4 | "io" 5 | "time" 6 | ) 7 | 8 | type HostComm interface { 9 | io.ReadWriter 10 | SetRWDeadline(deadline time.Time) error 11 | } 12 | 13 | type GuestComm interface { 14 | io.ReadWriteCloser 15 | } 16 | -------------------------------------------------------------------------------- /vm/qemu/qmp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 syzkaller project authors. All rights reserved. 2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 | 4 | package qemu 5 | 6 | import ( 7 | "encoding/json" 8 | "fmt" 9 | "net" 10 | ) 11 | 12 | type qmpVersion struct { 13 | Package string 14 | QEMU struct { 15 | Major int 16 | Micro int 17 | Minor int 18 | } 19 | } 20 | 21 | type qmpBanner struct { 22 | QMP struct { 23 | Version qmpVersion 24 | } 25 | } 26 | 27 | type qmpCommand struct { 28 | Execute string `json:"execute"` 29 | Arguments interface{} `json:"arguments,omitempty"` 30 | } 31 | 32 | type hmpCommand struct { 33 | Command string `json:"command-line"` 34 | CPU int `json:"cpu-index"` 35 | } 36 | 37 | type qmpResponse struct { 38 | Error struct { 39 | Class string 40 | Desc string 41 | } 42 | Return interface{} 43 | } 44 | 45 | func (inst *instance) qmpConnCheck() error { 46 | if inst.mon != nil { 47 | return nil 48 | } 49 | 50 | addr := fmt.Sprintf("127.0.0.1:%v", inst.monport) 51 | conn, err := net.Dial("tcp", addr) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | monDec := json.NewDecoder(conn) 57 | monEnc := json.NewEncoder(conn) 58 | 59 | var banner qmpBanner 60 | if err := monDec.Decode(&banner); err != nil { 61 | return err 62 | } 63 | 64 | inst.monEnc = monEnc 65 | inst.monDec = monDec 66 | if _, err := inst.doQmp(&qmpCommand{Execute: "qmp_capabilities"}); err != nil { 67 | inst.monEnc = nil 68 | inst.monDec = nil 69 | return err 70 | } 71 | inst.mon = conn 72 | 73 | return nil 74 | } 75 | 76 | func (inst *instance) qmpRecv() (*qmpResponse, error) { 77 | qmp := new(qmpResponse) 78 | err := inst.monDec.Decode(qmp) 79 | 80 | return qmp, err 81 | } 82 | 83 | func (inst *instance) doQmp(cmd *qmpCommand) (*qmpResponse, error) { 84 | if err := inst.monEnc.Encode(cmd); err != nil { 85 | return nil, err 86 | } 87 | return inst.qmpRecv() 88 | } 89 | 90 | func (inst *instance) qmp(cmd *qmpCommand) (interface{}, error) { 91 | if err := inst.qmpConnCheck(); err != nil { 92 | return nil, err 93 | } 94 | resp, err := inst.doQmp(cmd) 95 | if err != nil { 96 | return resp.Return, err 97 | } 98 | if resp.Error.Desc != "" { 99 | return resp.Return, fmt.Errorf("error %v", resp.Error) 100 | } 101 | if resp.Return == nil { 102 | return nil, fmt.Errorf(`no "return" nor "error" in [%v]`, resp) 103 | } 104 | return resp.Return, nil 105 | } 106 | 107 | func (inst *instance) hmp(cmd string, cpu int) (string, error) { 108 | req := &qmpCommand{ 109 | Execute: "human-monitor-command", 110 | Arguments: &hmpCommand{ 111 | Command: cmd, 112 | CPU: cpu, 113 | }, 114 | } 115 | resp, err := inst.qmp(req) 116 | if err != nil { 117 | return "", err 118 | } 119 | return resp.(string), nil 120 | } 121 | -------------------------------------------------------------------------------- /vm/qemu/snapshot.go: -------------------------------------------------------------------------------- 1 | package qemu 2 | 3 | func (inst *instance) myhmp(cmd string) (err error) { 4 | req := &qmpCommand{ 5 | Execute: "human-monitor-command", 6 | Arguments: &struct { 7 | Command string `json:"command-line"` 8 | }{ 9 | Command: cmd, 10 | }, 11 | } 12 | if err := inst.qmpConnCheck(); err != nil { 13 | return err 14 | } 15 | if err := inst.monEnc.Encode(req); err != nil { 16 | return err 17 | } 18 | for { 19 | res := make(map[string]interface{}) 20 | err = inst.monDec.Decode(&res) 21 | if err != nil { 22 | return 23 | } 24 | if _, ok := res["timestamp"]; ok { 25 | continue 26 | } 27 | if _, ok := res["return"]; ok { 28 | break 29 | } 30 | } 31 | return 32 | } 33 | 34 | func (inst *instance) SaveSnapshot() error { 35 | return inst.myhmp("savevm img") 36 | } 37 | 38 | func (inst *instance) LoadSnapshot() error { 39 | return inst.myhmp("loadvm img") 40 | } 41 | 42 | func (inst *instance) Pause() error { 43 | return inst.myhmp("stop") 44 | } 45 | 46 | func (inst *instance) Resume() error { 47 | return inst.myhmp("cont") 48 | } 49 | -------------------------------------------------------------------------------- /vm/qemu/virtio.go: -------------------------------------------------------------------------------- 1 | package qemu 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "path" 8 | "sync" 9 | "syscall" 10 | "time" 11 | ) 12 | 13 | type VirtioSerialHost struct { 14 | Index int 15 | hostFifoIn *os.File // host commmunication handler 16 | hostFifoOut *os.File // host commmunication handler 17 | hostFifoPath string 18 | } 19 | 20 | type VirtioSerialGuest struct { 21 | Index int 22 | guestFile *os.File 23 | guestDevPath string 24 | lock sync.Mutex 25 | } 26 | 27 | func virtioSerialName(index int) string { 28 | return fmt.Sprintf("virtio-serial-%v", index) 29 | } 30 | 31 | func InitVirtioGuest(index int) (*VirtioSerialGuest, error) { 32 | var v *VirtioSerialGuest 33 | var err error 34 | 35 | v = &VirtioSerialGuest{ 36 | Index: index, 37 | // We don't know the exact mapping between virtio-serial index and /dev/vportNp1, 38 | // as observed that N might start from 0 in some cases. 39 | // With a special udev rule: 40 | // KERNEL=="vport*", ATTR{name}=="?*", SYMLINK+="virtio-ports/$attr{name} 41 | // /dev/virtio-ports/{name} will be a symlink to actual /dev/vportNp1. 42 | // So we can establish the mapping between virtio-serial name and /dev/vportNp1. 43 | guestDevPath: fmt.Sprintf("/dev/virtio-ports/%v", virtioSerialName(index)), 44 | } 45 | v.guestFile, err = os.OpenFile(v.guestDevPath, os.O_RDWR, 0000) 46 | if err != nil { 47 | return nil, fmt.Errorf("cannot open virtio device %v: %v", v.guestDevPath, err) 48 | } 49 | return v, nil 50 | } 51 | 52 | const MaxChunkSize = 8 << 10 53 | 54 | var ErrHostNotConnected = errors.New("host not connected") 55 | 56 | func (v *VirtioSerialGuest) Read(data []byte) (int, error) { 57 | v.lock.Lock() 58 | defer v.lock.Unlock() 59 | return v.guestFile.Read(data) 60 | } 61 | 62 | func (v *VirtioSerialGuest) Write(data []byte) (int, error) { 63 | v.lock.Lock() 64 | defer v.lock.Unlock() 65 | return v.guestFile.Write(data) 66 | } 67 | 68 | func (v *VirtioSerialGuest) Close() error { 69 | return v.guestFile.Close() 70 | } 71 | 72 | // qemu args to create virtio serial device 73 | func (v *VirtioSerialHost) VMArg() []string { 74 | return []string{ 75 | "-chardev", 76 | fmt.Sprintf("pipe,id=chardev-%v,path=%v", v.Index, v.hostFifoPath), 77 | "-device", 78 | "virtio-serial", 79 | "-device", 80 | fmt.Sprintf("virtserialport,chardev=chardev-%v,name=%v", v.Index, virtioSerialName(v.Index)), 81 | } 82 | } 83 | 84 | // Init virtio host fifo. Serial port is always set to 1. 85 | func initVirtioHost(index int, workdir string) (*VirtioSerialHost, error) { 86 | 87 | var fifo, fifo_in, fifo_out string 88 | var err error 89 | var v *VirtioSerialHost 90 | 91 | // create fifo in workdir 92 | fifo = path.Join(workdir, fmt.Sprintf("virtio_pipe_%v", index)) 93 | fifo_in = fifo + ".in" 94 | fifo_out = fifo + ".out" 95 | err = syscall.Mkfifo(fifo_in, 0666) 96 | if err != nil { 97 | return nil, err 98 | } 99 | err = syscall.Mkfifo(fifo_out, 0666) 100 | if err != nil { 101 | return nil, err 102 | } 103 | v = &VirtioSerialHost{ 104 | Index: index, 105 | hostFifoPath: fifo, 106 | } 107 | v.hostFifoIn, err = os.OpenFile(fifo_in, os.O_RDWR, 0) 108 | if err != nil { 109 | return nil, err 110 | } 111 | v.hostFifoOut, err = os.OpenFile(fifo_out, os.O_RDWR, 0) 112 | if err != nil { 113 | return nil, err 114 | } 115 | 116 | return v, nil 117 | } 118 | 119 | func (v *VirtioSerialHost) SetRWDeadline(deadline time.Time) error { 120 | err := v.hostFifoIn.SetDeadline(deadline) 121 | if err != nil { 122 | return err 123 | } 124 | err = v.hostFifoOut.SetDeadline(deadline) 125 | return err 126 | } 127 | 128 | func (v *VirtioSerialHost) Read(data []byte) (int, error) { 129 | return v.hostFifoOut.Read(data) 130 | } 131 | 132 | func (v *VirtioSerialHost) Write(data []byte) (int, error) { 133 | return v.hostFifoIn.Write(data) 134 | } 135 | 136 | func (v *VirtioSerialHost) Close() error { 137 | err := v.hostFifoIn.Close() 138 | if err != nil { 139 | return err 140 | } 141 | err = v.hostFifoOut.Close() 142 | return err 143 | 144 | } 145 | -------------------------------------------------------------------------------- /vm/vm.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 syzkaller project authors. All rights reserved. 2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 | 4 | // Package vm provides an abstract test machine (VM, physical machine, etc) 5 | // interface for the rest of the system. 6 | // For convenience test machines are subsequently collectively called VMs. 7 | // Package wraps vmimpl package interface with some common functionality 8 | // and higher-level interface. 9 | package vm 10 | 11 | import ( 12 | "bytes" 13 | "fmt" 14 | "io" 15 | "os" 16 | "os/exec" 17 | "path/filepath" 18 | "time" 19 | 20 | "github.com/google/syzkaller/pkg/osutil" 21 | "github.com/google/syzkaller/pkg/report" 22 | "github.com/google/syzkaller/sys/targets" 23 | "github.com/rss/kit/vm/comm" 24 | "github.com/rss/kit/vm/qemu" 25 | "github.com/rss/kit/vm/vmimpl" 26 | 27 | // Import all VM implementations, so that users only need to import vm. 28 | _ "github.com/rss/kit/vm/qemu" 29 | ) 30 | 31 | type Pool struct { 32 | impl vmimpl.Pool 33 | workdir string 34 | template string 35 | timeouts targets.Timeouts 36 | } 37 | 38 | type Instance struct { 39 | impl vmimpl.Instance 40 | workdir string 41 | timeouts targets.Timeouts 42 | index int 43 | } 44 | 45 | var ( 46 | Shutdown = vmimpl.Shutdown 47 | ErrTimeout = vmimpl.ErrTimeout 48 | _ BootErrorer = vmimpl.BootError{} 49 | ) 50 | 51 | type BootErrorer interface { 52 | BootError() (string, []byte) 53 | } 54 | 55 | // AllowsOvercommit returns if the instance type allows overcommit of instances 56 | // (i.e. creation of instances out-of-thin-air). Overcommit is used during image 57 | // and patch testing in syz-ci when it just asks for more than specified in config 58 | // instances. Generally virtual machines (qemu, gce) support overcommit, 59 | // while physical machines (adb, isolated) do not. Strictly speaking, we should 60 | // never use overcommit and use only what's specified in config, because we 61 | // override resource limits specified in config (e.g. can OOM). But it works and 62 | // makes lots of things much simpler. 63 | func AllowsOvercommit(typ string) bool { 64 | return vmimpl.Types[typ].Overcommit 65 | } 66 | 67 | // Create creates a VM pool that can be used to create individual VMs. 68 | func Create(vmType string, env *vmimpl.Env) (*Pool, error) { 69 | typ, ok := vmimpl.Types[vmType] 70 | if !ok { 71 | return nil, fmt.Errorf("unknown instance type '%v'", vmType) 72 | } 73 | impl, err := typ.Ctor(env) 74 | if err != nil { 75 | return nil, err 76 | } 77 | return &Pool{ 78 | impl: impl, 79 | workdir: env.Workdir, 80 | template: env.Template, 81 | timeouts: env.Timeouts, 82 | }, nil 83 | } 84 | 85 | func (pool *Pool) Count() int { 86 | return pool.impl.Count() 87 | } 88 | 89 | func (pool *Pool) Create(index int) (*Instance, error) { 90 | if index < 0 || index >= pool.Count() { 91 | return nil, fmt.Errorf("invalid VM index %v (count %v)", index, pool.Count()) 92 | } 93 | workdir, err := osutil.ProcessTempDir(pool.workdir) 94 | if err != nil { 95 | return nil, fmt.Errorf("failed to create instance temp dir: %v", err) 96 | } 97 | if pool.template != "" { 98 | if err := osutil.CopyDirRecursively(pool.template, filepath.Join(workdir, "template")); err != nil { 99 | return nil, err 100 | } 101 | } 102 | impl, err := pool.impl.Create(workdir, index) 103 | if err != nil { 104 | os.RemoveAll(workdir) 105 | return nil, err 106 | } 107 | return &Instance{ 108 | impl: impl, 109 | workdir: workdir, 110 | timeouts: pool.timeouts, 111 | index: index, 112 | }, nil 113 | } 114 | 115 | func InitGuestComm(vmType, commType string, index int) (comm.GuestComm, error) { 116 | if vmType == "qemu" && commType == "virtio" { 117 | return qemu.InitVirtioGuest(index) 118 | } else { 119 | return nil, fmt.Errorf("unsupported guest comm %v/%v", vmType, commType) 120 | } 121 | } 122 | 123 | func (inst *Instance) LoadSnapshot() error { 124 | return inst.impl.LoadSnapshot() 125 | } 126 | 127 | func (inst *Instance) SaveSnapshot() error { 128 | return inst.impl.SaveSnapshot() 129 | } 130 | 131 | func (inst *Instance) CommType() string { 132 | return inst.impl.CommType() 133 | } 134 | 135 | func (inst *Instance) Comm(index int) comm.HostComm { 136 | return inst.impl.Comm(index) 137 | } 138 | 139 | func (inst *Instance) Pause() error { 140 | return inst.impl.Pause() 141 | } 142 | 143 | func (inst *Instance) Resume() error { 144 | return inst.impl.Pause() 145 | } 146 | 147 | func (inst *Instance) Image() string { 148 | return inst.impl.Image() 149 | } 150 | 151 | func (inst *Instance) RawRun(command string) (*exec.Cmd, io.WriteCloser, io.ReadCloser, io.ReadCloser, error) { 152 | return inst.impl.RawRun(command) 153 | } 154 | 155 | func (inst *Instance) Copy(hostSrc string) (string, error) { 156 | return inst.impl.Copy(hostSrc) 157 | } 158 | 159 | func (inst *Instance) Forward(port int) (string, error) { 160 | return inst.impl.Forward(port) 161 | } 162 | 163 | func (inst *Instance) Run(timeout time.Duration, stop <-chan bool, command string) ( 164 | outc <-chan []byte, errc <-chan error, err error) { 165 | return inst.impl.Run(timeout, stop, command) 166 | } 167 | 168 | func (inst *Instance) Info() ([]byte, error) { 169 | if ii, ok := inst.impl.(vmimpl.Infoer); ok { 170 | return ii.Info() 171 | } 172 | return nil, nil 173 | } 174 | 175 | func (inst *Instance) diagnose(rep *report.Report) ([]byte, bool) { 176 | if rep == nil { 177 | panic("rep is nil") 178 | } 179 | return inst.impl.Diagnose(rep) 180 | } 181 | 182 | func (inst *Instance) Close() { 183 | inst.impl.Close() 184 | os.RemoveAll(inst.workdir) 185 | } 186 | 187 | type ExitCondition int 188 | 189 | const ( 190 | // The program is allowed to exit after timeout. 191 | ExitTimeout = ExitCondition(1 << iota) 192 | // The program is allowed to exit with no errors. 193 | ExitNormal 194 | // The program is allowed to exit with errors. 195 | ExitError 196 | ) 197 | 198 | // MonitorExecution monitors execution of a program running inside of a VM. 199 | // It detects kernel oopses in output, lost connections, hangs, etc. 200 | // outc/errc is what vm.Instance.Run returns, reporter parses kernel output for oopses. 201 | // Exit says which exit modes should be considered as errors/OK. 202 | // Returns a non-symbolized crash report, or nil if no error happens. 203 | func (inst *Instance) MonitorExecution(outc <-chan []byte, errc <-chan error, 204 | reporter *report.Reporter, exit ExitCondition) (rep *report.Report) { 205 | mon := &monitor{ 206 | inst: inst, 207 | outc: outc, 208 | errc: errc, 209 | reporter: reporter, 210 | exit: exit, 211 | } 212 | lastExecuteTime := time.Now() 213 | ticker := time.NewTicker(tickerPeriod * inst.timeouts.Scale) 214 | defer ticker.Stop() 215 | for { 216 | select { 217 | case err := <-errc: 218 | switch err { 219 | case nil: 220 | // The program has exited without errors, 221 | // but wait for kernel output in case there is some delayed oops. 222 | crash := "" 223 | if mon.exit&ExitNormal == 0 { 224 | crash = lostConnectionCrash 225 | } 226 | return mon.extractError(crash) 227 | case ErrTimeout: 228 | if mon.exit&ExitTimeout == 0 { 229 | return mon.extractError(timeoutCrash) 230 | } 231 | return nil 232 | default: 233 | // Note: connection lost can race with a kernel oops message. 234 | // In such case we want to return the kernel oops. 235 | crash := "" 236 | if mon.exit&ExitError == 0 { 237 | crash = lostConnectionCrash 238 | } 239 | return mon.extractError(crash) 240 | } 241 | case out, ok := <-outc: 242 | if !ok { 243 | outc = nil 244 | continue 245 | } 246 | lastPos := len(mon.output) 247 | mon.output = append(mon.output, out...) 248 | if bytes.Contains(mon.output[lastPos:], executingProgram1) || 249 | bytes.Contains(mon.output[lastPos:], executingProgram2) { 250 | lastExecuteTime = time.Now() 251 | } 252 | if reporter.ContainsCrash(mon.output[mon.matchPos:]) { 253 | return mon.extractError("unknown error") 254 | } 255 | if len(mon.output) > 2*beforeContext { 256 | copy(mon.output, mon.output[len(mon.output)-beforeContext:]) 257 | mon.output = mon.output[:beforeContext] 258 | } 259 | // Find the starting position for crash matching on the next iteration. 260 | // We step back from the end of output by maxErrorLength to handle the case 261 | // when a crash line is currently split/incomplete. And then we try to find 262 | // the preceding '\n' to have a full line. This is required to handle 263 | // the case when a particular pattern is ignored as crash, but a suffix 264 | // of the pattern is detected as crash (e.g. "ODEBUG:" is trimmed to "BUG:"). 265 | mon.matchPos = len(mon.output) - maxErrorLength 266 | for i := 0; i < maxErrorLength; i++ { 267 | if mon.matchPos <= 0 || mon.output[mon.matchPos-1] == '\n' { 268 | break 269 | } 270 | mon.matchPos-- 271 | } 272 | if mon.matchPos < 0 { 273 | mon.matchPos = 0 274 | } 275 | case <-ticker.C: 276 | // Detect both "no output whatsoever" and "kernel episodically prints 277 | // something to console, but fuzzer is not actually executing programs". 278 | if time.Since(lastExecuteTime) > inst.timeouts.NoOutput { 279 | return mon.extractError(noOutputCrash) 280 | } 281 | case <-Shutdown: 282 | return nil 283 | } 284 | } 285 | } 286 | 287 | type monitor struct { 288 | inst *Instance 289 | outc <-chan []byte 290 | errc <-chan error 291 | reporter *report.Reporter 292 | exit ExitCondition 293 | output []byte 294 | matchPos int 295 | } 296 | 297 | func (mon *monitor) extractError(defaultError string) *report.Report { 298 | diagOutput, diagWait := []byte{}, false 299 | if defaultError != "" { 300 | diagOutput, diagWait = mon.inst.diagnose(mon.createReport(defaultError)) 301 | } 302 | // Give it some time to finish writing the error message. 303 | // But don't wait for "no output", we already waited enough. 304 | if defaultError != noOutputCrash || diagWait { 305 | mon.waitForOutput() 306 | } 307 | if bytes.Contains(mon.output, []byte(fuzzerPreemptedStr)) { 308 | return nil 309 | } 310 | if defaultError == "" && mon.reporter.ContainsCrash(mon.output[mon.matchPos:]) { 311 | // We did not call Diagnose above because we thought there is no error, so call it now. 312 | diagOutput, diagWait = mon.inst.diagnose(mon.createReport(defaultError)) 313 | if diagWait { 314 | mon.waitForOutput() 315 | } 316 | } 317 | rep := mon.createReport(defaultError) 318 | if rep == nil { 319 | return nil 320 | } 321 | if len(diagOutput) > 0 { 322 | rep.Output = append(rep.Output, vmDiagnosisStart...) 323 | rep.Output = append(rep.Output, diagOutput...) 324 | } 325 | return rep 326 | } 327 | 328 | func (mon *monitor) createReport(defaultError string) *report.Report { 329 | rep := mon.reporter.ParseFrom(mon.output, mon.matchPos) 330 | if rep == nil { 331 | if defaultError == "" { 332 | return nil 333 | } 334 | return &report.Report{ 335 | Title: defaultError, 336 | Output: mon.output, 337 | Suppressed: report.IsSuppressed(mon.reporter, mon.output), 338 | } 339 | } 340 | start := rep.StartPos - beforeContext 341 | if start < 0 { 342 | start = 0 343 | } 344 | end := rep.EndPos + afterContext 345 | if end > len(rep.Output) { 346 | end = len(rep.Output) 347 | } 348 | rep.Output = rep.Output[start:end] 349 | rep.StartPos -= start 350 | rep.EndPos -= start 351 | return rep 352 | } 353 | 354 | func (mon *monitor) waitForOutput() { 355 | timer := time.NewTimer(waitForOutputTimeout * mon.inst.timeouts.Scale) 356 | defer timer.Stop() 357 | for { 358 | select { 359 | case out, ok := <-mon.outc: 360 | if !ok { 361 | return 362 | } 363 | mon.output = append(mon.output, out...) 364 | case <-timer.C: 365 | return 366 | case <-Shutdown: 367 | return 368 | } 369 | } 370 | } 371 | 372 | const ( 373 | maxErrorLength = 256 374 | 375 | lostConnectionCrash = "lost connection to test machine" 376 | noOutputCrash = "no output from test machine" 377 | timeoutCrash = "timed out" 378 | fuzzerPreemptedStr = "SYZ-FUZZER: PREEMPTED" 379 | vmDiagnosisStart = "\nVM DIAGNOSIS:\n" 380 | ) 381 | 382 | var ( 383 | executingProgram1 = []byte("executing program") // syz-fuzzer output 384 | executingProgram2 = []byte("executed programs:") // syz-execprog output 385 | 386 | beforeContext = 1024 << 10 387 | afterContext = 128 << 10 388 | 389 | tickerPeriod = 10 * time.Second 390 | waitForOutputTimeout = 10 * time.Second 391 | ) 392 | -------------------------------------------------------------------------------- /vm/vmimpl/console.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 syzkaller project authors. All rights reserved. 2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 | 4 | package vmimpl 5 | 6 | import ( 7 | "fmt" 8 | "io" 9 | "os/exec" 10 | "sync" 11 | "syscall" 12 | 13 | "github.com/google/syzkaller/pkg/osutil" 14 | "golang.org/x/sys/unix" 15 | ) 16 | 17 | // Tested on Suzy-Q and BeagleBone. 18 | func OpenConsole(con string) (rc io.ReadCloser, err error) { 19 | fd, err := syscall.Open(con, syscall.O_RDONLY|syscall.O_NOCTTY|syscall.O_SYNC, 0) 20 | if err != nil { 21 | return nil, fmt.Errorf("failed to open console file: %v", err) 22 | } 23 | defer func() { 24 | if fd != -1 { 25 | syscall.Close(fd) 26 | } 27 | }() 28 | term, err := unix.IoctlGetTermios(fd, syscallTCGETS) 29 | if err != nil { 30 | return nil, fmt.Errorf("failed to get console termios: %v", err) 31 | } 32 | // No parity bit, only need 1 stop bit, no hardware flowcontrol, 33 | term.Cflag &^= unixCBAUD | unix.CSIZE | unix.PARENB | unix.CSTOPB | unixCRTSCTS 34 | // Ignore modem controls. 35 | term.Cflag |= unix.B115200 | unix.CS8 | unix.CLOCAL | unix.CREAD 36 | // Setup for non-canonical mode. 37 | term.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | 38 | unix.IGNCR | unix.ICRNL | unix.IXON 39 | term.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN 40 | term.Oflag &^= unix.OPOST 41 | term.Cc[unix.VMIN] = 0 42 | term.Cc[unix.VTIME] = 10 // 1 second timeout 43 | if err = unix.IoctlSetTermios(fd, syscallTCSETS, term); err != nil { 44 | return nil, fmt.Errorf("failed to get console termios: %v", err) 45 | } 46 | tmp := fd 47 | fd = -1 48 | return &tty{fd: tmp}, nil 49 | } 50 | 51 | type tty struct { 52 | mu sync.Mutex 53 | fd int 54 | } 55 | 56 | func (t *tty) Read(buf []byte) (int, error) { 57 | t.mu.Lock() 58 | defer t.mu.Unlock() 59 | if t.fd == -1 { 60 | return 0, io.EOF 61 | } 62 | n, err := syscall.Read(t.fd, buf) 63 | if n < 0 { 64 | n = 0 65 | } 66 | return n, err 67 | } 68 | 69 | func (t *tty) Close() error { 70 | t.mu.Lock() 71 | defer t.mu.Unlock() 72 | if t.fd != -1 { 73 | syscall.Close(t.fd) 74 | t.fd = -1 75 | } 76 | return nil 77 | } 78 | 79 | // OpenRemoteKernelLog accesses to the host where Android VM runs on, not Android VM itself. 80 | // The host stores all kernel outputs of Android VM so in case of crashes nothing will be lost. 81 | func OpenRemoteKernelLog(ip, console string) (rc io.ReadCloser, err error) { 82 | rpipe, wpipe, err := osutil.LongPipe() 83 | if err != nil { 84 | return nil, err 85 | } 86 | conAddr := "vsoc-01@" + ip 87 | cmd := osutil.Command("ssh", conAddr, "tail", "-f", console) 88 | cmd.Stdout = wpipe 89 | cmd.Stderr = wpipe 90 | if _, err := cmd.StdinPipe(); err != nil { 91 | rpipe.Close() 92 | wpipe.Close() 93 | return nil, err 94 | } 95 | if err := cmd.Start(); err != nil { 96 | rpipe.Close() 97 | wpipe.Close() 98 | return nil, fmt.Errorf("failed to connect to console server: %v", err) 99 | } 100 | wpipe.Close() 101 | con := &remoteCon{ 102 | cmd: cmd, 103 | rpipe: rpipe, 104 | } 105 | return con, nil 106 | } 107 | 108 | // Open dmesg remotely. 109 | func OpenRemoteConsole(bin string, args ...string) (rc io.ReadCloser, err error) { 110 | rpipe, wpipe, err := osutil.LongPipe() 111 | if err != nil { 112 | return nil, err 113 | } 114 | args = append(args, "dmesg -w") 115 | cmd := osutil.Command(bin, args...) 116 | cmd.Stdout = wpipe 117 | cmd.Stderr = wpipe 118 | if err := cmd.Start(); err != nil { 119 | rpipe.Close() 120 | wpipe.Close() 121 | return nil, fmt.Errorf("failed to start adb: %v", err) 122 | } 123 | wpipe.Close() 124 | con := &remoteCon{ 125 | cmd: cmd, 126 | rpipe: rpipe, 127 | } 128 | return con, err 129 | } 130 | 131 | // OpenAdbConsole provides fallback console output using 'adb shell dmesg -w'. 132 | func OpenAdbConsole(bin, dev string) (rc io.ReadCloser, err error) { 133 | return OpenRemoteConsole(bin, "-s", dev, "shell") 134 | } 135 | 136 | type remoteCon struct { 137 | closeMu sync.Mutex 138 | readMu sync.Mutex 139 | cmd *exec.Cmd 140 | rpipe io.ReadCloser 141 | } 142 | 143 | func (t *remoteCon) Read(buf []byte) (int, error) { 144 | t.readMu.Lock() 145 | n, err := t.rpipe.Read(buf) 146 | t.readMu.Unlock() 147 | return n, err 148 | } 149 | 150 | func (t *remoteCon) Close() error { 151 | t.closeMu.Lock() 152 | cmd := t.cmd 153 | t.cmd = nil 154 | t.closeMu.Unlock() 155 | if cmd == nil { 156 | return nil 157 | } 158 | 159 | cmd.Process.Kill() 160 | 161 | t.readMu.Lock() 162 | t.rpipe.Close() 163 | t.readMu.Unlock() 164 | 165 | cmd.Process.Wait() 166 | return nil 167 | } 168 | -------------------------------------------------------------------------------- /vm/vmimpl/console_linux_386.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 syzkaller project authors. All rights reserved. 2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 | 4 | package vmimpl 5 | 6 | import ( 7 | "golang.org/x/sys/unix" 8 | ) 9 | 10 | // Builds but is not tested. 11 | const ( 12 | unixCBAUD = unix.CBAUD 13 | unixCRTSCTS = unix.CRTSCTS 14 | syscallTCGETS = unix.TCGETS2 15 | syscallTCSETS = unix.TCSETS2 16 | ) 17 | -------------------------------------------------------------------------------- /vm/vmimpl/console_linux_amd64.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 syzkaller project authors. All rights reserved. 2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 | 4 | package vmimpl 5 | 6 | import ( 7 | "golang.org/x/sys/unix" 8 | ) 9 | 10 | const ( 11 | unixCBAUD = unix.CBAUD 12 | unixCRTSCTS = unix.CRTSCTS 13 | syscallTCGETS = unix.TCGETS2 14 | syscallTCSETS = unix.TCSETS2 15 | ) 16 | -------------------------------------------------------------------------------- /vm/vmimpl/linux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 syzkaller project authors. All rights reserved. 2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 | 4 | package vmimpl 5 | 6 | import ( 7 | "regexp" 8 | "strings" 9 | 10 | "github.com/google/syzkaller/pkg/report" 11 | ) 12 | 13 | // DiagnoseLinux diagnoses some Linux kernel bugs over the provided ssh callback. 14 | func DiagnoseLinux(rep *report.Report, ssh func(args ...string) ([]byte, error)) (output []byte, wait, handled bool) { 15 | if !strings.Contains(rep.Title, "MAX_LOCKDEP") { 16 | return nil, false, false 17 | } 18 | // Dump /proc/lockdep* files on BUG: MAX_LOCKDEP_{KEYS,ENTRIES,CHAINS,CHAIN_HLOCKS} too low! 19 | output, err := ssh("cat", "/proc/lockdep_stats", "/proc/lockdep", "/proc/lockdep_chains") 20 | if err != nil { 21 | output = append(output, err.Error()...) 22 | } 23 | // Remove mangled pointer values, they take lots of space but don't add any value. 24 | output = regexp.MustCompile(` *\[?[0-9a-f]{8,}\]?\s*`).ReplaceAll(output, nil) 25 | return output, false, true 26 | } 27 | -------------------------------------------------------------------------------- /vm/vmimpl/merger.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 syzkaller project authors. All rights reserved. 2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 | 4 | package vmimpl 5 | 6 | import ( 7 | "bytes" 8 | "fmt" 9 | "io" 10 | "sync" 11 | ) 12 | 13 | type OutputMerger struct { 14 | Output chan []byte 15 | Err chan error 16 | teeMu sync.Mutex 17 | tee io.Writer 18 | wg sync.WaitGroup 19 | } 20 | 21 | type MergerError struct { 22 | Name string 23 | R io.ReadCloser 24 | Err error 25 | } 26 | 27 | func (err MergerError) Error() string { 28 | return fmt.Sprintf("failed to read from %v: %v", err.Name, err.Err) 29 | } 30 | 31 | func NewOutputMerger(tee io.Writer) *OutputMerger { 32 | return &OutputMerger{ 33 | Output: make(chan []byte, 1000), 34 | Err: make(chan error, 1), 35 | tee: tee, 36 | } 37 | } 38 | 39 | func (merger *OutputMerger) Wait() { 40 | merger.wg.Wait() 41 | close(merger.Output) 42 | } 43 | 44 | func (merger *OutputMerger) Add(name string, r io.ReadCloser) { 45 | merger.AddDecoder(name, r, nil) 46 | } 47 | 48 | func (merger *OutputMerger) AddDecoder(name string, r io.ReadCloser, 49 | decoder func(data []byte) (start, size int, decoded []byte)) { 50 | merger.wg.Add(1) 51 | go func() { 52 | var pending []byte 53 | var proto []byte 54 | var buf [4 << 10]byte 55 | for { 56 | n, err := r.Read(buf[:]) 57 | if n != 0 { 58 | if decoder != nil { 59 | proto = append(proto, buf[:n]...) 60 | start, size, decoded := decoder(proto) 61 | proto = proto[start+size:] 62 | if len(decoded) != 0 { 63 | merger.Output <- decoded // note: this can block 64 | } 65 | } 66 | // Remove all carriage returns. 67 | buf := buf[:n] 68 | if bytes.IndexByte(buf, '\r') != -1 { 69 | buf = bytes.ReplaceAll(buf, []byte("\r"), nil) 70 | } 71 | pending = append(pending, buf...) 72 | if pos := bytes.LastIndexByte(pending, '\n'); pos != -1 { 73 | out := pending[:pos+1] 74 | if merger.tee != nil { 75 | merger.teeMu.Lock() 76 | merger.tee.Write(out) 77 | merger.teeMu.Unlock() 78 | } 79 | select { 80 | case merger.Output <- append([]byte{}, out...): 81 | r := copy(pending, pending[pos+1:]) 82 | pending = pending[:r] 83 | default: 84 | } 85 | } 86 | } 87 | if err != nil { 88 | if len(pending) != 0 { 89 | pending = append(pending, '\n') 90 | if merger.tee != nil { 91 | merger.teeMu.Lock() 92 | merger.tee.Write(pending) 93 | merger.teeMu.Unlock() 94 | } 95 | select { 96 | case merger.Output <- pending: 97 | default: 98 | } 99 | } 100 | r.Close() 101 | select { 102 | case merger.Err <- MergerError{name, r, err}: 103 | default: 104 | } 105 | merger.wg.Done() 106 | return 107 | } 108 | } 109 | }() 110 | } 111 | -------------------------------------------------------------------------------- /vm/vmimpl/merger_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 syzkaller project authors. All rights reserved. 2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 | 4 | package vmimpl 5 | 6 | import ( 7 | "bytes" 8 | "io" 9 | "testing" 10 | "time" 11 | 12 | "github.com/google/syzkaller/pkg/osutil" 13 | ) 14 | 15 | func TestMerger(t *testing.T) { 16 | tee := new(bytes.Buffer) 17 | merger := NewOutputMerger(tee) 18 | 19 | rp1, wp1, err := osutil.LongPipe() 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | defer wp1.Close() 24 | merger.Add("pipe1", rp1) 25 | 26 | rp2, wp2, err := osutil.LongPipe() 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | defer wp2.Close() 31 | merger.Add("pipe2", rp2) 32 | 33 | wp1.Write([]byte("111")) 34 | select { 35 | case <-merger.Output: 36 | t.Fatalf("merger produced incomplete line") 37 | case <-time.After(10 * time.Millisecond): 38 | } 39 | 40 | wp2.Write([]byte("222")) 41 | select { 42 | case <-merger.Output: 43 | t.Fatalf("merger produced incomplete line") 44 | case <-time.After(10 * time.Millisecond): 45 | } 46 | 47 | wp1.Write([]byte("333\n444\r")) 48 | got := string(<-merger.Output) 49 | if want := "111333\n"; got != want { 50 | t.Fatalf("bad line: '%s', want '%s'", got, want) 51 | } 52 | 53 | wp2.Write([]byte("555\r\n666\n\r\r777")) 54 | got = string(<-merger.Output) 55 | if want := "222555\n666\n"; got != want { 56 | t.Fatalf("bad line: '%s', want '%s'", got, want) 57 | } 58 | 59 | wp1.Close() 60 | got = string(<-merger.Output) 61 | if want := "444\n"; got != want { 62 | t.Fatalf("bad line: '%s', want '%s'", got, want) 63 | } 64 | 65 | if err := <-merger.Err; err == nil { 66 | t.Fatalf("merger did not produce an error on pipe close") 67 | } else if merr := err.(MergerError); merr.Name != "pipe1" || merr.R != rp1 || merr.Err != io.EOF { 68 | t.Fatalf("merger produced wrong error: %v", err) 69 | } 70 | 71 | wp2.Close() 72 | got = string(<-merger.Output) 73 | if want := "777\n"; got != want { 74 | t.Fatalf("bad line: '%s', want '%s'", got, want) 75 | } 76 | 77 | merger.Wait() 78 | want := "111333\n222555\n666\n444\n777\n" 79 | if got := tee.String(); got != want { 80 | t.Fatalf("bad tee: '%s', want '%s'", got, want) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /vm/vmimpl/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 syzkaller project authors. All rights reserved. 2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 | 4 | package vmimpl 5 | 6 | import ( 7 | "fmt" 8 | "time" 9 | 10 | "github.com/google/syzkaller/pkg/log" 11 | "github.com/google/syzkaller/pkg/osutil" 12 | "github.com/google/syzkaller/sys/targets" 13 | ) 14 | 15 | // Sleep for d. 16 | // If shutdown is in progress, return false prematurely. 17 | func SleepInterruptible(d time.Duration) bool { 18 | select { 19 | case <-time.After(d): 20 | return true 21 | case <-Shutdown: 22 | return false 23 | } 24 | } 25 | 26 | func WaitForSSH(debug bool, timeout time.Duration, addr, sshKey, sshUser, OS string, port int, stop chan error) error { 27 | pwd := "pwd" 28 | if OS == targets.Windows { 29 | pwd = "dir" 30 | } 31 | startTime := time.Now() 32 | SleepInterruptible(5 * time.Second) 33 | for { 34 | select { 35 | case <-time.After(5 * time.Second): 36 | case err := <-stop: 37 | return err 38 | case <-Shutdown: 39 | return fmt.Errorf("shutdown in progress") 40 | } 41 | args := append(SSHArgs(debug, sshKey, port), sshUser+"@"+addr, pwd) 42 | if debug { 43 | log.Logf(0, "running ssh: %#v", args) 44 | } 45 | _, err := osutil.RunCmd(time.Minute, "", "ssh", args...) 46 | if err == nil { 47 | return nil 48 | } 49 | if debug { 50 | log.Logf(0, "ssh failed: %v", err) 51 | } 52 | if time.Since(startTime) > timeout { 53 | return &osutil.VerboseError{Title: "can't ssh into the instance", Output: []byte(err.Error())} 54 | } 55 | } 56 | } 57 | 58 | func SSHArgs(debug bool, sshKey string, port int) []string { 59 | return sshArgs(debug, sshKey, "-p", port, 0) 60 | } 61 | 62 | func SSHArgsForward(debug bool, sshKey string, port, forwardPort int) []string { 63 | return sshArgs(debug, sshKey, "-p", port, forwardPort) 64 | } 65 | 66 | func SCPArgs(debug bool, sshKey string, port int) []string { 67 | return sshArgs(debug, sshKey, "-P", port, 0) 68 | } 69 | 70 | func sshArgs(debug bool, sshKey, portArg string, port, forwardPort int) []string { 71 | args := []string{ 72 | portArg, fmt.Sprint(port), 73 | "-F", "/dev/null", 74 | "-o", "UserKnownHostsFile=/dev/null", 75 | "-o", "BatchMode=yes", 76 | "-o", "IdentitiesOnly=yes", 77 | "-o", "StrictHostKeyChecking=no", 78 | "-o", "ConnectTimeout=10", 79 | // seems to be required by some sshds on old distros 80 | "-o", "PubkeyAcceptedKeyTypes=+ssh-rsa", 81 | } 82 | if sshKey != "" { 83 | args = append(args, "-i", sshKey) 84 | } 85 | if forwardPort != 0 { 86 | // Forward target port as part of the ssh connection (reverse proxy). 87 | args = append(args, "-R", fmt.Sprintf("%v:127.0.0.1:%v", forwardPort, forwardPort)) 88 | } 89 | if debug { 90 | args = append(args, "-v") 91 | } 92 | return args 93 | } 94 | -------------------------------------------------------------------------------- /vm/vmimpl/vmimpl.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 syzkaller project authors. All rights reserved. 2 | // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 | 4 | // Package vmimpl provides an abstract test machine (VM, physical machine, etc) 5 | // interface for the rest of the system. For convenience test machines are subsequently 6 | // collectively called VMs. 7 | // The package also provides various utility functions for VM implementations. 8 | package vmimpl 9 | 10 | import ( 11 | "errors" 12 | "fmt" 13 | "io" 14 | "math/rand" 15 | "net" 16 | "os/exec" 17 | "strings" 18 | "time" 19 | 20 | "github.com/google/syzkaller/pkg/log" 21 | "github.com/google/syzkaller/pkg/osutil" 22 | "github.com/google/syzkaller/pkg/report" 23 | "github.com/google/syzkaller/sys/targets" 24 | "github.com/rss/kit/vm/comm" 25 | ) 26 | 27 | // Pool represents a set of test machines (VMs, physical devices, etc) of particular type. 28 | type Pool interface { 29 | // Count returns total number of VMs in the pool. 30 | Count() int 31 | 32 | // Create creates and boots a new VM instance. 33 | Create(workdir string, index int) (Instance, error) 34 | } 35 | 36 | // Instance represents a single VM. 37 | type Instance interface { 38 | // Copy copies a hostSrc file into VM and returns file name in VM. 39 | Copy(hostSrc string) (string, error) 40 | 41 | // Forward sets up forwarding from within VM to the given tcp 42 | // port on the host and returns the address to use in VM. 43 | Forward(port int) (string, error) 44 | 45 | // Run runs cmd inside of the VM (think of ssh cmd). 46 | // outc receives combined cmd and kernel console output. 47 | // errc receives either command Wait return error or vmimpl.ErrTimeout. 48 | // Command is terminated after timeout. Send on the stop chan can be used to terminate it earlier. 49 | Run(timeout time.Duration, stop <-chan bool, command string) (outc <-chan []byte, errc <-chan error, err error) 50 | 51 | // Diagnose retrieves additional debugging info from the VM 52 | // (e.g. by sending some sys-rq's or SIGABORT'ing a Go program). 53 | // 54 | // Optionally returns (some or all) of the info directly. If wait == true, 55 | // the caller must wait for the VM to output info directly to its log. 56 | // 57 | // rep describes the reason why Diagnose was called. 58 | Diagnose(rep *report.Report) (diagnosis []byte, wait bool) 59 | 60 | // Close stops and destroys the VM. 61 | Close() 62 | 63 | // –––––––––––––––––––––––– Container Checker VM Requirements ––––––––––––––––––––––––––––– 64 | 65 | // Save snapshot. 66 | SaveSnapshot() error 67 | 68 | // Load snapshot. 69 | LoadSnapshot() error 70 | 71 | // Pause. 72 | Pause() error 73 | 74 | // Resume. 75 | Resume() error 76 | 77 | // Comm type. 78 | CommType() string 79 | 80 | // Get comm. 81 | Comm(index int) comm.HostComm 82 | 83 | // Run command through ssh. 84 | RawRun(command string) (*exec.Cmd, io.WriteCloser, io.ReadCloser, io.ReadCloser, error) 85 | 86 | // Path to image copy 87 | Image() string 88 | } 89 | 90 | // Infoer is an optional interface that can be implemented by Instance. 91 | type Infoer interface { 92 | // MachineInfo returns additional info about the VM, e.g. VMM version/arguments. 93 | Info() ([]byte, error) 94 | } 95 | 96 | // Env contains global constant parameters for a pool of VMs. 97 | type Env struct { 98 | // Unique name 99 | // Can be used for VM name collision resolution if several pools share global name space. 100 | Name string 101 | OS string // target OS 102 | Arch string // target arch 103 | Workdir string 104 | Image string 105 | SSHKey string 106 | SSHUser string 107 | Timeouts targets.Timeouts 108 | Debug bool 109 | Config []byte // json-serialized VM-type-specific config 110 | Template string 111 | CommNum int 112 | LoadVM bool // directly load to snapshot 113 | } 114 | 115 | // BootError is returned by Pool.Create when VM does not boot. 116 | type BootError struct { 117 | Title string 118 | Output []byte 119 | } 120 | 121 | func MakeBootError(err error, output []byte) error { 122 | switch err1 := err.(type) { 123 | case *osutil.VerboseError: 124 | return BootError{err1.Title, append(err1.Output, output...)} 125 | default: 126 | return BootError{err.Error(), output} 127 | } 128 | } 129 | 130 | func (err BootError) Error() string { 131 | return fmt.Sprintf("%v\n%s", err.Title, err.Output) 132 | } 133 | 134 | func (err BootError) BootError() (string, []byte) { 135 | return err.Title, err.Output 136 | } 137 | 138 | // Register registers a new VM type within the package. 139 | func Register(typ string, ctor ctorFunc, allowsOvercommit bool) { 140 | Types[typ] = Type{ 141 | Ctor: ctor, 142 | Overcommit: allowsOvercommit, 143 | } 144 | } 145 | 146 | type Type struct { 147 | Ctor ctorFunc 148 | Overcommit bool 149 | } 150 | 151 | type ctorFunc func(env *Env) (Pool, error) 152 | 153 | var ( 154 | // Close to interrupt all pending operations in all VMs. 155 | Shutdown = make(chan struct{}) 156 | ErrTimeout = errors.New("timeout") 157 | 158 | Types = make(map[string]Type) 159 | ) 160 | 161 | func Multiplex(cmd *exec.Cmd, merger *OutputMerger, console io.Closer, timeout time.Duration, 162 | stop, closed <-chan bool, debug bool) (<-chan []byte, <-chan error, error) { 163 | errc := make(chan error, 1) 164 | signal := func(err error) { 165 | select { 166 | case errc <- err: 167 | default: 168 | } 169 | } 170 | go func() { 171 | select { 172 | case <-time.After(timeout): 173 | signal(ErrTimeout) 174 | case <-stop: 175 | signal(ErrTimeout) 176 | case <-closed: 177 | if debug { 178 | log.Logf(0, "instance closed") 179 | } 180 | signal(fmt.Errorf("instance closed")) 181 | case err := <-merger.Err: 182 | cmd.Process.Kill() 183 | console.Close() 184 | merger.Wait() 185 | if cmdErr := cmd.Wait(); cmdErr == nil { 186 | // If the command exited successfully, we got EOF error from merger. 187 | // But in this case no error has happened and the EOF is expected. 188 | err = nil 189 | } 190 | signal(err) 191 | return 192 | } 193 | cmd.Process.Kill() 194 | console.Close() 195 | merger.Wait() 196 | cmd.Wait() 197 | }() 198 | return merger.Output, errc, nil 199 | } 200 | 201 | func RandomPort() int { 202 | return rand.Intn(64<<10-1<<10) + 1<<10 203 | } 204 | 205 | func UnusedTCPPort() int { 206 | for { 207 | port := RandomPort() 208 | ln, err := net.Listen("tcp", fmt.Sprintf("localhost:%v", port)) 209 | if err == nil { 210 | ln.Close() 211 | return port 212 | } 213 | } 214 | } 215 | 216 | // Escapes double quotes(and nested double quote escapes). Ignores any other escapes. 217 | // Reference: https://www.gnu.org/software/bash/manual/html_node/Double-Quotes.html 218 | func EscapeDoubleQuotes(inp string) string { 219 | var ret strings.Builder 220 | for pos := 0; pos < len(inp); pos++ { 221 | // If inp[pos] is not a double quote or a backslash, just use 222 | // as is. 223 | if inp[pos] != '"' && inp[pos] != '\\' { 224 | ret.WriteByte(inp[pos]) 225 | continue 226 | } 227 | // If it is a double quote, escape. 228 | if inp[pos] == '"' { 229 | ret.WriteString("\\\"") 230 | continue 231 | } 232 | // If we detect a backslash, reescape only if what it's already escaping 233 | // is a double-quotes. 234 | temp := "" 235 | j := pos 236 | for ; j < len(inp); j++ { 237 | if inp[j] == '\\' { 238 | temp += string(inp[j]) 239 | continue 240 | } 241 | // If the escape corresponds to a double quotes, re-escape. 242 | // Else, just use as is. 243 | if inp[j] == '"' { 244 | temp = temp + temp + "\\\"" 245 | } else { 246 | temp += string(inp[j]) 247 | } 248 | break 249 | } 250 | ret.WriteString(temp) 251 | pos = j 252 | } 253 | return ret.String() 254 | } 255 | --------------------------------------------------------------------------------