// Copyright 2024 Mikhail Klementev. All rights reserved. // Use of this source code is governed by a AGPLv3 license // (or later) that can be found in the LICENSE file. package cmd import ( "errors" "fmt" "os" "strings" "time" "github.com/rs/zerolog/log" "gopkg.in/logrusorgru/aurora.v2" "code.dumpstack.io/tools/out-of-tree/artifact" "code.dumpstack.io/tools/out-of-tree/config" "code.dumpstack.io/tools/out-of-tree/distro" "code.dumpstack.io/tools/out-of-tree/fs" "code.dumpstack.io/tools/out-of-tree/qemu" ) type DebugCmd struct { KernelRegex string `required:"" help:"set kernel regex"` DistroID string `required:"" help:"set distribution"` DistroRelease string `required:"" help:"set distribution release"` Gdb string `help:"gdb listen address" default:"tcp::1234"` SshAddr string `help:"ssh address to listen" default:"127.0.0.1"` SshPort int `help:"ssh port to listen" default:"50022"` ArtifactConfig string `help:"path to artifact config" type:"path"` 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"` } // TODO: merge with pew.go func (cmd *DebugCmd) Run(g *Globals) (err error) { kcfg, err := config.ReadKernelConfig(g.Config.Kernels) if err != nil { log.Error().Err(err).Msg("read kernel config") } var configPath string if cmd.ArtifactConfig == "" { configPath = g.WorkDir + "/.out-of-tree.toml" } else { configPath = cmd.ArtifactConfig } ka, err := artifact.Artifact{}.Read(configPath) if err != nil { return } if ka.SourcePath == "" { ka.SourcePath = g.WorkDir } var km artifact.Target km.Distro.ID, err = distro.NewID(cmd.DistroID) if err != nil { return } km.Distro.Release = cmd.DistroRelease km.Kernel.Regex = cmd.KernelRegex ka.Targets = []artifact.Target{km} ki, err := firstSupported(kcfg, ka) if err != nil { return } kernel := qemu.Kernel{KernelPath: ki.KernelPath, InitrdPath: ki.InitrdPath} q, err := qemu.NewSystem(qemu.X86x64, kernel, ki.RootFS) if err != nil { return } err = q.SetSSHAddrPort(cmd.SshAddr, cmd.SshPort) if err != nil { return } if ka.Qemu.Cpus != 0 { q.Cpus = ka.Qemu.Cpus } if ka.Qemu.Memory != 0 { q.Memory = ka.Qemu.Memory } if ka.Docker.Timeout.Duration != 0 { g.Config.Docker.Timeout.Duration = ka.Docker.Timeout.Duration } q.SetKASLR(false) // set KASLR to false by default because of gdb q.SetSMEP(!ka.Mitigations.DisableSmep) q.SetSMAP(!ka.Mitigations.DisableSmap) q.SetKPTI(!ka.Mitigations.DisableKpti) if cmd.Kaslr { q.SetKASLR(true) } else if cmd.NoKaslr { q.SetKASLR(false) } if cmd.Smep { q.SetSMEP(true) } else if cmd.NoSmep { q.SetSMEP(false) } if cmd.Smap { q.SetSMAP(true) } else if cmd.NoSmap { q.SetSMAP(false) } if cmd.Kpti { q.SetKPTI(true) } else if cmd.NoKpti { q.SetKPTI(false) } redgreen := func(name string, enabled bool) aurora.Value { if enabled { return aurora.BgGreen(aurora.Black(name)) } return aurora.BgRed(aurora.White(name)) } fmt.Printf("[*] %s %s %s %s\n", redgreen("KASLR", q.GetKASLR()), redgreen("SMEP", q.GetSMEP()), redgreen("SMAP", q.GetSMAP()), redgreen("KPTI", q.GetKPTI())) fmt.Printf("[*] SMP: %d CPUs\n", q.Cpus) fmt.Printf("[*] Memory: %d MB\n", q.Memory) q.Debug(cmd.Gdb) coloredGdbAddress := aurora.BgGreen(aurora.Black(cmd.Gdb)) fmt.Printf("[*] gdb is listening on %s\n", coloredGdbAddress) err = q.Start() if err != nil { return } defer q.Stop() tmp, err := fs.TempDir() if err != nil { return } defer os.RemoveAll(tmp) err = q.WaitForSSH(time.Minute) if err != nil { return } if ka.StandardModules { // Module depends on one of the standard modules err = artifact.CopyStandardModules(q, ki) if err != nil { log.Error().Err(err).Msg("copy standard modules") return } } err = artifact.PreloadModules(q, ka, ki, g.Config.Docker.Timeout.Duration) if err != nil { log.Error().Err(err).Msg("preload modules") return } var buildDir, outFile, output, remoteFile string if ka.Type == artifact.Script { err = q.CopyFile("root", ka.Script, ka.Script) if err != nil { return } } else { buildDir, outFile, output, err = artifact.Build(log.Logger, tmp, ka, ki, g.Config.Docker.Timeout.Duration) if err != nil { log.Error().Err(err).Msg(output) return } remoteFile = "/tmp/" + strings.Replace(ka.Name, " ", "_", -1) if ka.Type == artifact.KernelModule { remoteFile += ".ko" } err = q.CopyFile("user", outFile, remoteFile) if err != nil { return } } // Copy all test files to the remote machine for _, f := range ka.TestFiles { if f.Local[0] != '/' { if buildDir != "" { f.Local = buildDir + "/" + f.Local } } err = q.CopyFile(f.User, f.Local, f.Remote) if err != nil { log.Error().Err(err).Msgf("copy %s -> %s", f.Local, f.Remote) return } } coloredRemoteFile := aurora.BgGreen(aurora.Black(remoteFile)) fmt.Printf("[*] build result copied to %s\n", coloredRemoteFile) fmt.Printf("\n%s\n", q.GetSSHCommand()) fmt.Printf("gdb %s -ex 'target remote %s'\n\n", ki.VmlinuxPath, cmd.Gdb) // TODO set substitute-path /build/.../linux-... /path/to/linux-source err = interactive(q) return } func firstSupported(kcfg config.KernelConfig, ka artifact.Artifact) (ki distro.KernelInfo, err error) { for _, ki = range kcfg.Kernels { var supported bool supported, err = ka.Supported(ki) if err != nil || supported { return } } err = errors.New("no supported kernel found") return } func handleLine(q *qemu.System) (err error) { fmt.Print("out-of-tree> ") rawLine := "help" fmt.Scanf("%s", &rawLine) params := strings.Fields(rawLine) cmd := params[0] switch cmd { case "h", "help": fmt.Printf("help\t: print this help message\n") fmt.Printf("log\t: print qemu log\n") fmt.Printf("clog\t: print qemu log and cleanup buffer\n") fmt.Printf("cleanup\t: cleanup qemu log buffer\n") fmt.Printf("ssh\t: print arguments to ssh command\n") fmt.Printf("quit\t: quit\n") case "l", "log": fmt.Println(q.Stdout) case "cl", "clog": fmt.Println(q.Stdout) q.Stdout = "" case "c", "cleanup": q.Stdout = "" case "s", "ssh": fmt.Println(q.GetSSHCommand()) case "q", "quit": return errors.New("end of session") default: fmt.Println("No such command") } return } func interactive(q *qemu.System) (err error) { for { err = handleLine(q) if err != nil { return } } }