out-of-tree kernel {module, exploit} development tool
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

pew.go 8.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. // Copyright 2018 Mikhail Klementev. All rights reserved.
  2. // Use of this source code is governed by a AGPLv3 license
  3. // (or later) that can be found in the LICENSE file.
  4. package main
  5. import (
  6. "errors"
  7. "fmt"
  8. "io/ioutil"
  9. "log"
  10. "math/rand"
  11. "os"
  12. "os/exec"
  13. "runtime"
  14. "strings"
  15. "time"
  16. "github.com/logrusorgru/aurora"
  17. "github.com/otiai10/copy"
  18. "github.com/remeh/sizedwaitgroup"
  19. "github.com/jollheef/out-of-tree/config"
  20. qemu "github.com/jollheef/out-of-tree/qemu"
  21. )
  22. var somethingFailed = false
  23. func dockerRun(timeout time.Duration, container, workdir, command string) (
  24. output string, err error) {
  25. cmd := exec.Command("docker", "run", "-v", workdir+":/work",
  26. container, "bash", "-c", "cd /work && "+command)
  27. timer := time.AfterFunc(timeout, func() {
  28. cmd.Process.Kill()
  29. })
  30. defer timer.Stop()
  31. raw, err := cmd.CombinedOutput()
  32. if err != nil {
  33. return
  34. }
  35. output = string(raw)
  36. return
  37. }
  38. func build(tmp string, ka config.Artifact, ki config.KernelInfo,
  39. dockerTimeout time.Duration) (outPath, output string, err error) {
  40. target := fmt.Sprintf("%d_%s", rand.Int(), ki.KernelRelease)
  41. tmpSourcePath := tmp + "/source"
  42. err = copy.Copy(ka.SourcePath, tmpSourcePath)
  43. if err != nil {
  44. return
  45. }
  46. outPath = tmpSourcePath + "/" + target
  47. if ka.Type == config.KernelModule {
  48. outPath += ".ko"
  49. }
  50. kernel := "/lib/modules/" + ki.KernelRelease + "/build"
  51. output, err = dockerRun(dockerTimeout, ki.ContainerName,
  52. tmpSourcePath, "make KERNEL="+kernel+" TARGET="+target)
  53. if err != nil {
  54. err = errors.New("make execution error")
  55. return
  56. }
  57. return
  58. }
  59. func cleanDmesg(q *qemu.QemuSystem) (err error) {
  60. start := time.Now()
  61. for {
  62. _, err = q.Command("root", "dmesg -c")
  63. if err == nil {
  64. break
  65. }
  66. time.Sleep(time.Second)
  67. if time.Now().After(start.Add(time.Minute)) {
  68. err = errors.New("Can't connect to qemu")
  69. break
  70. }
  71. }
  72. return
  73. }
  74. func testKernelModule(q *qemu.QemuSystem, ka config.Artifact,
  75. test string) (output string, err error) {
  76. output, err = q.Command("root", test)
  77. // TODO generic checks for WARNING's and so on
  78. return
  79. }
  80. func testKernelExploit(q *qemu.QemuSystem, ka config.Artifact,
  81. test, exploit string) (output string, err error) {
  82. output, err = q.Command("user", "chmod +x "+exploit)
  83. if err != nil {
  84. return
  85. }
  86. randFilePath := fmt.Sprintf("/root/%d", rand.Int())
  87. cmd := fmt.Sprintf("%s %s %s", test, exploit, randFilePath)
  88. output, err = q.Command("user", cmd)
  89. if err != nil {
  90. return
  91. }
  92. _, err = q.Command("root", "stat "+randFilePath)
  93. if err != nil {
  94. return
  95. }
  96. return
  97. }
  98. func genOkFail(name string, ok bool) aurora.Value {
  99. if ok {
  100. s := " " + name + " SUCCESS "
  101. return aurora.BgGreen(aurora.Black(s))
  102. } else {
  103. somethingFailed = true
  104. s := " " + name + " FAILURE "
  105. return aurora.BgRed(aurora.Gray(aurora.Bold(s)))
  106. }
  107. }
  108. func dumpResult(q *qemu.QemuSystem, ka config.Artifact, ki config.KernelInfo,
  109. build_ok, run_ok, test_ok *bool) {
  110. distroInfo := fmt.Sprintf("%s-%s {%s}", ki.DistroType,
  111. ki.DistroRelease, ki.KernelRelease)
  112. colored := ""
  113. if ka.Type == config.KernelExploit {
  114. colored = aurora.Sprintf("[*] %40s: %s %s", distroInfo,
  115. genOkFail("BUILD", *build_ok),
  116. genOkFail("LPE", *test_ok))
  117. } else {
  118. colored = aurora.Sprintf("[*] %40s: %s %s %s", distroInfo,
  119. genOkFail("BUILD", *build_ok),
  120. genOkFail("INSMOD", *run_ok),
  121. genOkFail("TEST", *test_ok))
  122. }
  123. additional := ""
  124. if q.KernelPanic {
  125. additional = "(panic)"
  126. } else if q.KilledByTimeout {
  127. additional = "(timeout)"
  128. }
  129. if additional != "" {
  130. fmt.Println(colored, additional)
  131. } else {
  132. fmt.Println(colored)
  133. }
  134. }
  135. func whatever(swg *sizedwaitgroup.SizedWaitGroup, ka config.Artifact,
  136. ki config.KernelInfo, binaryPath, testPath string,
  137. qemuTimeout, dockerTimeout time.Duration) {
  138. defer swg.Done()
  139. kernel := qemu.Kernel{KernelPath: ki.KernelPath, InitrdPath: ki.InitrdPath}
  140. q, err := qemu.NewQemuSystem(qemu.X86_64, kernel, ki.RootFS)
  141. if err != nil {
  142. log.Println("Qemu creation error:", err)
  143. return
  144. }
  145. q.Timeout = qemuTimeout
  146. err = q.Start()
  147. if err != nil {
  148. log.Println("Qemu start error:", err)
  149. return
  150. }
  151. defer q.Stop()
  152. tmp, err := ioutil.TempDir("/tmp/", "out-of-tree_")
  153. if err != nil {
  154. log.Println("Temporary directory creation error:", err)
  155. return
  156. }
  157. defer os.RemoveAll(tmp)
  158. build_ok := false
  159. run_ok := false
  160. test_ok := false
  161. defer dumpResult(q, ka, ki, &build_ok, &run_ok, &test_ok)
  162. var outFile, output string
  163. if binaryPath == "" {
  164. // TODO Write build log to file or database
  165. outFile, output, err = build(tmp, ka, ki, dockerTimeout)
  166. if err != nil {
  167. log.Println(output)
  168. return
  169. }
  170. build_ok = true
  171. } else {
  172. outFile = binaryPath
  173. build_ok = true
  174. }
  175. err = cleanDmesg(q)
  176. if err != nil {
  177. return
  178. }
  179. if testPath == "" {
  180. testPath = outFile + "_test"
  181. }
  182. remoteTest := fmt.Sprintf("/tmp/test_%d", rand.Int())
  183. err = q.CopyFile("user", testPath, remoteTest)
  184. if err != nil {
  185. if ka.Type == config.KernelExploit {
  186. log.Println("Use `echo touch FILE | exploit` for test")
  187. q.Command("user",
  188. "echo -e '#!/bin/sh\necho touch $2 | $1' "+
  189. "> "+remoteTest+
  190. " && chmod +x "+remoteTest)
  191. } else {
  192. log.Println("copy file err", err)
  193. // we should not exit because of testing 'insmod' part
  194. // for kernel module
  195. }
  196. } else {
  197. _, err = q.Command("root", "chmod +x "+remoteTest)
  198. if err != nil {
  199. return
  200. }
  201. }
  202. if ka.Type == config.KernelModule {
  203. // TODO Write insmod log to file or database
  204. output, err := q.CopyAndInsmod(outFile)
  205. if err != nil {
  206. log.Println(output, err)
  207. return
  208. }
  209. run_ok = true
  210. // TODO Write test results to file or database
  211. output, err = testKernelModule(q, ka, remoteTest)
  212. if err != nil {
  213. log.Println(output, err)
  214. return
  215. }
  216. test_ok = true
  217. } else if ka.Type == config.KernelExploit {
  218. remoteExploit := fmt.Sprintf("/tmp/exploit_%d", rand.Int())
  219. err = q.CopyFile("user", outFile, remoteExploit)
  220. if err != nil {
  221. return
  222. }
  223. // TODO Write test results to file or database
  224. output, err = testKernelExploit(q, ka, remoteTest, remoteExploit)
  225. if err != nil {
  226. log.Println(output)
  227. return
  228. }
  229. run_ok = true // does not really used
  230. test_ok = true
  231. } else {
  232. err = errors.New("Unsupported artifact type")
  233. }
  234. return
  235. }
  236. func performCI(ka config.Artifact, kcfg config.KernelConfig, binaryPath,
  237. testPath string, qemuTimeout, dockerTimeout time.Duration) (err error) {
  238. found := false
  239. swg := sizedwaitgroup.New(runtime.NumCPU())
  240. for _, kernel := range kcfg.Kernels {
  241. var supported bool
  242. supported, err = ka.Supported(kernel)
  243. if err != nil {
  244. return
  245. }
  246. if supported {
  247. found = true
  248. swg.Add()
  249. go whatever(&swg, ka, kernel, binaryPath, testPath,
  250. qemuTimeout, dockerTimeout)
  251. }
  252. }
  253. swg.Wait()
  254. if !found {
  255. err = errors.New("No supported kernels found")
  256. }
  257. return
  258. }
  259. func exists(path string) bool {
  260. if _, err := os.Stat(path); err != nil {
  261. return false
  262. }
  263. return true
  264. }
  265. func kernelMask(kernel string) (km config.KernelMask, err error) {
  266. parts := strings.Split(kernel, ":")
  267. if len(parts) != 2 {
  268. err = errors.New("Kernel is not 'distroType:regex'")
  269. return
  270. }
  271. dt, err := config.NewDistroType(parts[0])
  272. if err != nil {
  273. return
  274. }
  275. km = config.KernelMask{DistroType: dt, ReleaseMask: parts[1]}
  276. return
  277. }
  278. func pewHandler(kcfg config.KernelConfig,
  279. workPath, ovrrdKrnl, binary, test string, guess bool,
  280. qemuTimeout, dockerTimeout time.Duration) (err error) {
  281. ka, err := config.ReadArtifactConfig(workPath + "/.out-of-tree.toml")
  282. if err != nil {
  283. return
  284. }
  285. if ka.SourcePath == "" {
  286. ka.SourcePath = workPath
  287. }
  288. if ovrrdKrnl != "" {
  289. var km config.KernelMask
  290. km, err = kernelMask(ovrrdKrnl)
  291. if err != nil {
  292. return
  293. }
  294. ka.SupportedKernels = []config.KernelMask{km}
  295. }
  296. if guess {
  297. ka.SupportedKernels = []config.KernelMask{}
  298. for _, dType := range config.DistroTypeStrings {
  299. var dt config.DistroType
  300. dt, err = config.NewDistroType(dType)
  301. if err != nil {
  302. return
  303. }
  304. km := config.KernelMask{DistroType: dt, ReleaseMask: ".*"}
  305. ka.SupportedKernels = append(ka.SupportedKernels, km)
  306. }
  307. }
  308. err = performCI(ka, kcfg, binary, test, qemuTimeout, dockerTimeout)
  309. if err != nil {
  310. return
  311. }
  312. return
  313. }