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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  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. "database/sql"
  7. "errors"
  8. "fmt"
  9. "io"
  10. "io/ioutil"
  11. "log"
  12. "math/rand"
  13. "os"
  14. "os/exec"
  15. "os/user"
  16. "strings"
  17. "time"
  18. "github.com/otiai10/copy"
  19. "github.com/remeh/sizedwaitgroup"
  20. "gopkg.in/logrusorgru/aurora.v1"
  21. "code.dumpstack.io/tools/out-of-tree/config"
  22. "code.dumpstack.io/tools/out-of-tree/qemu"
  23. )
  24. type runstate struct {
  25. Overall, Success float64
  26. }
  27. var (
  28. state runstate
  29. )
  30. func successRate(state runstate) float64 {
  31. return state.Success / state.Overall
  32. }
  33. const pathDevNull = "/dev/null"
  34. func dockerRun(timeout time.Duration, container, workdir, command string) (
  35. output string, err error) {
  36. cmd := exec.Command("docker", "run", "-v", workdir+":/work",
  37. container, "bash", "-c", "cd /work && "+command)
  38. timer := time.AfterFunc(timeout, func() {
  39. cmd.Process.Kill()
  40. })
  41. defer timer.Stop()
  42. raw, err := cmd.CombinedOutput()
  43. if err != nil {
  44. e := fmt.Sprintf("error `%v` for cmd `%v` with output `%v`",
  45. err, command, string(raw))
  46. err = errors.New(e)
  47. return
  48. }
  49. output = string(raw)
  50. return
  51. }
  52. func build(tmp string, ka config.Artifact, ki config.KernelInfo,
  53. dockerTimeout time.Duration) (outPath, output string, err error) {
  54. target := fmt.Sprintf("%d_%s", rand.Int(), ki.KernelRelease)
  55. tmpSourcePath := tmp + "/source"
  56. err = copy.Copy(ka.SourcePath, tmpSourcePath)
  57. if err != nil {
  58. return
  59. }
  60. outPath = tmpSourcePath + "/" + target
  61. if ka.Type == config.KernelModule {
  62. outPath += ".ko"
  63. }
  64. kernel := "/lib/modules/" + ki.KernelRelease + "/build"
  65. if ki.KernelSource != "" {
  66. kernel = ki.KernelSource
  67. }
  68. if ki.ContainerName != "" {
  69. output, err = dockerRun(dockerTimeout, ki.ContainerName,
  70. tmpSourcePath, "make KERNEL="+kernel+" TARGET="+target+
  71. " && chmod -R 777 /work")
  72. } else {
  73. command := "make KERNEL=" + kernel + " TARGET=" + target
  74. cmd := exec.Command("bash", "-c", "cd "+tmpSourcePath+" && "+command)
  75. timer := time.AfterFunc(dockerTimeout, func() {
  76. cmd.Process.Kill()
  77. })
  78. defer timer.Stop()
  79. var raw []byte
  80. raw, err = cmd.CombinedOutput()
  81. if err != nil {
  82. e := fmt.Sprintf("error `%v` for cmd `%v` with output `%v`",
  83. err, command, string(raw))
  84. err = errors.New(e)
  85. return
  86. }
  87. output = string(raw)
  88. }
  89. return
  90. }
  91. func testKernelModule(q *qemu.System, ka config.Artifact,
  92. test string) (output string, err error) {
  93. output, err = q.Command("root", test)
  94. // TODO generic checks for WARNING's and so on
  95. return
  96. }
  97. func testKernelExploit(q *qemu.System, ka config.Artifact,
  98. test, exploit string) (output string, err error) {
  99. output, err = q.Command("user", "chmod +x "+exploit)
  100. if err != nil {
  101. return
  102. }
  103. randFilePath := fmt.Sprintf("/root/%d", rand.Int())
  104. cmd := fmt.Sprintf("%s %s %s", test, exploit, randFilePath)
  105. output, err = q.Command("user", cmd)
  106. if err != nil {
  107. return
  108. }
  109. _, err = q.Command("root", "stat "+randFilePath)
  110. if err != nil {
  111. return
  112. }
  113. return
  114. }
  115. func genOkFail(name string, ok bool) (aurv aurora.Value) {
  116. state.Overall += 1
  117. if ok {
  118. state.Success += 1
  119. s := " " + name + " SUCCESS "
  120. aurv = aurora.BgGreen(aurora.Black(s))
  121. } else {
  122. s := " " + name + " FAILURE "
  123. aurv = aurora.BgRed(aurora.Gray(aurora.Bold(s)))
  124. }
  125. return
  126. }
  127. type phasesResult struct {
  128. BuildArtifact string
  129. Build, Run, Test struct {
  130. Output string
  131. Ok bool
  132. }
  133. }
  134. func copyFile(sourcePath, destinationPath string) (err error) {
  135. sourceFile, err := os.Open(sourcePath)
  136. if err != nil {
  137. return
  138. }
  139. defer sourceFile.Close()
  140. destinationFile, err := os.Create(destinationPath)
  141. if err != nil {
  142. return err
  143. }
  144. if _, err := io.Copy(destinationFile, sourceFile); err != nil {
  145. destinationFile.Close()
  146. return err
  147. }
  148. return destinationFile.Close()
  149. }
  150. func dumpResult(q *qemu.System, ka config.Artifact, ki config.KernelInfo,
  151. res *phasesResult, dist, tag, binary string, db *sql.DB) {
  152. // TODO merge (problem is it's not 100% same) with log.go:logLogEntry
  153. distroInfo := fmt.Sprintf("%s-%s {%s}", ki.DistroType,
  154. ki.DistroRelease, ki.KernelRelease)
  155. colored := ""
  156. if ka.Type == config.KernelExploit {
  157. colored = aurora.Sprintf("[*] %40s: %s %s", distroInfo,
  158. genOkFail("BUILD", res.Build.Ok),
  159. genOkFail("LPE", res.Test.Ok))
  160. } else {
  161. colored = aurora.Sprintf("[*] %40s: %s %s %s", distroInfo,
  162. genOkFail("BUILD", res.Build.Ok),
  163. genOkFail("INSMOD", res.Run.Ok),
  164. genOkFail("TEST", res.Test.Ok))
  165. }
  166. additional := ""
  167. if q.KernelPanic {
  168. additional = "(panic)"
  169. } else if q.KilledByTimeout {
  170. additional = "(timeout)"
  171. }
  172. if additional != "" {
  173. fmt.Println(colored, additional)
  174. } else {
  175. fmt.Println(colored)
  176. }
  177. err := addToLog(db, q, ka, ki, res, tag)
  178. if err != nil {
  179. log.Println("[db] addToLog (", ka, ") error:", err)
  180. }
  181. if binary == "" && dist != pathDevNull {
  182. err = os.MkdirAll(dist, os.ModePerm)
  183. if err != nil {
  184. log.Println("os.MkdirAll (", ka, ") error:", err)
  185. }
  186. path := fmt.Sprintf("%s/%s-%s-%s", dist, ki.DistroType,
  187. ki.DistroRelease, ki.KernelRelease)
  188. if ka.Type != config.KernelExploit {
  189. path += ".ko"
  190. }
  191. err = copyFile(res.BuildArtifact, path)
  192. if err != nil {
  193. log.Println("copyFile (", ka, ") error:", err)
  194. }
  195. }
  196. }
  197. func copyArtifactAndTest(q *qemu.System, ka config.Artifact,
  198. res *phasesResult, remoteTest string) (err error) {
  199. switch ka.Type {
  200. case config.KernelModule:
  201. res.Run.Output, err = q.CopyAndInsmod(res.BuildArtifact)
  202. if err != nil {
  203. log.Println(res.Run.Output, err)
  204. return
  205. }
  206. res.Run.Ok = true
  207. res.Test.Output, err = testKernelModule(q, ka, remoteTest)
  208. if err != nil {
  209. log.Println(res.Test.Output, err)
  210. return
  211. }
  212. res.Test.Ok = true
  213. case config.KernelExploit:
  214. remoteExploit := fmt.Sprintf("/tmp/exploit_%d", rand.Int())
  215. err = q.CopyFile("user", res.BuildArtifact, remoteExploit)
  216. if err != nil {
  217. return
  218. }
  219. res.Test.Output, err = testKernelExploit(q, ka, remoteTest,
  220. remoteExploit)
  221. if err != nil {
  222. log.Println(res.Test.Output)
  223. return
  224. }
  225. res.Run.Ok = true // does not really used
  226. res.Test.Ok = true
  227. default:
  228. log.Println("Unsupported artifact type")
  229. }
  230. return
  231. }
  232. func copyTest(q *qemu.System, testPath string, ka config.Artifact) (
  233. remoteTest string, err error) {
  234. remoteTest = fmt.Sprintf("/tmp/test_%d", rand.Int())
  235. err = q.CopyFile("user", testPath, remoteTest)
  236. if err != nil {
  237. if ka.Type == config.KernelExploit {
  238. q.Command("user",
  239. "echo -e '#!/bin/sh\necho touch $2 | $1' "+
  240. "> "+remoteTest+
  241. " && chmod +x "+remoteTest)
  242. } else {
  243. q.Command("user", "echo '#!/bin/sh' "+
  244. "> "+remoteTest+" && chmod +x "+remoteTest)
  245. }
  246. }
  247. _, err = q.Command("root", "chmod +x "+remoteTest)
  248. return
  249. }
  250. func whatever(swg *sizedwaitgroup.SizedWaitGroup, ka config.Artifact,
  251. ki config.KernelInfo, binaryPath, testPath string,
  252. qemuTimeout, dockerTimeout time.Duration, dist, tag string,
  253. db *sql.DB, verbose bool) {
  254. defer swg.Done()
  255. kernel := qemu.Kernel{KernelPath: ki.KernelPath, InitrdPath: ki.InitrdPath}
  256. q, err := qemu.NewSystem(qemu.X86x64, kernel, ki.RootFS)
  257. if err != nil {
  258. log.Println("Qemu creation error:", err)
  259. return
  260. }
  261. q.Timeout = qemuTimeout
  262. if ka.Qemu.Timeout.Duration != 0 {
  263. q.Timeout = ka.Qemu.Timeout.Duration
  264. }
  265. if ka.Qemu.Cpus != 0 {
  266. q.Cpus = ka.Qemu.Cpus
  267. }
  268. if ka.Qemu.Memory != 0 {
  269. q.Memory = ka.Qemu.Memory
  270. }
  271. q.SetKASLR(!ka.Mitigations.DisableKaslr)
  272. q.SetSMEP(!ka.Mitigations.DisableSmep)
  273. q.SetSMAP(!ka.Mitigations.DisableSmap)
  274. q.SetKPTI(!ka.Mitigations.DisableKpti)
  275. err = q.Start()
  276. if err != nil {
  277. log.Println("Qemu start error:", err)
  278. return
  279. }
  280. defer q.Stop()
  281. if verbose {
  282. go func() {
  283. for !q.Died {
  284. time.Sleep(time.Minute)
  285. log.Println(ka.Name, ki.DistroType,
  286. ki.DistroRelease, ki.KernelRelease,
  287. "still alive")
  288. }
  289. }()
  290. }
  291. usr, err := user.Current()
  292. if err != nil {
  293. return
  294. }
  295. tmpdir := usr.HomeDir + "/.out-of-tree/tmp"
  296. os.MkdirAll(tmpdir, os.ModePerm)
  297. tmp, err := ioutil.TempDir(tmpdir, "out-of-tree_")
  298. if err != nil {
  299. log.Println("Temporary directory creation error:", err)
  300. return
  301. }
  302. defer os.RemoveAll(tmp)
  303. result := phasesResult{}
  304. defer dumpResult(q, ka, ki, &result, dist, tag, binaryPath, db)
  305. if binaryPath == "" {
  306. result.BuildArtifact, result.Build.Output, err = build(tmp, ka,
  307. ki, dockerTimeout)
  308. if err != nil {
  309. log.Println(err)
  310. return
  311. }
  312. result.Build.Ok = true
  313. } else {
  314. result.BuildArtifact = binaryPath
  315. result.Build.Ok = true
  316. }
  317. if testPath == "" {
  318. testPath = result.BuildArtifact + "_test"
  319. if !exists(testPath) {
  320. testPath = tmp + "/" + "test.sh"
  321. }
  322. }
  323. remoteTest, err := copyTest(q, testPath, ka)
  324. if err != nil {
  325. return
  326. }
  327. copyArtifactAndTest(q, ka, &result, remoteTest)
  328. }
  329. func shuffleKernels(a []config.KernelInfo) []config.KernelInfo {
  330. // Fisher–Yates shuffle
  331. for i := len(a) - 1; i > 0; i-- {
  332. j := rand.Intn(i + 1)
  333. a[i], a[j] = a[j], a[i]
  334. }
  335. return a
  336. }
  337. func performCI(ka config.Artifact, kcfg config.KernelConfig, binaryPath,
  338. testPath string, stop time.Time,
  339. qemuTimeout, dockerTimeout time.Duration,
  340. max, runs int64, dist, tag string, threads int,
  341. db *sql.DB, verbose bool) (err error) {
  342. found := false
  343. swg := sizedwaitgroup.New(threads)
  344. for _, kernel := range shuffleKernels(kcfg.Kernels) {
  345. if max <= 0 {
  346. break
  347. }
  348. var supported bool
  349. supported, err = ka.Supported(kernel)
  350. if err != nil {
  351. return
  352. }
  353. if supported {
  354. found = true
  355. max--
  356. for i := int64(0); i < runs; i++ {
  357. if !stop.IsZero() && time.Now().After(stop) {
  358. break
  359. }
  360. swg.Add()
  361. go whatever(&swg, ka, kernel, binaryPath,
  362. testPath, qemuTimeout, dockerTimeout,
  363. dist, tag, db, verbose)
  364. }
  365. }
  366. }
  367. swg.Wait()
  368. if !found {
  369. err = errors.New("No supported kernels found")
  370. }
  371. return
  372. }
  373. func exists(path string) bool {
  374. if _, err := os.Stat(path); err != nil {
  375. return false
  376. }
  377. return true
  378. }
  379. func kernelMask(kernel string) (km config.KernelMask, err error) {
  380. parts := strings.Split(kernel, ":")
  381. if len(parts) != 2 {
  382. err = errors.New("Kernel is not 'distroType:regex'")
  383. return
  384. }
  385. dt, err := config.NewDistroType(parts[0])
  386. if err != nil {
  387. return
  388. }
  389. km = config.KernelMask{DistroType: dt, ReleaseMask: parts[1]}
  390. return
  391. }
  392. func genAllKernels() (sk []config.KernelMask, err error) {
  393. for _, dType := range config.DistroTypeStrings {
  394. var dt config.DistroType
  395. dt, err = config.NewDistroType(dType)
  396. if err != nil {
  397. return
  398. }
  399. sk = append(sk, config.KernelMask{
  400. DistroType: dt,
  401. ReleaseMask: ".*",
  402. })
  403. }
  404. return
  405. }
  406. // TODO: Now too many parameters, move all of them to some structure
  407. func pewHandler(kcfg config.KernelConfig,
  408. workPath, ovrrdKrnl, binary, test string, guess bool,
  409. stop time.Time, qemuTimeout, dockerTimeout time.Duration,
  410. max, runs int64, dist, tag string, threads int,
  411. db *sql.DB, verbose bool) (err error) {
  412. ka, err := config.ReadArtifactConfig(workPath + "/.out-of-tree.toml")
  413. if err != nil {
  414. return
  415. }
  416. if ka.SourcePath == "" {
  417. ka.SourcePath = workPath
  418. }
  419. if ovrrdKrnl != "" {
  420. var km config.KernelMask
  421. km, err = kernelMask(ovrrdKrnl)
  422. if err != nil {
  423. return
  424. }
  425. ka.SupportedKernels = []config.KernelMask{km}
  426. }
  427. if guess {
  428. ka.SupportedKernels, err = genAllKernels()
  429. if err != nil {
  430. return
  431. }
  432. }
  433. err = performCI(ka, kcfg, binary, test,
  434. stop, qemuTimeout, dockerTimeout,
  435. max, runs, dist, tag, threads, db, verbose)
  436. if err != nil {
  437. return
  438. }
  439. return
  440. }