From 2f52f6db6df02af99e48971cd4a9f92ca4dd734a Mon Sep 17 00:00:00 2001 From: Mikhail Klementev Date: Tue, 31 Jan 2023 07:13:33 +0000 Subject: [PATCH] Refactor command line interface --- debug.go | 31 ++++- gen.go | 14 +++ kernel.go | 206 +++++++++++++++++------------- log.go | 217 +++++++++++++++++++------------- main.go | 363 ++++++----------------------------------------------- pack.go | 52 +++++--- pew.go | 40 ++++++ preload.go | 6 - 8 files changed, 406 insertions(+), 523 deletions(-) diff --git a/debug.go b/debug.go index c5e6107..8c064bd 100644 --- a/debug.go +++ b/debug.go @@ -19,6 +19,33 @@ import ( "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, 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 - for _,f := range ka.TestFiles { - err = q.CopyFile(f.User,f.Local,f.Remote) + for _, f := range ka.TestFiles { + err = q.CopyFile(f.User, f.Local, f.Remote) if err != nil { log.Println("error copy err:", err, f.Local, f.Remote) return diff --git a/gen.go b/gen.go index 0e23cde..d690fb6 100644 --- a/gen.go +++ b/gen.go @@ -12,6 +12,20 @@ import ( "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) { a := config.Artifact{ Name: "Put name here", diff --git a/kernel.go b/kernel.go index c4e9e9f..0597dd1 100644 --- a/kernel.go +++ b/kernel.go @@ -24,18 +24,133 @@ import ( "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 { return errors.New("No kernels found") } + for _, k := range kcfg.Kernels { fmt.Println(k.DistroType, k.DistroRelease, k.KernelRelease) } + 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) ( pkgs []string, err error) { @@ -637,90 +752,3 @@ func generateKernels(km config.KernelMask, registry string, } 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) -} diff --git a/log.go b/log.go index 90cf077..9c0894e 100644 --- a/log.go +++ b/log.go @@ -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 // (or later) that can be found in the LICENSE file. @@ -18,56 +18,41 @@ import ( "code.dumpstack.io/tools/out-of-tree/config" ) -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 LogCmd struct { + Query LogQueryCmd `cmd:"" help:"query logs"` + Dump LogDumpCmd `cmd:"" help:"show all info for log entry with ID"` + Json LogJsonCmd `cmd:"" help:"generate json statistics"` + Makrdown LogMarkdownCmd `cmd:"" help:"generate markdown statistics"` } -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 - ka, kaErr := config.ReadArtifactConfig(path + "/.out-of-tree.toml") + ka, kaErr := config.ReadArtifactConfig(g.WorkDir + "/.out-of-tree.toml") if kaErr == nil { 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 { - les, err = getAllLogs(db, tag, num) + les, err = getAllLogs(db, cmd.Tag, cmd.Num) } if err != nil { return } s := "\nS" - if rate { + if cmd.Rate { if kaErr != nil { err = kaErr 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) - les, err = getAllArtifactLogs(db, tag, math.MaxInt64, ka) + les, err = getAllArtifactLogs(db, cmd.Tag, math.MaxInt64, ka) if err != nil { return } @@ -99,10 +84,20 @@ func logHandler(db *sql.DB, path, tag string, num int, rate bool) (err error) { 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 - if id > 0 { - l, err = getLogByID(db, id) + if cmd.ID > 0 { + l, err = getLogByID(db, cmd.ID) } else { l, err = getLastLog(db) } @@ -150,6 +145,102 @@ func logDumpHandler(db *sql.DB, id int) (err error) { 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 { All, BuildOK, RunOK, TestOK, Timeout, Panic int } @@ -206,45 +297,3 @@ func getStats(db *sql.DB, path, tag string) ( 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 -} diff --git a/main.go b/main.go index dbe77b1..37e02ab 100644 --- a/main.go +++ b/main.go @@ -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 // (or later) that can be found in the LICENSE file. @@ -8,69 +8,40 @@ import ( "fmt" "log" "math/rand" - "os" - "os/exec" - "os/user" - "runtime" - "sort" - "strconv" "time" - kingpin "gopkg.in/alecthomas/kingpin.v2" + "github.com/alecthomas/kong" "code.dumpstack.io/tools/out-of-tree/config" ) -func findFallback(kcfg config.KernelConfig, ki config.KernelInfo) (rootfs string) { - for _, k := range kcfg.Kernels { - if !exists(k.RootFS) || k.DistroType != ki.DistroType { - continue - } - if k.RootFS < ki.RootFS { - rootfs = k.RootFS - return - } - } - return +type Globals struct { + Config config.OutOfTree `help:"path to out-of-tree configuration" default:"~/.out-of-tree/out-of-tree.toml"` + + WorkDir string `help:"path to work directory" default:"./" type:"path"` } -func handleFallbacks(kcfg config.KernelConfig) { - sort.Sort(sort.Reverse(config.ByRootFS(kcfg.Kernels))) +type CLI struct { + Globals - for i, k := range kcfg.Kernels { - if !exists(k.RootFS) { - newRootFS := findFallback(kcfg, k) + Pew PewCmd `cmd:"" help:"build, run, and test module/exploit"` + Kernel KernelCmd `cmd:"" help:"manipulate kernels"` + 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 " - if newRootFS != "" { - s += "(fallback to " + newRootFS + ")" - } else { - s += "(no fallback found)" - } - - kcfg.Kernels[i].RootFS = newRootFS - log.Println(s) - } - } + Version VersionFlag `name:"version" help:"print version information and quit"` } -func checkRequiredUtils() (err error) { - // 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 -} +type VersionFlag string -func checkDockerPermissions() (err error) { - output, err := exec.Command("docker", "ps").CombinedOutput() - if err != nil { - err = fmt.Errorf("%s", output) - } - return +func (v VersionFlag) Decode(ctx *kong.DecodeContext) error { return nil } +func (v VersionFlag) IsBool() bool { return true } +func (v VersionFlag) BeforeApply(app *kong.Kong, vars kong.Vars) error { + fmt.Println(vars["version"]) + app.Exit(0) + return nil } func main() { @@ -78,283 +49,19 @@ func main() { rand.Seed(time.Now().UnixNano()) - app := kingpin.New( - "out-of-tree", - "kernel {module, exploit} development tool", + cli := CLI{} + ctx := kong.Parse(&cli, + 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 ") - app.Version("1.4.0") - - 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) - } + err := ctx.Run(&cli.Globals) + ctx.FatalIfErrorf(err) } diff --git a/pack.go b/pack.go index 5df9b4a..3e8c8ea 100644 --- a/pack.go +++ b/pack.go @@ -5,7 +5,6 @@ package main import ( - "database/sql" "fmt" "io/ioutil" "log" @@ -15,33 +14,57 @@ import ( "code.dumpstack.io/tools/out-of-tree/config" ) -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) { +type PackCmd struct { + Autogen bool `help:"kernel autogeneration"` + 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() tag := fmt.Sprintf("pack_run_%d", time.Now().Unix()) log.Println("Tag:", tag) - files, err := ioutil.ReadDir(path) + files, err := ioutil.ReadDir(g.WorkDir) if err != nil { return } for _, f := range files { - workPath := path + "/" + f.Name() + workPath := g.WorkDir + "/" + f.Name() if !exists(workPath + "/.out-of-tree.toml") { continue } - if autogen { + if cmd.Autogen { var perRegex int64 = 1 - err = kernelAutogenHandler(workPath, registry, - commands, perRegex, false, download) + err = kernelAutogenHandler(workPath, + g.Config.Docker.Registry, + g.Config.Docker.Commands, + perRegex, false, !cmd.NoDownload) if err != nil { return } @@ -49,9 +72,10 @@ func packHandler(db *sql.DB, path, registry string, stop time.Time, log.Println(f.Name()) - pewHandler(kcfg, workPath, "", "", "", false, - stop, dockerTimeout, qemuTimeout, - kernelRuns, exploitRuns, pathDevNull, + pewHandler(kcfg, workPath, "", "", "", false, stop, + g.Config.Docker.Timeout.Duration, + g.Config.Qemu.Timeout.Duration, + cmd.KernelRuns, cmd.ExploitRuns, pathDevNull, tag, threads, db, false) } diff --git a/pew.go b/pew.go index 0a9bceb..b6b537e 100644 --- a/pew.go +++ b/pew.go @@ -26,6 +26,46 @@ import ( "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 { Overall, Success float64 } diff --git a/preload.go b/preload.go index 706eb51..dffd35b 100644 --- a/preload.go +++ b/preload.go @@ -21,15 +21,9 @@ import ( "code.dumpstack.io/tools/out-of-tree/qemu" ) -var disablePreload *bool - func preloadModules(q *qemu.System, ka config.Artifact, ki config.KernelInfo, dockerTimeout time.Duration) (err error) { - if *disablePreload { - return - } - for _, pm := range ka.Preload { err = preload(q, ki, pm, dockerTimeout) if err != nil {