1
0

26 Commits

Author SHA1 Message Date
c1a3cb6ce5 Bump version 2019-12-25 14:40:13 +00:00
d58226c22c Do not check for (host) vsyscall support on the non-Linux systems
Fixes #19
2019-12-25 14:38:05 +00:00
9e1d71d1b2 Bump changelog version 2019-11-15 07:51:14 +00:00
9c70af4f6f Add flag for verbose output 2019-11-14 15:38:16 +00:00
7b8cf96b4a Kpti settings was not affected for regular runs 2019-11-14 15:37:34 +00:00
7b6e3a9ad6 Avoid slow mirrors 2019-09-05 18:27:23 +00:00
b117739c49 Add policykit-1 to rootfs 2019-08-31 12:45:55 +00:00
b28c47e64d Fix link to Travis-CI 2019-08-31 10:56:45 +00:00
4b14187dad GitHub Actions: convert case 2019-08-31 10:53:36 +00:00
950b1e5e83 Add Ubuntu build for GitHub Actions 2019-08-31 09:28:36 +00:00
bf90a10692 Add macOS build for GitHub Actions 2019-08-31 08:28:38 +00:00
3e7c564a5a Exclude host kernel generation for macOS 2019-08-31 08:05:43 +00:00
dc73413114 Set version 2019-08-30 17:40:01 +00:00
104e70f861 Implements global timeout 2019-08-30 16:34:14 +00:00
365c9d0e95 Implements reliablity threshold for exit code 2019-08-30 16:33:43 +00:00
5bad772125 Support custom docker commands
Resolves #17
2019-08-30 00:05:50 +00:00
f3b0c07af2 Implements parameter for setting up docker registry server 2019-08-29 22:49:59 +00:00
f3d67cc3c2 Update CHANGELOG 2019-08-29 22:29:23 +00:00
12b5bd2a99 Introduce global configuration file 2019-08-29 21:12:24 +00:00
b05c44ab9d Travis-CI: migrate to travis-ci.com 2019-08-28 03:18:37 +00:00
19535fc75c Do not produce error if existing 2019-08-23 10:46:37 +00:00
5e6a9dec93 Add rootfs generator for Ubuntu 14.04 2019-08-23 10:44:16 +00:00
0f89a868bd Add brief description 2019-08-21 08:08:35 +00:00
14b8010fee Fix spelling 2019-08-21 06:16:25 +00:00
7fd8614e3c Using sed to fix spelling was not a really good idea.
Revert "Fix spelling"

This reverts commit 3d958c1e10.
2019-08-21 06:14:58 +00:00
3d958c1e10 Fix spelling 2019-08-20 23:17:23 +00:00
19 changed files with 474 additions and 107 deletions

12
.github/workflows/macos.yml vendored Normal file
View File

@ -0,0 +1,12 @@
name: macOS
on: [push]
jobs:
build:
runs-on: macOS-latest
steps:
- uses: actions/checkout@v1
- name: Build
run: go build

23
.github/workflows/ubuntu.yml vendored Normal file
View File

@ -0,0 +1,23 @@
name: Ubuntu
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Build
run: go build
- name: Install dependencies for tests
run: |
sudo apt-get update
sudo apt-get install qemu
- name: Bootstrap
run: ./tools/qemu-debian-img/bootstrap.sh
- name: Test
run: go test -parallel 1 -v ./...

View File

