1
0

Refactor command line interface

This commit is contained in:
dump_stack() 2023-01-31 07:13:33 +00:00
parent 935266c850
commit 2f52f6db6d
Signed by: dump_stack
GPG Key ID: BE44DA8C062D87DC
8 changed files with 406 additions and 523 deletions

View File

@ -19,6 +19,33 @@ import (
"code.dumpstack.io/tools/out-of-tree/qemu" "code.dumpstack.io/tools/out-of-tree/qemu"
) )
type DebugCmd struct {
Kernel string `help:"regexp (first match)" required:""`
Gdb string `help:"gdb listen address" default:"tcp::1234"`
Kaslr bool `help:"Enable KASLR"`
Smep bool `help:"Enable SMEP"`
Smap bool `help:"Enable SMAP"`
Kpti bool `help:"Enable KPTI"`
NoKaslr bool `help:"Disable KASLR"`
NoSmep bool `help:"Disable SMEP"`
NoSmap bool `help:"Disable SMAP"`
NoKpti bool `help:"Disable KPTI"`
}
func (cmd *DebugCmd) Run(g *Globals) (err error) {
kcfg, err := config.ReadKernelConfig(g.Config.Kernels)
if err != nil {
log.Println(err)
}
return debugHandler(kcfg, g.WorkDir, cmd.Kernel, cmd.Gdb,
g.Config.Docker.Timeout.Duration,
cmd.Kaslr, cmd.Smep, cmd.Smap, cmd.Kpti,
cmd.NoKaslr, cmd.NoSmep, cmd.NoSmap, cmd.NoKpti)
}
func firstSupported(kcfg config.KernelConfig, ka config.Artifact, func firstSupported(kcfg config.KernelConfig, ka config.Artifact,
kernel string) (ki config.KernelInfo, err error) { kernel string) (ki config.KernelInfo, err error) {
@ -202,8 +229,8 @@ func debugHandler(kcfg config.KernelConfig, workPath, kernRegex, gdb string,
} }
// Copy all test files to the remote machine // Copy all test files to the remote machine
for _,f := range ka.TestFiles { for _, f := range ka.TestFiles {
err = q.CopyFile(f.User,f.Local,f.Remote) err = q.CopyFile(f.User, f.Local, f.Remote)
if err != nil { if err != nil {
log.Println("error copy err:", err, f.Local, f.Remote) log.Println("error copy err:", err, f.Local, f.Remote)
return return

14
gen.go
View File

@ -12,6 +12,20 @@ import (
"code.dumpstack.io/tools/out-of-tree/config" "code.dumpstack.io/tools/out-of-tree/config"
) )
type GenCmd struct {
Type string `enum:"module,exploit" required:"" help:"module/exploit"`
}
func (cmd *GenCmd) Run(g *Globals) (err error) {
switch cmd.Type {
case "module":
err = genConfig(config.KernelModule)
case "exploit":
err = genConfig(config.KernelExploit)
}
return
}
func genConfig(at config.ArtifactType) (err error) { func genConfig(at config.ArtifactType) (err error) {
a := config.Artifact{ a := config.Artifact{
Name: "Put name here", Name: "Put name here",

206
kernel.go
View File

@ -24,18 +24,133 @@ import (
"code.dumpstack.io/tools/out-of-tree/config" "code.dumpstack.io/tools/out-of-tree/config"
) )
const kernelsAll int64 = math.MaxInt64 type KernelCmd struct {
NoDownload bool `help:"do not download qemu image while kernel generation"`
UseHost bool `help:"also use host kernels"`
List KernelListCmd `cmd:"" help:"list kernels"`
Autogen KernelAutogenCmd `cmd:"" help:"generate kernels based on the current config"`
DockerRegen KernelDockerRegenCmd `cmd:"" help:"regenerate kernels config from out_of_tree_* docker images"`
Genall KernelGenallCmd `cmd:"" help:"generate all kernels for distro"`
}
type KernelListCmd struct{}
func (cmd *KernelListCmd) Run(g *Globals) (err error) {
kcfg, err := config.ReadKernelConfig(g.Config.Kernels)
if err != nil {
return
}
func kernelListHandler(kcfg config.KernelConfig) (err error) {
if len(kcfg.Kernels) == 0 { if len(kcfg.Kernels) == 0 {
return errors.New("No kernels found") return errors.New("No kernels found")
} }
for _, k := range kcfg.Kernels { for _, k := range kcfg.Kernels {
fmt.Println(k.DistroType, k.DistroRelease, k.KernelRelease) fmt.Println(k.DistroType, k.DistroRelease, k.KernelRelease)
} }
return return
} }
type KernelAutogenCmd struct {
Max int64 `help:"download random kernels from set defined by regex in release_mask, but no more than X for each of release_mask" default:"100500"`
}
func kernelAutogenHandler(workPath, registry string,
commands []config.DockerCommand,
max int64, host, download bool) (err error) {
return errors.New("MEH")
}
func (cmd *KernelAutogenCmd) Run(kernelCmd *KernelCmd, g *Globals) (err error) {
ka, err := config.ReadArtifactConfig(g.WorkDir + "/.out-of-tree.toml")
if err != nil {
return
}
for _, sk := range ka.SupportedKernels {
if sk.DistroRelease == "" {
err = errors.New("Please set distro_release")
return
}
err = generateKernels(sk, g.Config.Docker.Registry, g.Config.Docker.Commands, cmd.Max, !kernelCmd.NoDownload)
if err != nil {
return
}
}
return updateKernelsCfg(kernelCmd.UseHost, !kernelCmd.NoDownload)
}
type KernelDockerRegenCmd struct{}
func (cmd *KernelDockerRegenCmd) Run(kernelCmd *KernelCmd, g *Globals) (err error) {
dockerImages, err := listDockerImages()
if err != nil {
return
}
for _, d := range dockerImages {
var imagePath string
imagePath, err = dockerImagePath(config.KernelMask{
DistroType: d.DistroType,
DistroRelease: d.DistroRelease,
})
if err != nil {
return
}
cmd := exec.Command("docker", "build", "-t",
d.ContainerName, imagePath)
var rawOutput []byte
rawOutput, err = cmd.CombinedOutput()
if err != nil {
log.Println("docker build:", string(rawOutput))
return
}
err = kickImage(d.ContainerName)
if err != nil {
log.Println("kick image", d.ContainerName, ":", err)
continue
}
err = copyKernels(d.ContainerName)
if err != nil {
log.Println("copy kernels", d.ContainerName, ":", err)
continue
}
}
return updateKernelsCfg(kernelCmd.UseHost, !kernelCmd.NoDownload)
}
type KernelGenallCmd struct {
Distro string `required:"" help:"distribution"`
Ver string `required:"" help:"distro version"`
}
func (cmd *KernelGenallCmd) Run(kernelCmd *KernelCmd, g *Globals) (err error) {
distroType, err := config.NewDistroType(cmd.Distro)
if err != nil {
return
}
km := config.KernelMask{
DistroType: distroType,
DistroRelease: cmd.Ver,
ReleaseMask: ".*",
}
err = generateKernels(km, g.Config.Docker.Registry, g.Config.Docker.Commands, math.MaxInt64, !kernelCmd.NoDownload)
if err != nil {
return
}
return updateKernelsCfg(kernelCmd.UseHost, !kernelCmd.NoDownload)
}
func matchDebianHeadersPkg(container, mask string, generic bool) ( func matchDebianHeadersPkg(container, mask string, generic bool) (
pkgs []string, err error) { pkgs []string, err error) {
@ -637,90 +752,3 @@ func generateKernels(km config.KernelMask, registry string,
} }
return return
} }
func kernelAutogenHandler(workPath, registry string,
commands []config.DockerCommand,
max int64, host, download bool) (err error) {
ka, err := config.ReadArtifactConfig(workPath + "/.out-of-tree.toml")
if err != nil {
return
}
for _, sk := range ka.SupportedKernels {
if sk.DistroRelease == "" {
err = errors.New("Please set distro_release")
return
}
err = generateKernels(sk, registry, commands, max, download)
if err != nil {
return
}
}
err = updateKernelsCfg(host, download)
return
}
func kernelDockerRegenHandler(host, download bool) (err error) {
dockerImages, err := listDockerImages()
if err != nil {
return
}
for _, d := range dockerImages {
var imagePath string
imagePath, err = dockerImagePath(config.KernelMask{
DistroType: d.DistroType,
DistroRelease: d.DistroRelease,
})
if err != nil {
return
}
cmd := exec.Command("docker", "build", "-t",
d.ContainerName, imagePath)
var rawOutput []byte
rawOutput, err = cmd.CombinedOutput()
if err != nil {
log.Println("docker build:", string(rawOutput))
return
}
err = kickImage(d.ContainerName)
if err != nil {
log.Println("kick image", d.ContainerName, ":", err)
continue
}
err = copyKernels(d.ContainerName)
if err != nil {
log.Println("copy kernels", d.ContainerName, ":", err)
continue
}
}
return updateKernelsCfg(host, download)
}
func kernelGenallHandler(distro, version, registry string,
commands []config.DockerCommand, host, download bool) (err error) {
distroType, err := config.NewDistroType(distro)
if err != nil {
return
}
km := config.KernelMask{
DistroType: distroType,
DistroRelease: version,
ReleaseMask: ".*",
}
err = generateKernels(km, registry, commands, kernelsAll, download)
if err != nil {
return
}
return updateKernelsCfg(host, download)
}

217
log.go
View File

@ -1,4 +1,4 @@
// Copyright 2019 Mikhail Klementev. All rights reserved. // Copyright 2023 Mikhail Klementev. All rights reserved.
// Use of this source code is governed by a AGPLv3 license // Use of this source code is governed by a AGPLv3 license
// (or later) that can be found in the LICENSE file. // (or later) that can be found in the LICENSE file.
@ -18,56 +18,41 @@ import (
"code.dumpstack.io/tools/out-of-tree/config" "code.dumpstack.io/tools/out-of-tree/config"
) )
func logLogEntry(l logEntry) { type LogCmd struct {
distroInfo := fmt.Sprintf("%s-%s {%s}", l.DistroType, Query LogQueryCmd `cmd:"" help:"query logs"`
l.DistroRelease, l.KernelRelease) Dump LogDumpCmd `cmd:"" help:"show all info for log entry with ID"`
Json LogJsonCmd `cmd:"" help:"generate json statistics"`
artifactInfo := fmt.Sprintf("{[%s] %s}", l.Type, l.Name) Makrdown LogMarkdownCmd `cmd:"" help:"generate markdown statistics"`
colored := ""
if l.Type == config.KernelExploit {
colored = aurora.Sprintf("[%4d %4s] [%s] %40s %40s: %s %s",
l.ID, l.Tag, l.Timestamp, artifactInfo, distroInfo,
genOkFail("BUILD", l.Build.Ok),
genOkFail("LPE", l.Test.Ok))
} else {
colored = aurora.Sprintf("[%4d %4s] [%s] %40s %40s: %s %s %s",
l.ID, l.Tag, l.Timestamp, artifactInfo, distroInfo,
genOkFail("BUILD", l.Build.Ok),
genOkFail("INSMOD", l.Run.Ok),
genOkFail("TEST", l.Test.Ok))
}
additional := ""
if l.KernelPanic {
additional = "(panic)"
} else if l.KilledByTimeout {
additional = "(timeout)"
}
if additional != "" {
fmt.Println(colored, additional)
} else {
fmt.Println(colored)
}
} }
func logHandler(db *sql.DB, path, tag string, num int, rate bool) (err error) { type LogQueryCmd struct {
Num int `help:"how much lines" default:"50"`
Rate bool `help:"show artifact success rate"`
Tag string `help:"filter tag"`
}
func (cmd *LogQueryCmd) Run(g *Globals) (err error) {
db, err := openDatabase(g.Config.Database)
if err != nil {
log.Fatalln(err)
}
defer db.Close()
var les []logEntry var les []logEntry
ka, kaErr := config.ReadArtifactConfig(path + "/.out-of-tree.toml") ka, kaErr := config.ReadArtifactConfig(g.WorkDir + "/.out-of-tree.toml")
if kaErr == nil { if kaErr == nil {
log.Println(".out-of-tree.toml found, filter by artifact name") log.Println(".out-of-tree.toml found, filter by artifact name")
les, err = getAllArtifactLogs(db, tag, num, ka) les, err = getAllArtifactLogs(db, cmd.Tag, cmd.Num, ka)
} else { } else {
les, err = getAllLogs(db, tag, num) les, err = getAllLogs(db, cmd.Tag, cmd.Num)
} }
if err != nil { if err != nil {
return return
} }
s := "\nS" s := "\nS"
if rate { if cmd.Rate {
if kaErr != nil { if kaErr != nil {
err = kaErr err = kaErr
return return
@ -75,7 +60,7 @@ func logHandler(db *sql.DB, path, tag string, num int, rate bool) (err error) {
s = fmt.Sprintf("{[%s] %s} Overall s", ka.Type, ka.Name) s = fmt.Sprintf("{[%s] %s} Overall s", ka.Type, ka.Name)
les, err = getAllArtifactLogs(db, tag, math.MaxInt64, ka) les, err = getAllArtifactLogs(db, cmd.Tag, math.MaxInt64, ka)
if err != nil { if err != nil {
return return
} }
@ -99,10 +84,20 @@ func logHandler(db *sql.DB, path, tag string, num int, rate bool) (err error) {
return return
} }
func logDumpHandler(db *sql.DB, id int) (err error) { type LogDumpCmd struct {
ID int `help:"id" default:"-1"`
}
func (cmd *LogDumpCmd) Run(g *Globals) (err error) {
db, err := openDatabase(g.Config.Database)
if err != nil {
log.Fatalln(err)
}
defer db.Close()
var l logEntry var l logEntry
if id > 0 { if cmd.ID > 0 {
l, err = getLogByID(db, id) l, err = getLogByID(db, cmd.ID)
} else { } else {
l, err = getLastLog(db) l, err = getLastLog(db)
} }
@ -150,6 +145,102 @@ func logDumpHandler(db *sql.DB, id int) (err error) {
return return
} }
type LogJsonCmd struct {
Tag string `required:"" help:"filter tag"`
}
func (cmd *LogJsonCmd) Run(g *Globals) (err error) {
db, err := openDatabase(g.Config.Database)
if err != nil {
log.Fatalln(err)
}
defer db.Close()
distros, err := getStats(db, g.WorkDir, cmd.Tag)
if err != nil {
return
}
bytes, err := json.Marshal(&distros)
if err != nil {
return
}
fmt.Println(string(bytes))
return
}
type LogMarkdownCmd struct {
Tag string `required:"" help:"filter tag"`
}
func (cmd *LogMarkdownCmd) Run(g *Globals) (err error) {
db, err := openDatabase(g.Config.Database)
if err != nil {
log.Fatalln(err)
}
defer db.Close()
distros, err := getStats(db, g.WorkDir, cmd.Tag)
if err != nil {
return
}
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Distro", "Release", "Kernel", "Reliability"})
table.SetBorders(tablewriter.Border{
Left: true, Top: false, Right: true, Bottom: false})
table.SetCenterSeparator("|")
for distro, releases := range distros {
for release, kernels := range releases {
for kernel, stats := range kernels {
all := float64(stats.All)
ok := float64(stats.TestOK)
r := fmt.Sprintf("%6.02f%%", (ok/all)*100)
table.Append([]string{distro, release, kernel, r})
}
}
}
table.Render()
return
}
func logLogEntry(l logEntry) {
distroInfo := fmt.Sprintf("%s-%s {%s}", l.DistroType,
l.DistroRelease, l.KernelRelease)
artifactInfo := fmt.Sprintf("{[%s] %s}", l.Type, l.Name)
colored := ""
if l.Type == config.KernelExploit {
colored = aurora.Sprintf("[%4d %4s] [%s] %40s %40s: %s %s",
l.ID, l.Tag, l.Timestamp, artifactInfo, distroInfo,
genOkFail("BUILD", l.Build.Ok),
genOkFail("LPE", l.Test.Ok))
} else {
colored = aurora.Sprintf("[%4d %4s] [%s] %40s %40s: %s %s %s",
l.ID, l.Tag, l.Timestamp, artifactInfo, distroInfo,
genOkFail("BUILD", l.Build.Ok),
genOkFail("INSMOD", l.Run.Ok),
genOkFail("TEST", l.Test.Ok))
}
additional := ""
if l.KernelPanic {
additional = "(panic)"
} else if l.KilledByTimeout {
additional = "(timeout)"
}
if additional != "" {
fmt.Println(colored, additional)
} else {
fmt.Println(colored)
}
}
type runstat struct { type runstat struct {
All, BuildOK, RunOK, TestOK, Timeout, Panic int All, BuildOK, RunOK, TestOK, Timeout, Panic int
} }
@ -206,45 +297,3 @@ func getStats(db *sql.DB, path, tag string) (
return return
} }
func logJSONHandler(db *sql.DB, path, tag string) (err error) {
distros, err := getStats(db, path, tag)
if err != nil {
return
}
bytes, err := json.Marshal(&distros)
if err != nil {
return
}
fmt.Println(string(bytes))
return
}
func logMarkdownHandler(db *sql.DB, path, tag string) (err error) {
distros, err := getStats(db, path, tag)
if err != nil {
return
}
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Distro", "Release", "Kernel", "Reliability"})
table.SetBorders(tablewriter.Border{
Left: true, Top: false, Right: true, Bottom: false})
table.SetCenterSeparator("|")
for distro, releases := range distros {
for release, kernels := range releases {
for kernel, stats := range kernels {
all := float64(stats.All)
ok := float64(stats.TestOK)
r := fmt.Sprintf("%6.02f%%", (ok/all)*100)
table.Append([]string{distro, release, kernel, r})
}
}
}
table.Render()
return
}

363
main.go
View File

@ -1,4 +1,4 @@
// Copyright 2018 Mikhail Klementev. All rights reserved. // Copyright 2023 Mikhail Klementev. All rights reserved.
// Use of this source code is governed by a AGPLv3 license // Use of this source code is governed by a AGPLv3 license
// (or later) that can be found in the LICENSE file. // (or later) that can be found in the LICENSE file.
@ -8,69 +8,40 @@ import (
"fmt" "fmt"
"log" "log"
"math/rand" "math/rand"
"os"
"os/exec"
"os/user"
"runtime"
"sort"
"strconv"
"time" "time"
kingpin "gopkg.in/alecthomas/kingpin.v2" "github.com/alecthomas/kong"
"code.dumpstack.io/tools/out-of-tree/config" "code.dumpstack.io/tools/out-of-tree/config"
) )
func findFallback(kcfg config.KernelConfig, ki config.KernelInfo) (rootfs string) { type Globals struct {
for _, k := range kcfg.Kernels { Config config.OutOfTree `help:"path to out-of-tree configuration" default:"~/.out-of-tree/out-of-tree.toml"`
if !exists(k.RootFS) || k.DistroType != ki.DistroType {
continue WorkDir string `help:"path to work directory" default:"./" type:"path"`
}
if k.RootFS < ki.RootFS {
rootfs = k.RootFS
return
}
}
return
} }
func handleFallbacks(kcfg config.KernelConfig) { type CLI struct {
sort.Sort(sort.Reverse(config.ByRootFS(kcfg.Kernels))) Globals
for i, k := range kcfg.Kernels { Pew PewCmd `cmd:"" help:"build, run, and test module/exploit"`
if !exists(k.RootFS) { Kernel KernelCmd `cmd:"" help:"manipulate kernels"`
newRootFS := findFallback(kcfg, k) Debug DebugCmd `cmd:"" help:"debug environment"`
Log LogCmd `cmd:"" help:"query logs"`
Pack PackCmd `cmd:"" help:"exploit pack test"`
Gen GenCmd `cmd:"" help:"generate .out-of-tree.toml skeleton"`
s := k.RootFS + " does not exists " Version VersionFlag `name:"version" help:"print version information and quit"`
if newRootFS != "" {
s += "(fallback to " + newRootFS + ")"
} else {
s += "(no fallback found)"
}
kcfg.Kernels[i].RootFS = newRootFS
log.Println(s)
}
}
} }
func checkRequiredUtils() (err error) { type VersionFlag string
// Check for required commands
for _, cmd := range []string{"docker", "qemu-system-x86_64"} {
_, err := exec.Command("which", cmd).CombinedOutput()
if err != nil {
return fmt.Errorf("Command not found: %s", cmd)
}
}
return
}
func checkDockerPermissions() (err error) { func (v VersionFlag) Decode(ctx *kong.DecodeContext) error { return nil }
output, err := exec.Command("docker", "ps").CombinedOutput() func (v VersionFlag) IsBool() bool { return true }
if err != nil { func (v VersionFlag) BeforeApply(app *kong.Kong, vars kong.Vars) error {
err = fmt.Errorf("%s", output) fmt.Println(vars["version"])
} app.Exit(0)
return return nil
} }
func main() { func main() {
@ -78,283 +49,19 @@ func main() {
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
app := kingpin.New( cli := CLI{}
"out-of-tree", ctx := kong.Parse(&cli,
"kernel {module, exploit} development tool", kong.Name("out-of-tree"),
kong.Description("kernel {module, exploit} development tool"),
kong.UsageOnError(),
kong.ConfigureHelp(kong.HelpOptions{
Compact: true,
}),
kong.Vars{
"version": "1.4.0",
},
) )
app.Author("Mikhail Klementev <root@dumpstack.io>") err := ctx.Run(&cli.Globals)
app.Version("1.4.0") ctx.FatalIfErrorf(err)
pathFlag := app.Flag("path", "Path to work directory")
path := pathFlag.Default(".").ExistingDir()
usr, err := user.Current()
if err != nil {
return
}
os.MkdirAll(usr.HomeDir+"/.out-of-tree", os.ModePerm)
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")
kcfgPath := kcfgPathFlag.Default(conf.Kernels).String()
dbPathFlag := app.Flag("db", "Path to database")
dbPath := dbPathFlag.Default(conf.Database).String()
userKcfgPathFlag := app.Flag("user-kernels", "User kernels config")
userKcfgPathEnv := userKcfgPathFlag.Envar("OUT_OF_TREE_KCFG")
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")
qemuTimeout := qemuTimeoutFlag.Default(conf.Qemu.Timeout).Duration()
dockerTimeoutFlag := app.Flag("docker-timeout", "Timeout for docker")
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()
disablePreloadFlag := app.Flag("disable-preload", "Disable module preload")
disablePreload = disablePreloadFlag.Bool()
pewCommand := app.Command("pew", "Build, run and test module/exploit")
pewMax := pewCommand.Flag("max", "Test no more than X kernels").
PlaceHolder("X").Default(fmt.Sprint(kernelsAll)).Int64()
pewRuns := pewCommand.Flag("runs", "Runs per each kernel").
Default("1").Int64()
pewKernelFlag := pewCommand.Flag("kernel", "Override kernel regex")
pewKernel := pewKernelFlag.String()
pewGuessFlag := pewCommand.Flag("guess", "Try all defined kernels")
pewGuess := pewGuessFlag.Bool()
pewBinaryFlag := pewCommand.Flag("binary", "Use binary, do not build")
pewBinary := pewBinaryFlag.String()
pewTestFlag := pewCommand.Flag("test", "Override path test")
pewTest := pewTestFlag.String()
pewDistFlag := pewCommand.Flag("dist", "Build result path")
pewDist := pewDistFlag.Default(pathDevNull).String()
pewThreadsFlag := pewCommand.Flag("threads", "Build result path")
pewThreads := pewThreadsFlag.Default(strconv.Itoa(runtime.NumCPU())).Int()
pewTagFlag := pewCommand.Flag("tag", "Log tagging")
pewTag := pewTagFlag.String()
pewVerboseFlag := pewCommand.Flag("verbose", "Show more information")
pewVerbose := pewVerboseFlag.Bool()
kernelCommand := app.Command("kernel", "Manipulate kernels")
kernelNoDownload := kernelCommand.Flag("no-download",
"Do not download qemu image while kernel generation").Bool()
kernelUseHost := kernelCommand.Flag("host", "Use also host kernels").Bool()
kernelListCommand := kernelCommand.Command("list", "List kernels")
kernelAutogenCommand := kernelCommand.Command("autogen",
"Generate kernels based on a current config")
kernelAutogenMax := kernelAutogenCommand.Flag("max",
"Download random kernels from set defined by regex in "+
"release_mask, but no more than X for each of "+
"release_mask").PlaceHolder("X").Default(
fmt.Sprint(kernelsAll)).Int64()
kernelDockerRegenCommand := kernelCommand.Command("docker-regen",
"Regenerate kernels config from out_of_tree_* docker images")
kernelGenallCommand := kernelCommand.Command("genall",
"Generate all kernels for distro")
genallDistroFlag := kernelGenallCommand.Flag("distro", "Distributive")
distro := genallDistroFlag.Required().String()
genallVerFlag := kernelGenallCommand.Flag("ver", "Distro version")
version := genallVerFlag.Required().String()
genCommand := app.Command("gen", "Generate .out-of-tree.toml skeleton")
genModuleCommand := genCommand.Command("module",
"Generate .out-of-tree.toml skeleton for kernel module")
genExploitCommand := genCommand.Command("exploit",
"Generate .out-of-tree.toml skeleton for kernel exploit")
debugCommand := app.Command("debug", "Kernel debug environment")
debugCommandFlag := debugCommand.Flag("kernel", "Regex (first match)")
debugKernel := debugCommandFlag.Required().String()
debugFlagGDB := debugCommand.Flag("gdb", "Set gdb listen address")
debugGDB := debugFlagGDB.Default("tcp::1234").String()
yekaslr := debugCommand.Flag("enable-kaslr", "Enable KASLR").Bool()
yesmep := debugCommand.Flag("enable-smep", "Enable SMEP").Bool()
yesmap := debugCommand.Flag("enable-smap", "Enable SMAP").Bool()
yekpti := debugCommand.Flag("enable-kpti", "Enable KPTI").Bool()
nokaslr := debugCommand.Flag("disable-kaslr", "Disable KASLR").Bool()
nosmep := debugCommand.Flag("disable-smep", "Disable SMEP").Bool()
nosmap := debugCommand.Flag("disable-smap", "Disable SMAP").Bool()
nokpti := debugCommand.Flag("disable-kpti", "Disable KPTI").Bool()
bootstrapCommand := app.Command("bootstrap", "Apparently nothing")
logCommand := app.Command("log", "Logs")
logQueryCommand := logCommand.Command("query", "Query logs")
logNum := logQueryCommand.Flag("num", "How much lines").Default("50").Int()
logRate := logQueryCommand.Flag("rate", "Show artifact success rate").Bool()
logTag := logQueryCommand.Flag("tag", "Filter tag").String()
logDumpCommand := logCommand.Command("dump",
"Show all info for log entry with ID")
logDumpID := logDumpCommand.Arg("ID", "").Default("-1").Int()
logJSONCommand := logCommand.Command("json", "Generate json statistics")
logJSONTag := logJSONCommand.Flag("tag", "Filter tag").Required().String()
logMarkdownCommand := logCommand.Command("markdown", "Generate markdown statistics")
logMarkdownTag := logMarkdownCommand.Flag("tag", "Filter tag").Required().String()
packCommand := app.Command("pack", "Exploit pack test")
packAutogen := packCommand.Flag("autogen", "Kernel autogeneration").Bool()
packNoDownload := packCommand.Flag("no-download",
"Do not download qemu image while kernel generation").Bool()
packExploitRuns := packCommand.Flag("exploit-runs",
"Amount of runs of each exploit").Default("4").Int64()
packKernelRuns := packCommand.Flag("kernel-runs",
"Amount of runs of each kernel").Default("1").Int64()
err = checkRequiredUtils()
if err != nil {
log.Fatalln(err)
}
err = checkDockerPermissions()
if err != nil {
log.Println(err)
log.Println("You have two options:")
log.Println("\t1. Add user to group docker;")
log.Println("\t2. Run out-of-tree with sudo.")
os.Exit(1)
}
if !exists(usr.HomeDir + "/.out-of-tree/kernels.toml") {
log.Println("No ~/.out-of-tree/kernels.toml: Probably you " +
"need to run `out-of-tree kernel autogen` in " +
"directory that contains .out-of-tree.toml " +
"with defined kernel masks " +
"(see docs at https://out-of-tree.io)")
}
kingpin.MustParse(app.Parse(os.Args[1:]))
if *yekaslr && *nokaslr {
log.Fatalln("Only one of disable/enable can be used at once")
}
if *yesmep && *nosmep {
log.Fatalln("Only one of disable/enable can be used at once")
}
if *yesmap && *nosmap {
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)
if err != nil {
log.Println(err)
}
if exists(*userKcfgPath) {
userKcfg, err := config.ReadKernelConfig(*userKcfgPath)
if err != nil {
log.Fatalln(err)
}
for _, nk := range userKcfg.Kernels {
if !hasKernel(nk, kcfg) {
kcfg.Kernels = append(kcfg.Kernels, nk)
}
}
}
handleFallbacks(kcfg)
db, err := openDatabase(*dbPath)
if err != nil {
log.Fatalln(err)
}
defer db.Close()
stop := time.Time{} // never stop
if *timeout != 0 {
stop = time.Now().Add(*timeout)
}
switch kingpin.MustParse(app.Parse(os.Args[1:])) {
case pewCommand.FullCommand():
err = pewHandler(kcfg, *path, *pewKernel, *pewBinary,
*pewTest, *pewGuess, stop, *qemuTimeout, *dockerTimeout,
*pewMax, *pewRuns, *pewDist, *pewTag, *pewThreads,
db, *pewVerbose)
case kernelListCommand.FullCommand():
err = kernelListHandler(kcfg)
case kernelAutogenCommand.FullCommand():
err = kernelAutogenHandler(*path, *dockerRegistry,
conf.Docker.Commands, *kernelAutogenMax,
*kernelUseHost, !*kernelNoDownload)
case kernelDockerRegenCommand.FullCommand():
err = kernelDockerRegenHandler(*kernelUseHost, !*kernelNoDownload)
case kernelGenallCommand.FullCommand():
err = kernelGenallHandler(*distro, *version,
*dockerRegistry, conf.Docker.Commands,
*kernelUseHost, !*kernelNoDownload)
case genModuleCommand.FullCommand():
err = genConfig(config.KernelModule)
case genExploitCommand.FullCommand():
err = genConfig(config.KernelExploit)
case debugCommand.FullCommand():
err = debugHandler(kcfg, *path, *debugKernel, *debugGDB,
*dockerTimeout, *yekaslr, *yesmep, *yesmap, *yekpti,
*nokaslr, *nosmep, *nosmap, *nokpti)
case bootstrapCommand.FullCommand():
fmt.Println("bootstrap is no more required, " +
"now images downloading on-demand")
fmt.Println("please, remove it from any automation scripts, " +
"because it'll be removed in the next release")
case logQueryCommand.FullCommand():
err = logHandler(db, *path, *logTag, *logNum, *logRate)
case logDumpCommand.FullCommand():
err = logDumpHandler(db, *logDumpID)
case logJSONCommand.FullCommand():
err = logJSONHandler(db, *path, *logJSONTag)
case logMarkdownCommand.FullCommand():
err = logMarkdownHandler(db, *path, *logMarkdownTag)
case packCommand.FullCommand():
err = packHandler(db, *path, *dockerRegistry, stop,
conf.Docker.Commands, kcfg, *packAutogen,
!*packNoDownload, *packExploitRuns, *packKernelRuns)
}
if err != nil {
log.Fatalln(err)
}
if successRate(state) < *threshold {
os.Exit(1)
}
} }

52
pack.go
View File

@ -5,7 +5,6 @@
package main package main
import ( import (
"database/sql"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
@ -15,33 +14,57 @@ import (
"code.dumpstack.io/tools/out-of-tree/config" "code.dumpstack.io/tools/out-of-tree/config"
) )
func packHandler(db *sql.DB, path, registry string, stop time.Time, type PackCmd struct {
commands []config.DockerCommand, kcfg config.KernelConfig, Autogen bool `help:"kernel autogeneration"`
autogen, download bool, exploitRuns, kernelRuns int64) (err error) { NoDownload bool `help:"do not download qemu image while kernel generation"`
ExploitRuns int64 `default:"4" help:"amount of runs of each exploit"`
KernelRuns int64 `default:"1" help:"amount of runs of each kernel"`
Tag string `help:"filter tag"`
Timeout time.Duration `help:"timeout after tool will not spawn new tests"`
}
func (cmd *PackCmd) Run(g *Globals) (err error) {
kcfg, err := config.ReadKernelConfig(g.Config.Kernels)
if err != nil {
log.Println(err)
}
db, err := openDatabase(g.Config.Database)
if err != nil {
log.Fatalln(err)
}
defer db.Close()
stop := time.Time{} // never stop
if cmd.Timeout != 0 {
stop = time.Now().Add(cmd.Timeout)
}
dockerTimeout := time.Minute
qemuTimeout := time.Minute
threads := runtime.NumCPU() threads := runtime.NumCPU()
tag := fmt.Sprintf("pack_run_%d", time.Now().Unix()) tag := fmt.Sprintf("pack_run_%d", time.Now().Unix())
log.Println("Tag:", tag) log.Println("Tag:", tag)
files, err := ioutil.ReadDir(path) files, err := ioutil.ReadDir(g.WorkDir)
if err != nil { if err != nil {
return return
} }
for _, f := range files { for _, f := range files {
workPath := path + "/" + f.Name() workPath := g.WorkDir + "/" + f.Name()
if !exists(workPath + "/.out-of-tree.toml") { if !exists(workPath + "/.out-of-tree.toml") {
continue continue
} }
if autogen { if cmd.Autogen {
var perRegex int64 = 1 var perRegex int64 = 1
err = kernelAutogenHandler(workPath, registry, err = kernelAutogenHandler(workPath,
commands, perRegex, false, download) g.Config.Docker.Registry,
g.Config.Docker.Commands,
perRegex, false, !cmd.NoDownload)
if err != nil { if err != nil {
return return
} }
@ -49,9 +72,10 @@ func packHandler(db *sql.DB, path, registry string, stop time.Time,
log.Println(f.Name()) log.Println(f.Name())
pewHandler(kcfg, workPath, "", "", "", false, pewHandler(kcfg, workPath, "", "", "", false, stop,
stop, dockerTimeout, qemuTimeout, g.Config.Docker.Timeout.Duration,
kernelRuns, exploitRuns, pathDevNull, g.Config.Qemu.Timeout.Duration,
cmd.KernelRuns, cmd.ExploitRuns, pathDevNull,
tag, threads, db, false) tag, threads, db, false)
} }

40
pew.go
View File

@ -26,6 +26,46 @@ import (
"code.dumpstack.io/tools/out-of-tree/qemu" "code.dumpstack.io/tools/out-of-tree/qemu"
) )
type PewCmd struct {
Max int64 `help:"test no more than X kernels" default:"100500"`
Runs int64 `help:"runs per each kernel" default:"1"`
Kernel string `help:"override kernel regex"`
Guess bool `help:"try all defined kernels"`
Binary string `help:"use binary, do not build"`
Test string `help:"override path for test"`
Dist string `help:"build result path" default:"/dev/null"`
Threads int `help:"threads"`
Tag string `help:"log tagging"`
Verbose bool `help:"show more information"`
Timeout time.Duration `help:"timeout after tool will not spawn new tests"`
}
func (cmd *PewCmd) Run(g *Globals) (err error) {
kcfg, err := config.ReadKernelConfig(g.Config.Kernels)
if err != nil {
log.Println(err)
}
stop := time.Time{} // never stop
if cmd.Timeout != 0 {
stop = time.Now().Add(cmd.Timeout)
}
db, err := openDatabase(g.Config.Database)
if err != nil {
log.Fatalln(err)
}
defer db.Close()
return pewHandler(
kcfg, g.WorkDir, cmd.Kernel, cmd.Binary, cmd.Test,
cmd.Guess, stop, g.Config.Qemu.Timeout.Duration,
g.Config.Docker.Timeout.Duration,
cmd.Max, cmd.Runs, cmd.Dist, cmd.Tag, cmd.Threads,
db, cmd.Verbose,
)
}
type runstate struct { type runstate struct {
Overall, Success float64 Overall, Success float64
} }

View File

@ -21,15 +21,9 @@ import (
"code.dumpstack.io/tools/out-of-tree/qemu" "code.dumpstack.io/tools/out-of-tree/qemu"
) )
var disablePreload *bool
func preloadModules(q *qemu.System, ka config.Artifact, ki config.KernelInfo, func preloadModules(q *qemu.System, ka config.Artifact, ki config.KernelInfo,
dockerTimeout time.Duration) (err error) { dockerTimeout time.Duration) (err error) {
if *disablePreload {
return
}
for _, pm := range ka.Preload { for _, pm := range ka.Preload {
err = preload(q, ki, pm, dockerTimeout) err = preload(q, ki, pm, dockerTimeout)
if err != nil { if err != nil {