1
0
out-of-tree/pew.go

409 lines
8.4 KiB
Go
Raw Normal View History

2018-11-17 19:37:04 +00:00
// Copyright 2018 Mikhail Klementev. All rights reserved.
// Use of this source code is governed by a AGPLv3 license
// (or later) that can be found in the LICENSE file.
package main
import (
"errors"
"fmt"
"io/ioutil"
"log"
"math/rand"
"os"
"os/exec"
"runtime"
"strings"
"time"
"github.com/otiai10/copy"
"github.com/remeh/sizedwaitgroup"
2019-06-10 18:55:10 +00:00
"gopkg.in/logrusorgru/aurora.v1"
2018-11-17 19:37:04 +00:00
2019-02-02 21:24:29 +00:00
"code.dumpstack.io/tools/out-of-tree/config"
"code.dumpstack.io/tools/out-of-tree/qemu"
2018-11-17 19:37:04 +00:00
)
var somethingFailed = false
func dockerRun(timeout time.Duration, container, workdir, command string) (
output string, err error) {
cmd := exec.Command("docker", "run", "-v", workdir+":/work",
container, "bash", "-c", "cd /work && "+command)
timer := time.AfterFunc(timeout, func() {
cmd.Process.Kill()
})
defer timer.Stop()
raw, err := cmd.CombinedOutput()
if err != nil {
e := fmt.Sprintf("error `%v` for cmd `%v` with output `%v`",
err, command, string(raw))
err = errors.New(e)
return
}
output = string(raw)
return
2018-11-17 19:37:04 +00:00
}
2018-11-17 20:18:50 +00:00
func build(tmp string, ka config.Artifact, ki config.KernelInfo,
2018-11-17 19:37:04 +00:00
dockerTimeout time.Duration) (outPath, output string, err error) {
target := fmt.Sprintf("%d_%s", rand.Int(), ki.KernelRelease)
tmpSourcePath := tmp + "/source"
err = copy.Copy(ka.SourcePath, tmpSourcePath)
if err != nil {
return
}
outPath = tmpSourcePath + "/" + target
2018-11-17 20:18:50 +00:00
if ka.Type == config.KernelModule {
2018-11-17 19:37:04 +00:00
outPath += ".ko"
}
kernel := "/lib/modules/" + ki.KernelRelease + "/build"
output, err = dockerRun(dockerTimeout, ki.ContainerName,
tmpSourcePath, "make KERNEL="+kernel+" TARGET="+target)
2018-11-17 19:37:04 +00:00
if err != nil {
err = errors.New("make execution error")
return
}
return
}
func cleanDmesg(q *qemu.QemuSystem) (err error) {
start := time.Now()
for {
_, err = q.Command("root", "dmesg -c")
if err == nil {
break
}
time.Sleep(time.Second)
if time.Now().After(start.Add(time.Minute)) {
err = errors.New("Can't connect to qemu")
break
}
}
return
}
2018-11-17 20:18:50 +00:00
func testKernelModule(q *qemu.QemuSystem, ka config.Artifact,
test string) (output string, err error) {
2018-11-17 19:37:04 +00:00
output, err = q.Command("root", test)
// TODO generic checks for WARNING's and so on
return
}
2018-11-17 20:18:50 +00:00
func testKernelExploit(q *qemu.QemuSystem, ka config.Artifact,
test, exploit string) (output string, err error) {
2018-11-17 19:37:04 +00:00
output, err = q.Command("user", "chmod +x "+exploit)
if err != nil {
return
}
randFilePath := fmt.Sprintf("/root/%d", rand.Int())
cmd := fmt.Sprintf("%s %s %s", test, exploit, randFilePath)
output, err = q.Command("user", cmd)
if err != nil {
return
}
_, err = q.Command("root", "stat "+randFilePath)
if err != nil {
return
}
return
}
2018-12-10 02:41:45 +00:00
func genOkFail(name string, ok bool) (aurv aurora.Value) {
2018-11-17 19:37:04 +00:00
if ok {
s := " " + name + " SUCCESS "
2018-12-10 02:41:45 +00:00
aurv = aurora.BgGreen(aurora.Black(s))
2018-11-17 19:37:04 +00:00
} else {
somethingFailed = true
2018-11-17 19:37:04 +00:00
s := " " + name + " FAILURE "
2018-12-10 02:41:45 +00:00
aurv = aurora.BgRed(aurora.Gray(aurora.Bold(s)))
2018-11-17 19:37:04 +00:00
}
2018-12-10 02:41:45 +00:00
return
2018-11-17 19:37:04 +00:00
}
2018-11-17 20:18:50 +00:00
func dumpResult(q *qemu.QemuSystem, ka config.Artifact, ki config.KernelInfo,
2018-12-10 02:35:43 +00:00
buildOk, runOk, testOk *bool) {
2018-11-17 20:18:50 +00:00
2018-11-17 19:37:04 +00:00
distroInfo := fmt.Sprintf("%s-%s {%s}", ki.DistroType,
ki.DistroRelease, ki.KernelRelease)
colored := ""
2018-11-17 20:18:50 +00:00
if ka.Type == config.KernelExploit {
2018-11-17 19:37:04 +00:00
colored = aurora.Sprintf("[*] %40s: %s %s", distroInfo,
2018-12-10 02:35:43 +00:00
genOkFail("BUILD", *buildOk),
genOkFail("LPE", *testOk))
2018-11-17 19:37:04 +00:00
} else {
colored = aurora.Sprintf("[*] %40s: %s %s %s", distroInfo,
2018-12-10 02:35:43 +00:00
genOkFail("BUILD", *buildOk),
genOkFail("INSMOD", *runOk),
genOkFail("TEST", *testOk))
2018-11-17 19:37:04 +00:00
}
additional := ""
if q.KernelPanic {
additional = "(panic)"
} else if q.KilledByTimeout {
additional = "(timeout)"
}
if additional != "" {
fmt.Println(colored, additional)
} else {
fmt.Println(colored)
}
}
2018-11-17 20:18:50 +00:00
func whatever(swg *sizedwaitgroup.SizedWaitGroup, ka config.Artifact,
ki config.KernelInfo, binaryPath, testPath string,
2018-11-17 19:37:04 +00:00
qemuTimeout, dockerTimeout time.Duration) {
defer swg.Done()
kernel := qemu.Kernel{KernelPath: ki.KernelPath, InitrdPath: ki.InitrdPath}
q, err := qemu.NewQemuSystem(qemu.X86_64, kernel, ki.RootFS)
if err != nil {
2018-12-01 14:30:28 +00:00
log.Println("Qemu creation error:", err)
2018-11-17 19:37:04 +00:00
return
}
q.Timeout = qemuTimeout
err = q.Start()
if err != nil {
2018-12-01 14:30:28 +00:00
log.Println("Qemu start error:", err)
2018-11-17 19:37:04 +00:00
return
}
defer q.Stop()
tmp, err := ioutil.TempDir("/tmp/", "out-of-tree_")
if err != nil {
2018-12-01 14:30:28 +00:00
log.Println("Temporary directory creation error:", err)
2018-11-17 19:37:04 +00:00
return
}
defer os.RemoveAll(tmp)
2018-12-10 02:35:43 +00:00
buildOk := false
runOk := false
testOk := false
defer dumpResult(q, ka, ki, &buildOk, &runOk, &testOk)
2018-11-17 19:37:04 +00:00
var outFile, output string
if binaryPath == "" {
// TODO Write build log to file or database
outFile, output, err = build(tmp, ka, ki, dockerTimeout)
if err != nil {
log.Println(output)
return
}
2018-12-10 02:35:43 +00:00
buildOk = true
2018-11-17 19:37:04 +00:00
} else {
outFile = binaryPath
2018-12-10 02:35:43 +00:00
buildOk = true
2018-11-17 19:37:04 +00:00
}
err = cleanDmesg(q)
if err != nil {
return
}
if testPath == "" {
testPath = outFile + "_test"
}
remoteTest := fmt.Sprintf("/tmp/test_%d", rand.Int())
err = q.CopyFile("user", testPath, remoteTest)
if err != nil {
if ka.Type == config.KernelExploit {
log.Println("Use `echo touch FILE | exploit` for test")
q.Command("user",
"echo -e '#!/bin/sh\necho touch $2 | $1' "+
"> "+remoteTest+
" && chmod +x "+remoteTest)
} else {
log.Println("No test, use dummy")
q.Command("user", "echo '#!/bin/sh' "+
"> "+remoteTest+" && chmod +x "+remoteTest)
}
} else {
_, err = q.Command("root", "chmod +x "+remoteTest)
if err != nil {
return
}
2018-11-17 19:37:04 +00:00
}
2018-12-10 02:32:13 +00:00
switch ka.Type {
case config.KernelModule:
2018-11-17 19:37:04 +00:00
// TODO Write insmod log to file or database
output, err := q.CopyAndInsmod(outFile)
if err != nil {
log.Println(output, err)
return
}
2018-12-10 02:35:43 +00:00
runOk = true
2018-11-17 19:37:04 +00:00
// TODO Write test results to file or database
output, err = testKernelModule(q, ka, remoteTest)
if err != nil {
log.Println(output, err)
return
}
2018-12-10 02:35:43 +00:00
testOk = true
2018-12-10 02:32:13 +00:00
case config.KernelExploit:
2018-11-17 19:37:04 +00:00
remoteExploit := fmt.Sprintf("/tmp/exploit_%d", rand.Int())
err = q.CopyFile("user", outFile, remoteExploit)
if err != nil {
return
}
// TODO Write test results to file or database
output, err = testKernelExploit(q, ka, remoteTest, remoteExploit)
if err != nil {
log.Println(output)
return
}
2018-12-10 02:35:43 +00:00
runOk = true // does not really used
testOk = true
2018-12-10 02:32:13 +00:00
default:
2018-12-10 02:56:22 +00:00
log.Println("Unsupported artifact type")
2018-11-17 19:37:04 +00:00
}
}
2019-08-12 23:21:38 +00:00
func shuffleKernels(a []config.KernelInfo) []config.KernelInfo {
// FisherYates shuffle
for i := len(a) - 1; i > 0; i-- {
j := rand.Intn(i + 1)
a[i], a[j] = a[j], a[i]
}
return a
}
2018-11-17 20:18:50 +00:00
func performCI(ka config.Artifact, kcfg config.KernelConfig, binaryPath,
2019-08-12 23:21:38 +00:00
testPath string, qemuTimeout, dockerTimeout time.Duration,
max int64) (err error) {
2018-11-17 19:37:04 +00:00
found := false
swg := sizedwaitgroup.New(runtime.NumCPU())
2019-08-12 23:21:38 +00:00
for _, kernel := range shuffleKernels(kcfg.Kernels) {
if max <= 0 {
break
}
2018-11-17 19:37:04 +00:00
var supported bool
supported, err = ka.Supported(kernel)
if err != nil {
return
}
if supported {
found = true
2019-08-12 23:21:38 +00:00
max -= 1
2018-11-17 19:37:04 +00:00
swg.Add()
go whatever(&swg, ka, kernel, binaryPath, testPath,
qemuTimeout, dockerTimeout)
}
}
swg.Wait()
if !found {
err = errors.New("No supported kernels found")
}
return
}
func exists(path string) bool {
if _, err := os.Stat(path); err != nil {
return false
}
return true
}
func kernelMask(kernel string) (km config.KernelMask, err error) {
parts := strings.Split(kernel, ":")
if len(parts) != 2 {
err = errors.New("Kernel is not 'distroType:regex'")
return
}
dt, err := config.NewDistroType(parts[0])
if err != nil {
return
}
km = config.KernelMask{DistroType: dt, ReleaseMask: parts[1]}
return
}
func genAllKernels() (sk []config.KernelMask, err error) {
for _, dType := range config.DistroTypeStrings {
var dt config.DistroType
dt, err = config.NewDistroType(dType)
if err != nil {
return
}
sk = append(sk, config.KernelMask{
DistroType: dt,
ReleaseMask: ".*",
})
}
return
}
2018-11-17 20:18:50 +00:00
func pewHandler(kcfg config.KernelConfig,
workPath, ovrrdKrnl, binary, test string, guess bool,
2019-08-12 23:21:38 +00:00
qemuTimeout, dockerTimeout time.Duration,
max int64) (err error) {
2018-11-17 19:37:04 +00:00
2018-11-17 20:18:50 +00:00
ka, err := config.ReadArtifactConfig(workPath + "/.out-of-tree.toml")
2018-11-17 19:37:04 +00:00
if err != nil {
return
}
if ka.SourcePath == "" {
ka.SourcePath = workPath
}
if ovrrdKrnl != "" {
var km config.KernelMask
km, err = kernelMask(ovrrdKrnl)
2018-11-17 19:37:04 +00:00
if err != nil {
return
}
2018-11-17 20:18:50 +00:00
ka.SupportedKernels = []config.KernelMask{km}
2018-11-17 19:37:04 +00:00
}
if guess {
ka.SupportedKernels, err = genAllKernels()
if err != nil {
return
2018-11-17 19:37:04 +00:00
}
}
2019-08-12 23:21:38 +00:00
err = performCI(ka, kcfg, binary, test, qemuTimeout, dockerTimeout, max)
2018-11-17 19:37:04 +00:00
if err != nil {
return
}
return
}