@ -4,6 +4,66 @@
[Semantic Versioning](https://semver.org/spec/v2.0.0.html). [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.2.1] 2019-12-25
### Fixed
- macOS support.
## [1.2.0] 2019-11-15
### Added
- Flag for Verbose output. Right now only qemu status messages is
implemented.
### Fixed
- Kpti settings was not affected for regular runs.
## [1.1.2] 2019-09-05
### Added
- Added policykit-1 to rootfs for Ubuntu.
### Fixed
- Avoided slow mirrors with use of mirror://mirrors.ubuntu.com for
Ubuntu 16.04 and newer.
## [1.1.1] 2019-08-31
### Fixed
- macOS support.
## [1.1.0] 2019-08-30
### Added
- Global configuration file (~/.out-of-tree/out-of-tree.toml) allow to
set up default values for settings.
- rootfs generator for Ubuntu 14.04.
- Parameter for setting up docker registry server.
- Support for (distro-specific) custom docker commands that will be
executed before the base template.
- Parameter for setting up a reliability threshold for exit code.
- Parameter for setting up global timeout, after which no new tasks
will be started.
### Fixed
- Spelling in output.
- Now kernel generation will not fail if there are no directory
/lib/modules inside the container.
## [1.0.0] 2019-08-20 ## [1.0.0] 2019-08-20
### Added ### Added

View File

@ -1,5 +1,5 @@
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/aba4aad2046b4d1a9a99cf98e22c018b)](https://app.codacy.com/app/jollheef/out-of-tree?utm_source=github.com&utm_medium=referral&utm_content=jollheef/out-of-tree&utm_campaign=Badge_Grade_Dashboard) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/aba4aad2046b4d1a9a99cf98e22c018b)](https://app.codacy.com/app/jollheef/out-of-tree?utm_source=github.com&utm_medium=referral&utm_content=jollheef/out-of-tree&utm_campaign=Badge_Grade_Dashboard)
[![Build Status](https://travis-ci.org/jollheef/out-of-tree.svg?branch=master)](https://travis-ci.org/jollheef/out-of-tree) [![Build Status](https://travis-ci.com/jollheef/out-of-tree.svg?branch=master)](https://travis-ci.com/jollheef/out-of-tree)
[![Go Report Card](https://goreportcard.com/badge/code.dumpstack.io/tools/out-of-tree)](https://goreportcard.com/report/code.dumpstack.io/tools/out-of-tree) [![Go Report Card](https://goreportcard.com/badge/code.dumpstack.io/tools/out-of-tree)](https://goreportcard.com/report/code.dumpstack.io/tools/out-of-tree)
[![Documentation Status](https://readthedocs.org/projects/out-of-tree/badge/?version=latest)](https://out-of-tree.readthedocs.io/en/latest/?badge=latest) [![Documentation Status](https://readthedocs.org/projects/out-of-tree/badge/?version=latest)](https://out-of-tree.readthedocs.io/en/latest/?badge=latest)
[![Donate](https://img.shields.io/badge/donate-paypal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=R8W2UQPZ5X5JE&source=url) [![Donate](https://img.shields.io/badge/donate-paypal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=R8W2UQPZ5X5JE&source=url)
@ -9,6 +9,8 @@
out-of-tree kernel {module, exploit} development tool out-of-tree kernel {module, exploit} development tool
out-of-tree is for automating some routine actions for creating development environments for debugging kernel modules and exploits, generating reliability statistics for exploits, and also provides the ability to easily integrate into CI (Continuous Integration).
![Screenshot](https://cloudflare-ipfs.com/ipfs/Qmb88fgdDjbWkxz91sWsgmoZZNfVThnCtj37u3mF2s3T3T) ![Screenshot](https://cloudflare-ipfs.com/ipfs/Qmb88fgdDjbWkxz91sWsgmoZZNfVThnCtj37u3mF2s3T3T)
## Requirements ## Requirements

77
config/out-of-tree.go Normal file
View File

@ -0,0 +1,77 @@
// Copyright 2019 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 config
import (
"os/user"
"github.com/naoina/toml"
)
type DockerCommand struct {
DistroType DistroType
Command string
}
type OutOfTree struct {
Kernels string
UserKernels string
Database string
Qemu struct {
Timeout string
}
Docker struct {
Timeout string
Registry string
// Commands that will be executed before
// the base layer of Dockerfile
Commands []DockerCommand
}
}
func ReadOutOfTreeConf(path string) (c OutOfTree, err error) {
buf, err := readFileAll(path)
if err == nil {
err = toml.Unmarshal(buf, &c)
if err != nil {
return
}
} else {
// It's ok if there's no configuration
// then we'll just set default values
err = nil
}
usr, err := user.Current()
if err != nil {
return
}
if c.Kernels == "" {
c.Kernels = usr.HomeDir + "/.out-of-tree/kernels.toml"
}
if c.UserKernels == "" {
c.UserKernels = usr.HomeDir + "/.out-of-tree/kernels.user.toml"
}
if c.Database == "" {
c.Database = usr.HomeDir + "/.out-of-tree/db.sqlite"
}
if c.Qemu.Timeout == "" {
c.Qemu.Timeout = "1m"
}
if c.Docker.Timeout == "" {
c.Docker.Timeout = "1m"
}
return
}

View File

@ -161,7 +161,7 @@ func debugHandler(kcfg config.KernelConfig, workPath, kernRegex, gdb string,
q.Debug(gdb) q.Debug(gdb)
coloredGdbAddress := aurora.BgGreen(aurora.Black(gdb)) coloredGdbAddress := aurora.BgGreen(aurora.Black(gdb))
fmt.Printf("[*] gdb runned on %s\n", coloredGdbAddress) fmt.Printf("[*] gdb is listening on %s\n", coloredGdbAddress)
err = q.Start() err = q.Start()
if err != nil { if err != nil {

View File

@ -54,7 +54,7 @@ Overview
$ out-of-tree debug --kernel 'Ubuntu:4.15.0-58-generic' $ out-of-tree debug --kernel 'Ubuntu:4.15.0-58-generic'
[*] KASLR SMEP SMAP [*] KASLR SMEP SMAP
[*] gdb runned on tcp::1234 [*] gdb is listening on tcp::1234
[*] build result copied to /tmp/exploit [*] build result copied to /tmp/exploit
ssh -o StrictHostKeyChecking=no -p 29308 root@127.133.45.236 ssh -o StrictHostKeyChecking=no -p 29308 root@127.133.45.236

View File

@ -2,7 +2,7 @@
# - KERNEL: kernel headers path # - KERNEL: kernel headers path
# - TARGET: name of exploit binary that MUST be produced by makefile. # - TARGET: name of exploit binary that MUST be produced by makefile.
# - $(TARGET)_test: name of test binary that MUST be produced by makefile # - $(TARGET)_test: name of test binary that MUST be produced by makefile
# and it's will be runned on a LPE stage. TARGET_TEST MUST accept two argument: # and it's will be executed on a LPE stage. TARGET_TEST MUST accept two argument:
# - Path to exploit binary # - Path to exploit binary
# - File that MUST be created with exploit. It uses for test that exploit works # - File that MUST be created with exploit. It uses for test that exploit works
# correctly. # correctly.

125
kernel.go
View File

@ -15,11 +15,11 @@ import (
"os/exec" "os/exec"
"os/user" "os/user"
"regexp" "regexp"
"runtime"
"strings" "strings"
"time" "time"
"github.com/naoina/toml" "github.com/naoina/toml"
"github.com/zcalusic/sysinfo"
"code.dumpstack.io/tools/out-of-tree/config" "code.dumpstack.io/tools/out-of-tree/config"
) )
@ -100,6 +100,14 @@ func dockerImagePath(sk config.KernelMask) (path string, err error) {
} }
func vsyscallAvailable() (available bool, err error) { func vsyscallAvailable() (available bool, err error) {
if runtime.GOOS != "linux" {
// Docker for non-Linux systems is not using the host
// kernel but uses kernel inside a virtual machine, so
// it builds by the Docker team with vsyscall support.
available = true
return
}
buf, err := ioutil.ReadFile("/proc/self/maps") buf, err := ioutil.ReadFile("/proc/self/maps")
if err != nil { if err != nil {
return return
@ -109,7 +117,9 @@ func vsyscallAvailable() (available bool, err error) {
return return
} }
func generateBaseDockerImage(sk config.KernelMask) (err error) { func generateBaseDockerImage(registry string, commands []config.DockerCommand,
sk config.KernelMask) (err error) {
imagePath, err := dockerImagePath(sk) imagePath, err := dockerImagePath(sk)
if err != nil { if err != nil {
return return
@ -128,7 +138,12 @@ func generateBaseDockerImage(sk config.KernelMask) (err error) {
sk.DistroType.String(), sk.DistroRelease) sk.DistroType.String(), sk.DistroRelease)
os.MkdirAll(imagePath, os.ModePerm) os.MkdirAll(imagePath, os.ModePerm)
d += fmt.Sprintf("FROM %s:%s\n", d += "FROM "
if registry != "" {
d += registry + "/"
}
d += fmt.Sprintf("%s:%s\n",
strings.ToLower(sk.DistroType.String()), strings.ToLower(sk.DistroType.String()),
sk.DistroRelease, sk.DistroRelease,
) )
@ -138,16 +153,38 @@ func generateBaseDockerImage(sk config.KernelMask) (err error) {
return return
} }
for _, c := range commands {
switch c.DistroType {
case config.Ubuntu:
d += "RUN " + c.Command + "\n"
case config.CentOS:
d += "RUN " + c.Command + "\n"
case config.Debian:
d += "RUN " + c.Command + "\n"
default:
err = fmt.Errorf("%s not yet supported",
sk.DistroType.String())
return
}
}
switch sk.DistroType { switch sk.DistroType {
case config.Ubuntu: case config.Ubuntu:
d += "ENV DEBIAN_FRONTEND=noninteractive\n" d += "ENV DEBIAN_FRONTEND=noninteractive\n"
if sk.DistroRelease >= "16.04" {
from := "http://.*ubuntu/"
to := "mirror://mirrors.ubuntu.com/mirrors.txt"
file := "/etc/apt/sources.list"
s := fmt.Sprintf("sed -i 's;%s;%s;' %s", from, to, file)
d += "RUN " + s + "\n"
}
d += "RUN apt-get update\n" d += "RUN apt-get update\n"
d += "RUN apt-get install -y build-essential libelf-dev\n" d += "RUN apt-get install -y build-essential libelf-dev\n"
d += "RUN apt-get install -y wget git\n" d += "RUN apt-get install -y wget git\n"
if sk.DistroRelease >= "14.04" { if sk.DistroRelease >= "14.04" {
d += "RUN apt-get install -y libseccomp-dev\n" d += "RUN apt-get install -y libseccomp-dev\n"
} }
d += "RUN mkdir /lib/modules\n" d += "RUN mkdir -p /lib/modules\n"
case config.CentOS: case config.CentOS:
if sk.DistroRelease < "7" && !vsyscall { if sk.DistroRelease < "7" && !vsyscall {
log.Println("Old CentOS requires `vsyscall=emulate` " + log.Println("Old CentOS requires `vsyscall=emulate` " +
@ -397,66 +434,6 @@ func listDockerImages() (diis []dockerImageInfo, err error) {
return return
} }
func genHostKernels(download bool) (kcfg config.KernelConfig, err error) {
si := sysinfo.SysInfo{}
si.GetSysInfo()
distroType, err := config.NewDistroType(si.OS.Vendor)
if err != nil {
return
}
cmd := exec.Command("ls", "/lib/modules")
rawOutput, err := cmd.CombinedOutput()
if err != nil {
log.Println(string(rawOutput), err)
return
}
kernelsBase := "/boot/"
files, err := ioutil.ReadDir(kernelsBase)
if err != nil {
return
}
// only for compatibility, docker is not really used
dii := dockerImageInfo{
ContainerName: config.KernelMask{
DistroType: distroType,
DistroRelease: si.OS.Version,
}.DockerName(),
}
rootfs, err := genRootfsImage(dii, download)
if err != nil {
return
}
for _, k := range strings.Fields(string(rawOutput)) {
ki := config.KernelInfo{
DistroType: distroType,
DistroRelease: si.OS.Version,
KernelRelease: k,
KernelSource: "/lib/modules/" + k + "/build",
KernelPath: kernelsBase + genKernelPath(files, k),
InitrdPath: kernelsBase + genInitrdPath(files, k),
RootFS: rootfs,
}
vmlinux := "/usr/lib/debug/boot/vmlinux-" + k
log.Println("vmlinux", vmlinux)
if exists(vmlinux) {
ki.VmlinuxPath = vmlinux
}
kcfg.Kernels = append(kcfg.Kernels, ki)
}
return
}
func updateKernelsCfg(host, download bool) (err error) { func updateKernelsCfg(host, download bool) (err error) {
newkcfg := config.KernelConfig{} newkcfg := config.KernelConfig{}
@ -573,7 +550,10 @@ func shuffle(a []string) []string {
return a return a
} }
func generateKernels(km config.KernelMask, max int64, download bool) (err error) { func generateKernels(km config.KernelMask, registry string,
commands []config.DockerCommand, max int64,
download bool) (err error) {
log.Println("Generating for kernel mask", km) log.Println("Generating for kernel mask", km)
_, err = genRootfsImage(dockerImageInfo{ContainerName: km.DockerName()}, _, err = genRootfsImage(dockerImageInfo{ContainerName: km.DockerName()},
@ -582,7 +562,7 @@ func generateKernels(km config.KernelMask, max int64, download bool) (err error)
return return
} }
err = generateBaseDockerImage(km) err = generateBaseDockerImage(registry, commands, km)
if err != nil { if err != nil {
return return
} }
@ -632,7 +612,10 @@ func generateKernels(km config.KernelMask, max int64, download bool) (err error)
return return
} }
func kernelAutogenHandler(workPath string, max int64, host, download bool) (err error) { func kernelAutogenHandler(workPath, registry string,
commands []config.DockerCommand,
max int64, host, download bool) (err error) {
ka, err := config.ReadArtifactConfig(workPath + "/.out-of-tree.toml") ka, err := config.ReadArtifactConfig(workPath + "/.out-of-tree.toml")
if err != nil { if err != nil {
return return
@ -644,7 +627,7 @@ func kernelAutogenHandler(workPath string, max int64, host, download bool) (err
return return
} }
err = generateKernels(sk, max, download) err = generateKernels(sk, registry, commands, max, download)
if err != nil { if err != nil {
return return
} }
@ -695,7 +678,9 @@ func kernelDockerRegenHandler(host, download bool) (err error) {
return updateKernelsCfg(host, download) return updateKernelsCfg(host, download)
} }
func kernelGenallHandler(distro, version string, host, download bool) (err error) { func kernelGenallHandler(distro, version, registry string,
commands []config.DockerCommand, host, download bool) (err error) {
distroType, err := config.NewDistroType(distro) distroType, err := config.NewDistroType(distro)
if err != nil { if err != nil {
return return
@ -706,7 +691,7 @@ func kernelGenallHandler(distro, version string, host, download bool) (err error
DistroRelease: version, DistroRelease: version,
ReleaseMask: ".*", ReleaseMask: ".*",
} }
err = generateKernels(km, kernelsAll, download) err = generateKernels(km, registry, commands, kernelsAll, download)
if err != nil { if err != nil {
return return
} }

77
kernel_linux.go Normal file
View File

@ -0,0 +1,77 @@
// 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.
// +build linux
package main
import (
"io/ioutil"
"log"
"os/exec"
"strings"
"code.dumpstack.io/tools/out-of-tree/config"
"github.com/zcalusic/sysinfo"
)
func genHostKernels(download bool) (kcfg config.KernelConfig, err error) {
si := sysinfo.SysInfo{}
si.GetSysInfo()
distroType, err := config.NewDistroType(si.OS.Vendor)
if err != nil {
return
}
cmd := exec.Command("ls", "/lib/modules")
rawOutput, err := cmd.CombinedOutput()
if err != nil {
log.Println(string(rawOutput), err)
return
}
kernelsBase := "/boot/"
files, err := ioutil.ReadDir(kernelsBase)
if err != nil {
return
}
// only for compatibility, docker is not really used
dii := dockerImageInfo{
ContainerName: config.KernelMask{
DistroType: distroType,
DistroRelease: si.OS.Version,
}.DockerName(),
}
rootfs, err := genRootfsImage(dii, download)
if err != nil {
return
}
for _, k := range strings.Fields(string(rawOutput)) {
ki := config.KernelInfo{
DistroType: distroType,
DistroRelease: si.OS.Version,
KernelRelease: k,
KernelSource: "/lib/modules/" + k + "/build",
KernelPath: kernelsBase + genKernelPath(files, k),
InitrdPath: kernelsBase + genInitrdPath(files, k),
RootFS: rootfs,
}
vmlinux := "/usr/lib/debug/boot/vmlinux-" + k
log.Println("vmlinux", vmlinux)
if exists(vmlinux) {
ki.VmlinuxPath = vmlinux
}
kcfg.Kernels = append(kcfg.Kernels, ki)
}
return
}

18
kernel_macos.go Normal file
View File

@ -0,0 +1,18 @@
// 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.
// +build darwin
package main
import (
"errors"
"code.dumpstack.io/tools/out-of-tree/config"
)
func genHostKernels(download bool) (kcfg config.KernelConfig, err error) {
err = errors.New("generate host kernels for macOS is not supported")
return
}

55
main.go
View File

@ -84,7 +84,7 @@ func main() {
) )
app.Author("Mikhail Klementev <root@dumpstack.io>") app.Author("Mikhail Klementev <root@dumpstack.io>")
app.Version("1.0.1") app.Version("1.2.1")
pathFlag := app.Flag("path", "Path to work directory") pathFlag := app.Flag("path", "Path to work directory")
path := pathFlag.Default(".").ExistingDir() path := pathFlag.Default(".").ExistingDir()
@ -95,25 +95,36 @@ func main() {
} }
os.MkdirAll(usr.HomeDir+"/.out-of-tree", os.ModePerm) os.MkdirAll(usr.HomeDir+"/.out-of-tree", os.ModePerm)
defaultKcfgPath := usr.HomeDir + "/.out-of-tree/kernels.toml" confPath := usr.HomeDir + "/.out-of-tree/out-of-tree.toml"
conf, err := config.ReadOutOfTreeConf(confPath)
if err != nil {
return
}
kcfgPathFlag := app.Flag("kernels", "Path to main kernels config") kcfgPathFlag := app.Flag("kernels", "Path to main kernels config")
kcfgPath := kcfgPathFlag.Default(defaultKcfgPath).String() kcfgPath := kcfgPathFlag.Default(conf.Kernels).String()
defaultDbPath := usr.HomeDir + "/.out-of-tree/db.sqlite"
dbPathFlag := app.Flag("db", "Path to database") dbPathFlag := app.Flag("db", "Path to database")
dbPath := dbPathFlag.Default(defaultDbPath).String() dbPath := dbPathFlag.Default(conf.Database).String()
defaultUserKcfgPath := usr.HomeDir + "/.out-of-tree/kernels.user.toml"
userKcfgPathFlag := app.Flag("user-kernels", "User kernels config") userKcfgPathFlag := app.Flag("user-kernels", "User kernels config")
userKcfgPathEnv := userKcfgPathFlag.Envar("OUT_OF_TREE_KCFG") userKcfgPathEnv := userKcfgPathFlag.Envar("OUT_OF_TREE_KCFG")
userKcfgPath := userKcfgPathEnv.Default(defaultUserKcfgPath).String() userKcfgPath := userKcfgPathEnv.Default(conf.UserKernels).String()
timeoutFlag := app.Flag("timeout", "Timeout after tool will not spawn new tests")
timeout := timeoutFlag.Duration()
qemuTimeoutFlag := app.Flag("qemu-timeout", "Timeout for qemu") qemuTimeoutFlag := app.Flag("qemu-timeout", "Timeout for qemu")
qemuTimeout := qemuTimeoutFlag.Default("1m").Duration() qemuTimeout := qemuTimeoutFlag.Default(conf.Qemu.Timeout).Duration()
dockerTimeoutFlag := app.Flag("docker-timeout", "Timeout for docker") dockerTimeoutFlag := app.Flag("docker-timeout", "Timeout for docker")
dockerTimeout := dockerTimeoutFlag.Default("1m").Duration() dockerTimeout := dockerTimeoutFlag.Default(conf.Docker.Timeout).Duration()
dockerRegistryFlag := app.Flag("docker-registry", "Registry for docker")
dockerRegistry := dockerRegistryFlag.Default(conf.Docker.Registry).String()
thresholdFlag := app.Flag("threshold", "Reliablity threshold for exit code")
threshold := thresholdFlag.Default("1.00").Float64()
pewCommand := app.Command("pew", "Build, run and test module/exploit") pewCommand := app.Command("pew", "Build, run and test module/exploit")
@ -144,6 +155,9 @@ func main() {
pewTagFlag := pewCommand.Flag("tag", "Log tagging") pewTagFlag := pewCommand.Flag("tag", "Log tagging")
pewTag := pewTagFlag.String() pewTag := pewTagFlag.String()
pewVerboseFlag := pewCommand.Flag("verbose", "Show more information")
pewVerbose := pewVerboseFlag.Bool()
kernelCommand := app.Command("kernel", "Manipulate kernels") kernelCommand := app.Command("kernel", "Manipulate kernels")
kernelNoDownload := kernelCommand.Flag("no-download", kernelNoDownload := kernelCommand.Flag("no-download",
"Do not download qemu image while kernel generation").Bool() "Do not download qemu image while kernel generation").Bool()
@ -253,6 +267,10 @@ func main() {
log.Fatalln("Only one of disable/enable can be used at once") log.Fatalln("Only one of disable/enable can be used at once")
} }
if *yekpti && *nokpti {
log.Fatalln("Only one of disable/enable can be used at once")
}
kcfg, err := config.ReadKernelConfig(*kcfgPath) kcfg, err := config.ReadKernelConfig(*kcfgPath)
if err != nil { if err != nil {
log.Println(err) log.Println(err)
@ -279,20 +297,28 @@ func main() {
} }
defer db.Close() defer db.Close()
stop := time.Time{} // never stop
if *timeout != 0 {
stop = time.Now().Add(*timeout)
}
switch kingpin.MustParse(app.Parse(os.Args[1:])) { switch kingpin.MustParse(app.Parse(os.Args[1:])) {
case pewCommand.FullCommand(): case pewCommand.FullCommand():
err = pewHandler(kcfg, *path, *pewKernel, *pewBinary, err = pewHandler(kcfg, *path, *pewKernel, *pewBinary,
*pewTest, *pewGuess, *qemuTimeout, *dockerTimeout, *pewTest, *pewGuess, stop, *qemuTimeout, *dockerTimeout,
*pewMax, *pewRuns, *pewDist, *pewTag, *pewThreads, db) *pewMax, *pewRuns, *pewDist, *pewTag, *pewThreads,
db, *pewVerbose)
case kernelListCommand.FullCommand(): case kernelListCommand.FullCommand():
err = kernelListHandler(kcfg) err = kernelListHandler(kcfg)
case kernelAutogenCommand.FullCommand(): case kernelAutogenCommand.FullCommand():
err = kernelAutogenHandler(*path, *kernelAutogenMax, err = kernelAutogenHandler(*path, *dockerRegistry,
conf.Docker.Commands, *kernelAutogenMax,
*kernelUseHost, !*kernelNoDownload) *kernelUseHost, !*kernelNoDownload)
case kernelDockerRegenCommand.FullCommand(): case kernelDockerRegenCommand.FullCommand():
err = kernelDockerRegenHandler(*kernelUseHost, !*kernelNoDownload) err = kernelDockerRegenHandler(*kernelUseHost, !*kernelNoDownload)
case kernelGenallCommand.FullCommand(): case kernelGenallCommand.FullCommand():
err = kernelGenallHandler(*distro, *version, err = kernelGenallHandler(*distro, *version,
*dockerRegistry, conf.Docker.Commands,
*kernelUseHost, !*kernelNoDownload) *kernelUseHost, !*kernelNoDownload)
case genModuleCommand.FullCommand(): case genModuleCommand.FullCommand():
err = genConfig(config.KernelModule) err = genConfig(config.KernelModule)
@ -316,7 +342,8 @@ func main() {
case logMarkdownCommand.FullCommand(): case logMarkdownCommand.FullCommand():
err = logMarkdownHandler(db, *path, *logMarkdownTag) err = logMarkdownHandler(db, *path, *logMarkdownTag)
case packCommand.FullCommand(): case packCommand.FullCommand():
err = packHandler(db, *path, kcfg, *packAutogen, err = packHandler(db, *path, *dockerRegistry, stop,
conf.Docker.Commands, kcfg, *packAutogen,
!*packNoDownload, *packExploitRuns, *packKernelRuns) !*packNoDownload, *packExploitRuns, *packKernelRuns)
} }
@ -324,7 +351,7 @@ func main() {
log.Fatalln(err) log.Fatalln(err)
} }
if somethingFailed { if successRate(state) < *threshold {
os.Exit(1) os.Exit(1)
} }
} }

11
pack.go
View File

@ -15,7 +15,8 @@ import (
"code.dumpstack.io/tools/out-of-tree/config" "code.dumpstack.io/tools/out-of-tree/config"
) )
func packHandler(db *sql.DB, path string, kcfg config.KernelConfig, func packHandler(db *sql.DB, path, registry string, stop time.Time,
commands []config.DockerCommand, kcfg config.KernelConfig,
autogen, download bool, exploitRuns, kernelRuns int64) (err error) { autogen, download bool, exploitRuns, kernelRuns int64) (err error) {
dockerTimeout := time.Minute dockerTimeout := time.Minute
@ -39,7 +40,8 @@ func packHandler(db *sql.DB, path string, kcfg config.KernelConfig,
if autogen { if autogen {
var perRegex int64 = 1 var perRegex int64 = 1
err = kernelAutogenHandler(workPath, perRegex, false, download) err = kernelAutogenHandler(workPath, registry,
commands, perRegex, false, download)
if err != nil { if err != nil {
return return
} }
@ -48,8 +50,9 @@ func packHandler(db *sql.DB, path string, kcfg config.KernelConfig,
log.Println(f.Name()) log.Println(f.Name())
pewHandler(kcfg, workPath, "", "", "", false, pewHandler(kcfg, workPath, "", "", "", false,
dockerTimeout, qemuTimeout, stop, dockerTimeout, qemuTimeout,
kernelRuns, exploitRuns, pathDevNull, tag, threads, db) kernelRuns, exploitRuns, pathDevNull,
tag, threads, db, false)
} }
return return

50
pew.go
View File

@ -26,7 +26,17 @@ import (
"code.dumpstack.io/tools/out-of-tree/qemu" "code.dumpstack.io/tools/out-of-tree/qemu"
) )
var somethingFailed = false type runstate struct {
Overall, Success float64
}
var (
state runstate
)
func successRate(state runstate) float64 {
return state.Success / state.Overall
}
const pathDevNull = "/dev/null" const pathDevNull = "/dev/null"
@ -134,11 +144,12 @@ func testKernelExploit(q *qemu.System, ka config.Artifact,
} }
func genOkFail(name string, ok bool) (aurv aurora.Value) { func genOkFail(name string, ok bool) (aurv aurora.Value) {
state.Overall += 1
if ok { if ok {
state.Success += 1
s := " " + name + " SUCCESS " s := " " + name + " SUCCESS "
aurv = aurora.BgGreen(aurora.Black(s)) aurv = aurora.BgGreen(aurora.Black(s))
} else { } else {
somethingFailed = true
s := " " + name + " FAILURE " s := " " + name + " FAILURE "
aurv = aurora.BgRed(aurora.Gray(aurora.Bold(s))) aurv = aurora.BgRed(aurora.Gray(aurora.Bold(s)))
} }
@ -292,7 +303,7 @@ func copyTest(q *qemu.System, testPath string, ka config.Artifact) (
func whatever(swg *sizedwaitgroup.SizedWaitGroup, ka config.Artifact, func whatever(swg *sizedwaitgroup.SizedWaitGroup, ka config.Artifact,
ki config.KernelInfo, binaryPath, testPath string, ki config.KernelInfo, binaryPath, testPath string,
qemuTimeout, dockerTimeout time.Duration, dist, tag string, qemuTimeout, dockerTimeout time.Duration, dist, tag string,
db *sql.DB) { db *sql.DB, verbose bool) {
defer swg.Done() defer swg.Done()
@ -317,6 +328,7 @@ func whatever(swg *sizedwaitgroup.SizedWaitGroup, ka config.Artifact,
q.SetKASLR(!ka.Mitigations.DisableKaslr) q.SetKASLR(!ka.Mitigations.DisableKaslr)
q.SetSMEP(!ka.Mitigations.DisableSmep) q.SetSMEP(!ka.Mitigations.DisableSmep)
q.SetSMAP(!ka.Mitigations.DisableSmap) q.SetSMAP(!ka.Mitigations.DisableSmap)
q.SetKPTI(!ka.Mitigations.DisableKpti)
err = q.Start() err = q.Start()
if err != nil { if err != nil {
@ -325,6 +337,18 @@ func whatever(swg *sizedwaitgroup.SizedWaitGroup, ka config.Artifact,
} }
defer q.Stop() defer q.Stop()
if verbose {
go func() {
for !q.Died {
time.Sleep(time.Minute)
log.Println(ka.Name, ki.DistroType,
ki.DistroRelease, ki.KernelRelease,
"still alive")
}
}()
}
usr, err := user.Current() usr, err := user.Current()
if err != nil { if err != nil {
return return
@ -380,9 +404,10 @@ func shuffleKernels(a []config.KernelInfo) []config.KernelInfo {
} }
func performCI(ka config.Artifact, kcfg config.KernelConfig, binaryPath, func performCI(ka config.Artifact, kcfg config.KernelConfig, binaryPath,
testPath string, qemuTimeout, dockerTimeout time.Duration, testPath string, stop time.Time,
qemuTimeout, dockerTimeout time.Duration,
max, runs int64, dist, tag string, threads int, max, runs int64, dist, tag string, threads int,
db *sql.DB) (err error) { db *sql.DB, verbose bool) (err error) {
found := false found := false
@ -402,10 +427,13 @@ func performCI(ka config.Artifact, kcfg config.KernelConfig, binaryPath,
found = true found = true
max-- max--
for i := int64(0); i < runs; i++ { for i := int64(0); i < runs; i++ {
if !stop.IsZero() && time.Now().After(stop) {
break
}
swg.Add() swg.Add()
go whatever(&swg, ka, kernel, binaryPath, go whatever(&swg, ka, kernel, binaryPath,
testPath, qemuTimeout, dockerTimeout, testPath, qemuTimeout, dockerTimeout,
dist, tag, db) dist, tag, db, verbose)
} }
} }
} }
@ -457,11 +485,12 @@ func genAllKernels() (sk []config.KernelMask, err error) {
return return
} }
// TODO: Now too many parameters, move all of them to some structure
func pewHandler(kcfg config.KernelConfig, func pewHandler(kcfg config.KernelConfig,
workPath, ovrrdKrnl, binary, test string, guess bool, workPath, ovrrdKrnl, binary, test string, guess bool,
qemuTimeout, dockerTimeout time.Duration, stop time.Time, qemuTimeout, dockerTimeout time.Duration,
max, runs int64, dist, tag string, threads int, max, runs int64, dist, tag string, threads int,
db *sql.DB) (err error) { db *sql.DB, verbose bool) (err error) {
ka, err := config.ReadArtifactConfig(workPath + "/.out-of-tree.toml") ka, err := config.ReadArtifactConfig(workPath + "/.out-of-tree.toml")
if err != nil { if err != nil {
@ -489,8 +518,9 @@ func pewHandler(kcfg config.KernelConfig,
} }
} }
err = performCI(ka, kcfg, binary, test, qemuTimeout, dockerTimeout, err = performCI(ka, kcfg, binary, test,
max, runs, dist, tag, threads, db) stop, qemuTimeout, dockerTimeout,
max, runs, dist, tag, threads, db, verbose)
if err != nil { if err != nil {
return return
} }

View File

@ -60,7 +60,7 @@ type Kernel struct {
InitrdPath string InitrdPath string
} }
// System describe qemu parameters and runned process // System describe qemu parameters and executed process
type System struct { type System struct {
arch arch arch arch
kernel Kernel kernel Kernel
@ -86,7 +86,7 @@ type System struct {
Died bool Died bool
sshAddrPort string sshAddrPort string
// accessible while qemu is runned // accessible while qemu is running
cmd *exec.Cmd cmd *exec.Cmd
pipe struct { pipe struct {
stdin io.WriteCloser stdin io.WriteCloser

View File

@ -47,7 +47,7 @@ ENV IMAGE=/shared/out_of_tree_centos_7.img
RUN mkdir $IMAGEDIR RUN mkdir $IMAGEDIR
# Must be runned with --privileged because of /dev/loop # Must be executed with --privileged because of /dev/loop
CMD qemu-img create $IMAGE 2G && \ CMD qemu-img create $IMAGE 2G && \
mkfs.ext4 -F $IMAGE && \ mkfs.ext4 -F $IMAGE && \
mount -o loop $IMAGE $IMAGEDIR && \ mount -o loop $IMAGE $IMAGEDIR && \

View File

@ -0,0 +1,35 @@
# 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.
#
# Usage:
#
# $ docker build -t gen-ubuntu1404-image .
# $ docker run --privileged -v $(pwd):/shared -t gen-ubuntu1404-image
#
# ubuntu1404.img will be created in current directory. You can change $(pwd) to
# different directory to use different destination for image.
#
FROM ubuntu:14.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update
RUN apt-get install -y debootstrap qemu
ENV TMPDIR=/tmp/ubuntu
ENV IMAGEDIR=/tmp/image
ENV IMAGE=/shared/out_of_tree_ubuntu_14__04.img
ENV REPOSITORY=http://archive.ubuntu.com/ubuntu
ENV RELEASE=trusty
RUN mkdir $IMAGEDIR
# Must be executed with --privileged because of /dev/loop
CMD debootstrap --include=openssh-server,policykit-1 \
$RELEASE $TMPDIR $REPOSITORY && \
/shared/setup.sh $TMPDIR && \
qemu-img create $IMAGE 2G && \
mkfs.ext4 -F $IMAGE && \
mount -o loop $IMAGE $IMAGEDIR && \
cp -a $TMPDIR/* $IMAGEDIR/ && \
umount $IMAGEDIR

View File

@ -0,0 +1,17 @@
#!/bin/sh -eux
# 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.
TMPDIR=$1
chroot $TMPDIR /bin/sh -c 'useradd -m user'
sed -i 's/root:\*:/root::/' $TMPDIR/etc/shadow
sed -i 's/user:!!:/user::/' $TMPDIR/etc/shadow
echo auth sufficient pam_permit.so > $TMPDIR/etc/pam.d/sshd
sed -i '/PermitEmptyPasswords/d' $TMPDIR/etc/ssh/sshd_config
echo PermitEmptyPasswords yes >> $TMPDIR/etc/ssh/sshd_config
sed -i '/PermitRootLogin/d' $TMPDIR/etc/ssh/sshd_config
echo PermitRootLogin yes >> $TMPDIR/etc/ssh/sshd_config
echo '#!/bin/sh' > $TMPDIR/etc/rc.local
echo 'dhclient eth0' >> $TMPDIR/etc/rc.local
chmod +x $TMPDIR/etc/rc.local

View File

@ -25,8 +25,9 @@ ENV RELEASE=bionic
RUN mkdir $IMAGEDIR RUN mkdir $IMAGEDIR
# Must be runned with --privileged because of /dev/loop # Must be executed with --privileged because of /dev/loop
CMD debootstrap --include=openssh-server $RELEASE $TMPDIR $REPOSITORY && \ CMD debootstrap --include=openssh-server,policykit-1 \
$RELEASE $TMPDIR $REPOSITORY && \
/shared/setup.sh $TMPDIR && \ /shared/setup.sh $TMPDIR && \
qemu-img create $IMAGE 2G && \ qemu-img create $IMAGE 2G && \
mkfs.ext4 -F $IMAGE && \ mkfs.ext4 -F $IMAGE && \