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.

kernel.go 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  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"
  11. "math/rand"
  12. "os"
  13. "os/exec"
  14. "os/user"
  15. "regexp"
  16. "runtime"
  17. "strings"
  18. "time"
  19. "github.com/naoina/toml"
  20. "code.dumpstack.io/tools/out-of-tree/config"
  21. )
  22. const kernelsAll int64 = math.MaxInt64
  23. func kernelListHandler(kcfg config.KernelConfig) (err error) {
  24. if len(kcfg.Kernels) == 0 {
  25. return errors.New("No kernels found")
  26. }
  27. for _, k := range kcfg.Kernels {
  28. fmt.Println(k.DistroType, k.DistroRelease, k.KernelRelease)
  29. }
  30. return
  31. }
  32. func matchDebianHeadersPkg(container, mask string, generic bool) (
  33. pkgs []string, err error) {
  34. cmd := "apt-cache search linux-headers | cut -d ' ' -f 1"
  35. output, err := dockerRun(time.Minute, container, "/tmp", cmd)
  36. if err != nil {
  37. return
  38. }
  39. r, err := regexp.Compile("linux-headers-" + mask)
  40. if err != nil {
  41. return
  42. }
  43. kernels := r.FindAll([]byte(output), -1)
  44. for _, k := range kernels {
  45. pkg := string(k)
  46. if generic && !strings.HasSuffix(pkg, "generic") {
  47. continue
  48. }
  49. if pkg == "linux-headers-generic" {
  50. continue
  51. }
  52. pkgs = append(pkgs, pkg)
  53. }
  54. return
  55. }
  56. func matchCentOSDevelPkg(container, mask string, generic bool) (
  57. pkgs []string, err error) {
  58. cmd := "yum search kernel-devel --showduplicates | " +
  59. "grep '^kernel-devel' | cut -d ' ' -f 1"
  60. output, err := dockerRun(time.Minute, container, "/tmp", cmd)
  61. if err != nil {
  62. return
  63. }
  64. r, err := regexp.Compile("kernel-devel-" + mask)
  65. if err != nil {
  66. return
  67. }
  68. for _, k := range r.FindAll([]byte(output), -1) {
  69. pkgs = append(pkgs, string(k))
  70. }
  71. return
  72. }
  73. func dockerImagePath(sk config.KernelMask) (path string, err error) {
  74. usr, err := user.Current()
  75. if err != nil {
  76. return
  77. }
  78. path = usr.HomeDir + "/.out-of-tree/"
  79. path += sk.DistroType.String() + "/" + sk.DistroRelease
  80. return
  81. }
  82. func vsyscallAvailable() (available bool, err error) {
  83. if runtime.GOOS != "linux" {
  84. // Docker for non-Linux systems is not using the host
  85. // kernel but uses kernel inside a virtual machine, so
  86. // it builds by the Docker team with vsyscall support.
  87. available = true
  88. return
  89. }
  90. buf, err := ioutil.ReadFile("/proc/self/maps")
  91. if err != nil {
  92. return
  93. }
  94. available = strings.Contains(string(buf), "[vsyscall]")
  95. return
  96. }
  97. func generateBaseDockerImage(registry string, commands []config.DockerCommand,
  98. sk config.KernelMask) (err error) {
  99. imagePath, err := dockerImagePath(sk)
  100. if err != nil {
  101. return
  102. }
  103. dockerPath := imagePath + "/Dockerfile"
  104. d := "# BASE\n"
  105. if exists(dockerPath) {
  106. log.Printf("Base image for %s:%s found",
  107. sk.DistroType.String(), sk.DistroRelease)
  108. return
  109. }
  110. log.Printf("Base image for %s:%s not found, start generating",
  111. sk.DistroType.String(), sk.DistroRelease)
  112. os.MkdirAll(imagePath, os.ModePerm)
  113. d += "FROM "
  114. if registry != "" {
  115. d += registry + "/"
  116. }
  117. d += fmt.Sprintf("%s:%s\n",
  118. strings.ToLower(sk.DistroType.String()),
  119. sk.DistroRelease,
  120. )
  121. vsyscall, err := vsyscallAvailable()
  122. if err != nil {
  123. return
  124. }
  125. for _, c := range commands {
  126. switch c.DistroType {
  127. case config.Ubuntu:
  128. d += "RUN " + c.Command + "\n"
  129. case config.CentOS:
  130. d += "RUN " + c.Command + "\n"
  131. case config.Debian:
  132. d += "RUN " + c.Command + "\n"
  133. default:
  134. err = fmt.Errorf("%s not yet supported",
  135. sk.DistroType.String())
  136. return
  137. }
  138. }
  139. switch sk.DistroType {
  140. case config.Ubuntu:
  141. d += "ENV DEBIAN_FRONTEND=noninteractive\n"
  142. if sk.DistroRelease >= "16.04" {
  143. from := "http://.*ubuntu/"
  144. to := "mirror://mirrors.ubuntu.com/mirrors.txt"
  145. file := "/etc/apt/sources.list"
  146. s := fmt.Sprintf("sed -i 's;%s;%s;' %s", from, to, file)
  147. d += "RUN " + s + "\n"
  148. }
  149. d += "RUN apt-get update\n"
  150. d += "RUN apt-get install -y build-essential libelf-dev\n"
  151. d += "RUN apt-get install -y wget git\n"
  152. if sk.DistroRelease >= "14.04" {
  153. d += "RUN apt-get install -y libseccomp-dev\n"
  154. }
  155. d += "RUN mkdir -p /lib/modules\n"
  156. case config.CentOS:
  157. if sk.DistroRelease < "7" && !vsyscall {
  158. log.Println("Old CentOS requires `vsyscall=emulate` " +
  159. "on the latest kernels")
  160. log.Println("Check out `A note about vsyscall` " +
  161. "at https://hub.docker.com/_/centos")
  162. log.Println("See also https://lwn.net/Articles/446528/")
  163. err = fmt.Errorf("vsyscall is not available")
  164. return
  165. }
  166. // enable rpms from old minor releases
  167. d += "RUN sed -i 's/enabled=0/enabled=1/' /etc/yum.repos.d/CentOS-Vault.repo\n"
  168. // do not remove old kernels
  169. d += "RUN sed -i 's;installonly_limit=;installonly_limit=100500;' /etc/yum.conf\n"
  170. d += "RUN yum -y update\n"
  171. if sk.DistroRelease == "8" {
  172. // FIXME CentOS Vault repository list for 8 is empty
  173. // at the time of this fix; check for it and use a
  174. // workaround if it's still empty
  175. d += `RUN grep enabled /etc/yum.repos.d/CentOS-Vault.repo` +
  176. ` || echo -e '[8.0.1905]\nbaseurl=http://vault.centos.org/8.0.1905/BaseOS/$basearch/os/'` +
  177. ` >> /etc/yum.repos.d/CentOS-Vault.repo` + "\n"
  178. }
  179. d += "RUN yum -y groupinstall 'Development Tools'\n"
  180. if sk.DistroRelease < "8" {
  181. d += "RUN yum -y install deltarpm\n"
  182. } else {
  183. d += "RUN yum -y install drpm grub2-tools-minimal " +
  184. "elfutils-libelf-devel\n"
  185. }
  186. default:
  187. err = fmt.Errorf("%s not yet supported", sk.DistroType.String())
  188. return
  189. }
  190. d += "# END BASE\n\n"
  191. err = ioutil.WriteFile(dockerPath, []byte(d), 0644)
  192. if err != nil {
  193. return
  194. }
  195. cmd := exec.Command("docker", "build", "-t", sk.DockerName(), imagePath)
  196. rawOutput, err := cmd.CombinedOutput()
  197. if err != nil {
  198. log.Printf("Base image for %s:%s generating error, see log",
  199. sk.DistroType.String(), sk.DistroRelease)
  200. log.Println(string(rawOutput))
  201. return
  202. }
  203. log.Printf("Base image for %s:%s generating success",
  204. sk.DistroType.String(), sk.DistroRelease)
  205. return
  206. }
  207. func dockerImageAppend(sk config.KernelMask, pkgname string) (err error) {
  208. imagePath, err := dockerImagePath(sk)
  209. if err != nil {
  210. return
  211. }
  212. raw, err := ioutil.ReadFile(imagePath + "/Dockerfile")
  213. if err != nil {
  214. return
  215. }
  216. if strings.Contains(string(raw), pkgname) {
  217. // already installed kernel
  218. log.Printf("kernel %s for %s:%s is already exists",
  219. pkgname, sk.DistroType.String(), sk.DistroRelease)
  220. return
  221. }
  222. var s string
  223. switch sk.DistroType {
  224. case config.Ubuntu:
  225. imagepkg := strings.Replace(pkgname, "headers", "image", -1)
  226. log.Printf("Start adding kernel %s for %s:%s",
  227. imagepkg, sk.DistroType.String(), sk.DistroRelease)
  228. s = fmt.Sprintf("RUN apt-get install -y %s %s\n", imagepkg,
  229. pkgname)
  230. case config.CentOS:
  231. imagepkg := strings.Replace(pkgname, "-devel", "", -1)
  232. log.Printf("Start adding kernel %s for %s:%s",
  233. imagepkg, sk.DistroType.String(), sk.DistroRelease)
  234. version := strings.Replace(pkgname, "kernel-devel-", "", -1)
  235. s = fmt.Sprintf("RUN yum -y install %s %s\n", imagepkg,
  236. pkgname)
  237. s += fmt.Sprintf("RUN dracut --add-drivers 'e1000 ext4' -f "+
  238. "/boot/initramfs-%s.img %s\n", version, version)
  239. default:
  240. err = fmt.Errorf("%s not yet supported", sk.DistroType.String())
  241. return
  242. }
  243. err = ioutil.WriteFile(imagePath+"/Dockerfile",
  244. append(raw, []byte(s)...), 0644)
  245. if err != nil {
  246. return
  247. }
  248. cmd := exec.Command("docker", "build", "-t", sk.DockerName(), imagePath)
  249. rawOutput, err := cmd.CombinedOutput()
  250. if err != nil {
  251. // Fallback to previous state
  252. werr := ioutil.WriteFile(imagePath+"/Dockerfile", raw, 0644)
  253. if werr != nil {
  254. return
  255. }
  256. log.Printf("Add kernel %s for %s:%s error, see log",
  257. pkgname, sk.DistroType.String(), sk.DistroRelease)
  258. log.Println(string(rawOutput))
  259. return
  260. }
  261. log.Printf("Add kernel %s for %s:%s success",
  262. pkgname, sk.DistroType.String(), sk.DistroRelease)
  263. return
  264. }
  265. func kickImage(name string) (err error) {
  266. cmd := exec.Command("docker", "run", name, "bash", "-c", "ls")
  267. _, err = cmd.CombinedOutput()
  268. return
  269. }
  270. func copyKernels(name string) (err error) {
  271. cmd := exec.Command("docker", "ps", "-a")
  272. rawOutput, err := cmd.CombinedOutput()
  273. if err != nil {
  274. log.Println(string(rawOutput))
  275. return
  276. }
  277. r, err := regexp.Compile(".*" + name)
  278. if err != nil {
  279. return
  280. }
  281. var containerID string
  282. what := r.FindAll(rawOutput, -1)
  283. for _, w := range what {
  284. containerID = strings.Fields(string(w))[0]
  285. break
  286. }
  287. usr, err := user.Current()
  288. if err != nil {
  289. return
  290. }
  291. target := usr.HomeDir + "/.out-of-tree/kernels/"
  292. if !exists(target) {
  293. os.MkdirAll(target, os.ModePerm)
  294. }
  295. cmd = exec.Command("docker", "cp", containerID+":/boot/.", target)
  296. rawOutput, err = cmd.CombinedOutput()
  297. if err != nil {
  298. log.Println(string(rawOutput))
  299. return
  300. }
  301. return
  302. }
  303. func genKernelPath(files []os.FileInfo, kname string) string {
  304. for _, file := range files {
  305. if strings.HasPrefix(file.Name(), "vmlinuz") {
  306. if strings.Contains(file.Name(), kname) {
  307. return file.Name()
  308. }
  309. }
  310. }
  311. return "unknown"
  312. }
  313. func genInitrdPath(files []os.FileInfo, kname string) string {
  314. for _, file := range files {
  315. if strings.HasPrefix(file.Name(), "initrd") ||
  316. strings.HasPrefix(file.Name(), "initramfs") {
  317. if strings.Contains(file.Name(), kname) {
  318. return file.Name()
  319. }
  320. }
  321. }
  322. return "unknown"
  323. }
  324. func genRootfsImage(d dockerImageInfo, download bool) (rootfs string, err error) {
  325. usr, err := user.Current()
  326. if err != nil {
  327. return
  328. }
  329. imageFile := d.ContainerName + ".img"
  330. imagesPath := usr.HomeDir + "/.out-of-tree/images/"
  331. os.MkdirAll(imagesPath, os.ModePerm)
  332. rootfs = imagesPath + imageFile
  333. if !exists(rootfs) {
  334. if download {
  335. log.Println(imageFile, "not exists, start downloading...")
  336. err = downloadImage(imagesPath, imageFile)
  337. }
  338. }
  339. return
  340. }
  341. type dockerImageInfo struct {
  342. ContainerName string
  343. DistroType config.DistroType
  344. DistroRelease string // 18.04/7.4.1708/9.1
  345. }
  346. func listDockerImages() (diis []dockerImageInfo, err error) {
  347. cmd := exec.Command("docker", "images")
  348. rawOutput, err := cmd.CombinedOutput()
  349. if err != nil {
  350. return
  351. }
  352. r, err := regexp.Compile("out_of_tree_.*")
  353. if err != nil {
  354. return
  355. }
  356. containers := r.FindAll(rawOutput, -1)
  357. for _, c := range containers {
  358. container := strings.Fields(string(c))[0]
  359. s := strings.Replace(container, "__", ".", -1)
  360. values := strings.Split(s, "_")
  361. distro, ver := values[3], values[4]
  362. dii := dockerImageInfo{
  363. ContainerName: container,
  364. DistroRelease: ver,
  365. }
  366. dii.DistroType, err = config.NewDistroType(distro)
  367. if err != nil {
  368. return
  369. }
  370. diis = append(diis, dii)
  371. }
  372. return
  373. }
  374. func updateKernelsCfg(host, download bool) (err error) {
  375. newkcfg := config.KernelConfig{}
  376. if host {
  377. // Get host kernels
  378. newkcfg, err = genHostKernels(download)
  379. if err != nil {
  380. return
  381. }
  382. }
  383. // Get docker kernels
  384. dockerImages, err := listDockerImages()
  385. if err != nil {
  386. return
  387. }
  388. for _, d := range dockerImages {
  389. err = genDockerKernels(d, &newkcfg, download)
  390. if err != nil {
  391. log.Println("gen kernels", d.ContainerName, ":", err)
  392. continue
  393. }
  394. }
  395. stripkcfg := config.KernelConfig{}
  396. for _, nk := range newkcfg.Kernels {
  397. if !hasKernel(nk, stripkcfg) {
  398. stripkcfg.Kernels = append(stripkcfg.Kernels, nk)
  399. }
  400. }
  401. buf, err := toml.Marshal(&stripkcfg)
  402. if err != nil {
  403. return
  404. }
  405. buf = append([]byte("# Autogenerated\n# DO NOT EDIT\n\n"), buf...)
  406. usr, err := user.Current()
  407. if err != nil {
  408. return
  409. }
  410. // TODO move all cfg path values to one provider
  411. kernelsCfgPath := usr.HomeDir + "/.out-of-tree/kernels.toml"
  412. err = ioutil.WriteFile(kernelsCfgPath, buf, 0644)
  413. if err != nil {
  414. return
  415. }
  416. log.Println(kernelsCfgPath, "is successfully updated")
  417. return
  418. }
  419. func genDockerKernels(dii dockerImageInfo, newkcfg *config.KernelConfig,
  420. download bool) (err error) {
  421. name := dii.ContainerName
  422. cmd := exec.Command("docker", "run", name, "ls", "/lib/modules")
  423. rawOutput, err := cmd.CombinedOutput()
  424. if err != nil {
  425. log.Println(string(rawOutput), err)
  426. return
  427. }
  428. usr, err := user.Current()
  429. if err != nil {
  430. return
  431. }
  432. kernelsBase := usr.HomeDir + "/.out-of-tree/kernels/"
  433. files, err := ioutil.ReadDir(kernelsBase)
  434. if err != nil {
  435. return
  436. }
  437. rootfs, err := genRootfsImage(dii, download)
  438. if err != nil {
  439. return
  440. }
  441. for _, k := range strings.Fields(string(rawOutput)) {
  442. ki := config.KernelInfo{
  443. DistroType: dii.DistroType,
  444. DistroRelease: dii.DistroRelease,
  445. KernelRelease: k,
  446. ContainerName: name,
  447. KernelPath: kernelsBase + genKernelPath(files, k),
  448. InitrdPath: kernelsBase + genInitrdPath(files, k),
  449. RootFS: rootfs,
  450. }
  451. newkcfg.Kernels = append(newkcfg.Kernels, ki)
  452. }
  453. return
  454. }
  455. func hasKernel(ki config.KernelInfo, kcfg config.KernelConfig) bool {
  456. for _, sk := range kcfg.Kernels {
  457. if sk == ki {
  458. return true
  459. }
  460. }
  461. return false
  462. }
  463. func shuffle(a []string) []string {
  464. // Fisher–Yates shuffle
  465. for i := len(a) - 1; i > 0; i-- {
  466. j := rand.Intn(i + 1)
  467. a[i], a[j] = a[j], a[i]
  468. }
  469. return a
  470. }
  471. func generateKernels(km config.KernelMask, registry string,
  472. commands []config.DockerCommand, max int64,
  473. download bool) (err error) {
  474. log.Println("Generating for kernel mask", km)
  475. _, err = genRootfsImage(dockerImageInfo{ContainerName: km.DockerName()},
  476. download)
  477. if err != nil {
  478. return
  479. }
  480. err = generateBaseDockerImage(registry, commands, km)
  481. if err != nil {
  482. return
  483. }
  484. var pkgs []string
  485. switch km.DistroType {
  486. case config.Ubuntu:
  487. pkgs, err = matchDebianHeadersPkg(km.DockerName(),
  488. km.ReleaseMask, true)
  489. case config.CentOS:
  490. pkgs, err = matchCentOSDevelPkg(km.DockerName(),
  491. km.ReleaseMask, true)
  492. default:
  493. err = fmt.Errorf("%s not yet supported", km.DistroType.String())
  494. }
  495. if err != nil {
  496. return
  497. }
  498. for i, pkg := range shuffle(pkgs) {
  499. if max <= 0 {
  500. log.Println("Max is reached")
  501. break
  502. }
  503. log.Println(i, "/", len(pkgs), pkg)
  504. err = dockerImageAppend(km, pkg)
  505. if err == nil {
  506. max--
  507. } else {
  508. log.Println("dockerImageAppend", err)
  509. }
  510. }
  511. err = kickImage(km.DockerName())
  512. if err != nil {
  513. log.Println("kick image", km.DockerName(), ":", err)
  514. return
  515. }
  516. err = copyKernels(km.DockerName())
  517. if err != nil {
  518. log.Println("copy kernels", km.DockerName(), ":", err)
  519. return
  520. }
  521. return
  522. }
  523. func kernelAutogenHandler(workPath, registry string,
  524. commands []config.DockerCommand,
  525. max int64, host, download bool) (err error) {
  526. ka, err := config.ReadArtifactConfig(workPath + "/.out-of-tree.toml")
  527. if err != nil {
  528. return
  529. }
  530. for _, sk := range ka.SupportedKernels {
  531. if sk.DistroRelease == "" {
  532. err = errors.New("Please set distro_release")
  533. return
  534. }
  535. err = generateKernels(sk, registry, commands, max, download)
  536. if err != nil {
  537. return
  538. }
  539. }
  540. err = updateKernelsCfg(host, download)
  541. return
  542. }
  543. func kernelDockerRegenHandler(host, download bool) (err error) {
  544. dockerImages, err := listDockerImages()
  545. if err != nil {
  546. return
  547. }
  548. for _, d := range dockerImages {
  549. var imagePath string
  550. imagePath, err = dockerImagePath(config.KernelMask{
  551. DistroType: d.DistroType,
  552. DistroRelease: d.DistroRelease,
  553. })
  554. if err != nil {
  555. return
  556. }
  557. cmd := exec.Command("docker", "build", "-t",
  558. d.ContainerName, imagePath)
  559. var rawOutput []byte
  560. rawOutput, err = cmd.CombinedOutput()
  561. if err != nil {
  562. log.Println("docker build:", string(rawOutput))
  563. return
  564. }
  565. err = kickImage(d.ContainerName)
  566. if err != nil {
  567. log.Println("kick image", d.ContainerName, ":", err)
  568. continue
  569. }
  570. err = copyKernels(d.ContainerName)
  571. if err != nil {
  572. log.Println("copy kernels", d.ContainerName, ":", err)
  573. continue
  574. }
  575. }
  576. return updateKernelsCfg(host, download)
  577. }
  578. func kernelGenallHandler(distro, version, registry string,
  579. commands []config.DockerCommand, host, download bool) (err error) {
  580. distroType, err := config.NewDistroType(distro)
  581. if err != nil {
  582. return
  583. }
  584. km := config.KernelMask{
  585. DistroType: distroType,
  586. DistroRelease: version,
  587. ReleaseMask: ".*",
  588. }
  589. err = generateKernels(km, registry, commands, kernelsAll, download)
  590. if err != nil {
  591. return
  592. }
  593. return updateKernelsCfg(host, download)
  594. }