Refactor command line interface
This commit is contained in:
parent
935266c850
commit
2f52f6db6d
31
debug.go
31
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
|
||||
|
14
gen.go
14
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",
|
||||
|
206
kernel.go
206
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)
|
||||
}
|
||||
|
217
log.go
217
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
|
||||
}
|
||||
|
363
main.go
363
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 <root@dumpstack.io>")
|
||||
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)
|
||||
}
|
||||
|
52
pack.go
52
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)
|
||||
}
|
||||
|
||||
|
40
pew.go
40
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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user