1
0

216 Commits

Author SHA1 Message Date
f9c2849658 Bump version 2023-04-07 13:55:58 +00:00
caba73cd7e Skip the kernel after the end of retries 2023-04-07 13:30:30 +00:00
5bb79302dd Bump version 2023-04-07 10:42:34 +00:00
4570e9adbe Handling discrepancies between /lib/modules and /boot 2023-04-07 10:27:59 +00:00
8029ad2185 Update readme 2023-04-07 00:48:38 +00:00
2f8446864a go mod tidy 2023-04-07 00:04:10 +00:00
dd602df291 Set go version to 1.17 2023-04-06 23:52:22 +00:00
c9d71601f2 Update readme 2023-04-06 23:32:02 +00:00
9863c93c02 Fix brew tap url 2023-04-06 23:18:47 +00:00
27a3cc498c Bump version 2023-04-06 22:48:54 +00:00
b75289a9d1 Update changelog 2023-04-06 22:46:46 +00:00
fd973c367f Add --noautoremove for CentOS 8 2023-04-06 22:37:15 +00:00
4bc4ca738b Typo 2023-04-06 21:57:47 +00:00
cd7cf0f2b6 Cache kernel package dependencies 2023-04-06 21:56:22 +00:00
87a5c389df Fix log level 2023-04-06 21:09:12 +00:00
be3f519573 Fix package cache 2023-04-06 21:08:51 +00:00
a5bfe334cb Install both headers and image 2023-04-06 20:53:59 +00:00
c0dd0ae07b Trim last added newline 2023-04-06 20:45:20 +00:00
a4c83c1637 Use different names for logs upload 2023-04-06 20:40:18 +00:00
897ac0699d Archive logs 2023-04-06 20:14:36 +00:00
5b444a3193 Use headers package 2023-04-06 20:10:07 +00:00
8aed31e41b Install a single kernel to ensure all dependencies are cached 2023-04-06 20:03:07 +00:00
f57b3408be Add stdout trace for qemu 2023-04-06 19:50:57 +00:00
483e56163e Revert "Add linux-image-generic to base container (for dependencies)"
This reverts commit 5931c08de1.
2023-04-06 19:40:44 +00:00
ac5f83349c Check that files exist before copy 2023-04-06 19:28:03 +00:00
5931c08de1 Add linux-image-generic to base container (for dependencies) 2023-04-06 19:07:58 +00:00
0d3a075d76 Add commands to manage containers 2023-04-06 19:05:41 +00:00
bbd6f79443 Add stdout trace for qemu ssh commands 2023-04-06 18:20:55 +00:00
5ce73d2fc5 Add stdout trace for sh commands 2023-04-06 18:14:24 +00:00
f65d4ad879 No need to have it at debug log level 2023-04-06 18:13:56 +00:00
7dddf71d93 Add stdout trace for container build 2023-04-06 18:12:56 +00:00
f75c70db94 Log container stdout at trace level 2023-04-06 18:00:46 +00:00
603e91af6f Write debug log to file 2023-04-06 17:32:54 +00:00
42dc8ac98c Revert "end-to-end testing for macOS"
This reverts commit 87ef1e42b5.
2023-04-06 15:01:04 +00:00
b7404aa453 Run end-to-end tests by non-root 2023-04-06 14:50:58 +00:00
bf455d9788 Copy from inside the container to avoid permission problems 2023-04-06 14:48:12 +00:00
a0ed1eb1f5 Improve logging 2023-04-06 14:32:05 +00:00
3220b9a5ae Additional arguments for containers 2023-04-06 14:30:42 +00:00
87ef1e42b5 end-to-end testing for macOS 2023-04-06 13:59:44 +00:00
17a4b746cc Proper CentOS 8 support 2023-04-06 13:38:33 +00:00
7314cc72db Remove genall test for Ubuntu 20.04 2023-04-06 13:21:04 +00:00
c353618c17 Fix log output 2023-04-06 13:12:57 +00:00
fe3092371c Switch to 22.04 2023-04-06 13:03:10 +00:00
ab7a70cc0a Try to install only 10 Ubuntu 20.04 kernels 2023-04-06 13:00:51 +00:00
0907129529 Add test genall with --no-headers 2023-04-06 12:59:35 +00:00
a874ac9fc7 Precise kernel version 2023-04-06 12:56:48 +00:00
23e933824b Add --no-headers tests, split jobs 2023-04-06 12:55:24 +00:00
80d7f9fb52 Add build info if run with debug 2023-04-06 12:50:44 +00:00
fad8502639 Fix wrong kernel version 2023-04-06 12:37:51 +00:00
5b468a4ec1 Keep genall tests only for Ubuntu 2023-04-06 12:36:06 +00:00
4a22df770b GitHub Actions: split jobs 2023-04-06 12:15:52 +00:00
88a3ff3869 GitHub Actions: fix path to binary 2023-04-06 12:12:28 +00:00
c5645f1985 GitHub Actions: add test for one kernel installation/reinstallation 2023-04-06 12:11:35 +00:00
bf421f80c8 GitHub Actions: use debug log level 2023-04-06 12:09:46 +00:00
055ea6b83d GitHub Actions: add genall tests 2023-04-06 11:50:47 +00:00
96c267d093 Run end-to-end tests by root 2023-04-05 19:50:59 +00:00
301eb2a60b Refactor 2023-04-05 19:32:31 +00:00
fcfbf4f36d Match also centos mask as string 2023-04-05 18:04:22 +00:00
b98abe4a83 Match also as string 2023-04-05 18:01:04 +00:00
72d51c0e1c Add timeout between retries 2023-04-05 16:40:20 +00:00
2d345c584b Do not retry on success 2023-04-05 16:36:09 +00:00
97fb543fef Typo 2023-04-05 16:26:26 +00:00
3fd2fd5966 Implements retry for failed kernel installations 2023-04-05 16:24:45 +00:00
29af467bee Install kernels to tmp directory first 2023-04-05 16:14:30 +00:00
604d21e4a2 Refactor deb package matcher 2023-04-05 12:31:46 +00:00
e44124c063 go mod tidy 2023-04-05 11:51:42 +00:00
fc0c76f114 Do not randomize kernels installation/test order by default 2023-04-05 11:29:31 +00:00
f399390c2c Ignore linux-image-generic 2023-04-05 10:52:40 +00:00
8d3986ce8e Refactor 2023-03-29 17:25:32 +00:00
3aba883b81 Add --no-headers flag 2023-03-29 17:15:27 +00:00
3329dc4c24 Add kernel config-regen command 2023-03-24 04:20:28 +00:00
34f3692d01 Fix timeout 2023-03-24 04:15:08 +00:00
1e66c156fa Run permission fix only once per container 2023-03-23 20:28:42 +00:00
2b54d13b9e Fix only kernels permissions 2023-03-23 20:26:30 +00:00
44494b65a6 Improve logging 2023-03-23 20:20:46 +00:00
a36d5ddb12 Increase timeout 2023-03-23 20:16:13 +00:00
488d2380e1 Do not reset old permissions 2023-03-23 20:05:29 +00:00
292e3dc211 Set permissions on the internals of all container volumes 2023-03-23 19:18:14 +00:00
ec1732c8ec Set kernels permissions inside container 2023-03-23 17:45:24 +00:00
bcdfb23112 Set debug log level for CI 2023-03-23 15:51:43 +00:00
d70150b496 Set kernel/initrd permissions 2023-03-22 21:16:00 +00:00
105809ddec Add script artifact type 2023-03-22 20:56:44 +00:00
5ece0e0f15 Add context for testing logs 2023-03-22 18:32:40 +00:00
2150162e8e Remove obsolete tests 2023-03-22 18:26:57 +00:00
7b16a439d8 Harmonise logging 2023-03-22 18:24:09 +00:00
7e050d9e99 Add command to install specific kernel 2023-03-22 18:21:21 +00:00
2c7341f0d8 Add force reinstall flag 2023-03-22 18:20:45 +00:00
b98dc87d54 Reduce verbosity 2023-03-22 18:08:48 +00:00
0f1bdc795d Fix check for installed kernels 2023-03-22 18:05:28 +00:00
3e9410bf09 Install kernels in mounted volume instead of dockerfile layers 2023-03-22 17:45:56 +00:00
0b198f71ca Show file:line only for debug log level 2023-03-22 17:36:04 +00:00
d6c678b0cd Do not use the default known hosts file 2023-03-19 18:00:10 +00:00
e2fcc20f36 Improve logging 2023-03-19 17:59:56 +00:00
60bc7238a8 Use the legacy SCP protocol for directory transfers instead of SFTP 2023-03-19 17:56:09 +00:00
04106e7537 VM commands debug logs 2023-03-19 13:30:10 +00:00
21d8bec382 Set default log level to info 2023-03-19 13:20:39 +00:00
c82bd6a554 Consider copy standard modules error as fatal 2023-03-19 13:20:29 +00:00
08beba2bab Add debug logs for exec.Command 2023-03-19 13:14:14 +00:00
305c6972ca Improve logging 2023-03-19 12:36:19 +00:00
78069c6240 More debug logging 2023-03-18 22:55:38 +00:00
992a0f871c More debug logging 2023-03-18 22:48:24 +00:00
3f16599109 Remove verbose flag 2023-03-18 22:39:24 +00:00
c2c3837f44 Set default log level to debug 2023-03-18 22:34:44 +00:00
f1f67e38ee Improve logging 2023-03-18 22:34:30 +00:00
ae20a6d11d Add log level parameter 2023-03-18 21:53:53 +00:00
8bffea0aea Switch to zerolog 2023-03-18 21:30:07 +00:00
feb1ab7d37 Revert "Use the legacy SCP protocol for directory transfers instead of SFTP"
This reverts commit cc1261b0b0.
2023-03-16 18:54:48 +00:00
12d5d43d7a Build with cgo 2023-03-16 18:46:46 +00:00
585a608083 go mod way to build 2023-03-16 09:52:34 +00:00
f10c4165a1 use docker run --rm ... 2023-03-16 09:42:07 +00:00
51e4cfec30 Print current tag at start 2023-03-16 09:41:49 +00:00
d5d9cce517 Suggest use of official docker installation guide 2023-03-16 09:41:31 +00:00
0e153b2763 debug: add parameters to set ssh addr/port 2023-02-28 19:11:28 +00:00
71f5530fed kernel genall: limit generated kernels to 100 2023-02-27 18:53:06 +00:00
870fe202b7 Treat test files as relative to build directory 2023-02-27 18:28:10 +00:00
b0587a4ade Add --artifact-config parameter to debug command 2023-02-23 08:26:54 +00:00
4fdcc5d098 Refactor 2023-02-23 08:25:19 +00:00
09feffb6a8 Treat test files as relative to build directory 2023-02-16 10:46:24 +00:00
2d6db97b43 Add support for applying patches 2023-02-16 10:22:08 +00:00
cc1261b0b0 Use the legacy SCP protocol for directory transfers instead of SFTP 2023-02-16 08:30:29 +00:00
24b6749504 Wait until ssh is available 2023-02-16 06:30:59 +00:00
f97cb3f10a Typo 2023-02-15 16:57:31 +00:00
b246ecf956 Support make targets 2023-02-15 16:54:46 +00:00
c9618be454 Store kernels in separate directories 2023-02-15 16:09:11 +00:00
f6b6b823a9 Use already defined path 2023-02-15 11:57:33 +00:00
3f79c8e461 Standard modules dependencies 2023-02-15 11:48:25 +00:00
3d6961dfd7 Treat any SCP output as an error 2023-02-15 11:45:25 +00:00
9910921e30 Cleanup symbolic links from modules dir 2023-02-15 11:38:02 +00:00
d59049e531 qemu: add CopyDirectory (via scp) 2023-02-15 11:20:30 +00:00
668bc1e391 Store the default kernel modules 2023-02-15 11:00:00 +00:00
3ec919abc1 Image editing command 2023-02-15 10:17:57 +00:00
0529b30558 qemu: allow to run mutable 2023-02-15 09:48:13 +00:00
063df192b4 mirror:// is slower and less reliable in the end 2023-02-14 05:30:28 +00:00
1a952e0212 Set the default maximum number of kernels to be downloaded to 100
The overlayfs driver natively supports up to 128 layers
2023-02-13 22:10:23 +00:00
8b5ce9923b Revert podman layer squashing, does not work well with caching 2023-02-11 15:02:21 +00:00
b1493b79a3 podman --squash-all requires --layers to cache intermediate layers 2023-02-11 14:47:43 +00:00
fb5b2a2bbb Case insensitive 2023-02-11 14:30:37 +00:00
a9db750ea5 Fix case where both docker and podman are installed, but docker is not podman alias 2023-02-11 08:52:49 +00:00
55032f07af Squash podman layers, not docker layers 2023-02-11 08:33:20 +00:00
bb7c2f94d5 podman: squash all layers into a single layer 2023-02-11 08:28:26 +00:00
422f05d25b Use squash only if podman is used 2023-02-11 08:22:37 +00:00
3c8e80cace docker/podman: squash newly built layers into a single new layer 2023-02-11 08:08:26 +00:00
a0ee660e50 Add artifact config flag 2023-02-01 08:04:27 +00:00
82436cbd83 Keep help lowercase 2023-01-31 09:57:53 +00:00
ce8f8d3a38 Typo 2023-01-31 09:56:49 +00:00
330da3b930 Reliability threshold flag 2023-01-31 09:34:23 +00:00
ce7794ce84 Add missing flags, refactor 2023-01-31 09:05:43 +00:00
abd8e69186 Update changelog 2023-01-31 07:21:53 +00:00
2f52f6db6d Refactor command line interface 2023-01-31 07:13:33 +00:00
935266c850 Add alecthomas/kong support for config 2023-01-30 20:42:22 +00:00
a7b619fc40 lint 2023-01-30 20:15:17 +00:00
0e185ab36b Check for local docker image 2023-01-30 11:15:12 +00:00
b8bb11943a Fix default test.sh path 2023-01-29 22:27:35 +00:00
2bc55e2011 lint 2023-01-29 22:27:24 +00:00
6e1216201e podman compatibility 2023-01-29 20:42:16 +00:00
92706c68fb go mod tidy 2023-01-24 15:41:57 +00:00
49ee65de76 Fix package name 2023-01-24 15:32:04 +00:00
8fca9dbd2e Ignore binary 2023-01-24 15:12:26 +00:00
1deb201e25 Switch to Ubuntu 22.04 for testing 2023-01-19 17:22:25 +00:00
cc26ff8626 Add shell.nix 2023-01-19 16:59:25 +00:00
05ae073fe6 Use upstream lkrg in the module template 2022-02-16 20:45:55 +00:00
603a2c98bd Use upstream lkrg 2021-01-05 20:52:27 +00:00
cfee4c565c Remove donate 2020-12-16 16:46:25 +00:00
02663fad64 Run tests on pull request 2020-09-15 11:05:53 +00:00
e43993c6e5 adding TestFiles to artifact config, transfers extra test files to VM 2020-09-15 12:57:11 +02:00
90829e2409 Follow redirect 2020-06-18 15:22:35 +00:00
514e2c9c91 Update donate.yml 2020-06-18 13:33:50 +00:00
5b0bf7de01 Bump version 2020-06-14 21:03:12 +00:00
992c41c84b Show last log if no ID specified 2020-06-14 20:46:56 +00:00
22a8e32e2c Implements modules preload list 2020-06-14 20:14:59 +00:00
2f5f1db0db Add docker timeout to artifact configuration 2020-06-14 17:32:57 +00:00
551ec7f7ef Update readme 2020-05-30 14:37:41 +00:00
8a53b6081c Update changelog 2020-05-30 14:26:12 +00:00
27d8291bb2 Workaround for CentOS 8 Vault repos 2020-05-30 14:13:03 +00:00
db5d31d563 CentOS 8 image generator 2020-05-30 13:42:47 +00:00
d27fbf6671 No kernels in boot if there is no grub 2020-05-30 13:42:04 +00:00
cf79a9f94f Yum in CentOS 8 does not support --show{-}duplicates with a dash 2020-05-30 12:48:24 +00:00
bfc6f11a7e Fix deltarpm support for CentOS 8 2020-05-30 12:40:12 +00:00
bfae451749 Fix kernel module name for unit tests 2020-05-30 12:31:27 +00:00
9b8d4a056e Fix path to vmlinuz/initrd 2020-05-30 12:05:24 +00:00
81234fc3a6 Update bootstrap scripts to Ubuntu 20.04 2020-05-30 11:37:00 +00:00
81db5a6d6a Update go.mod 2020-05-30 10:43:12 +00:00
5bb7e08188 Sync with the latest logrusorgru/aurora
Fixes #15
2020-05-19 14:48:53 +00:00
dce1ce6c17 Make go mod tidy 2020-05-19 14:48:53 +00:00
1c2ea77920 GitHub Actions: Use latest stable nixpkgs channel 2020-02-21 00:32:02 +00:00
f92b4e6640 Add dashboard access token 2020-01-20 09:27:06 +00:00
db72ff0aea Donations 2020-01-16 23:58:19 +00:00
a6b81a3a24 GitHub Actions: better build job names 2020-01-05 07:21:48 +00:00
f93f4e7072 Remove Travis-CI 2020-01-05 06:49:37 +00:00
70168afa4a Add note about docker group 2019-12-28 08:50:10 +00:00
26a724096e Remove build status badge (status is already showed in UI) 2019-12-28 01:17:24 +00:00
0a332c670a Remove CircleCI because it does not support macOS on free plan 2019-12-28 01:07:00 +00:00
196f17277c CircleCI: specify xcode version 2019-12-28 01:00:28 +00:00
7f418b30ac Add circleci configuration for macOS 2019-12-28 00:54:14 +00:00
2494c94f6e Move build from source to documentation 2019-12-27 08:38:47 +00:00
27ffff2d05 Actualize title 2019-12-27 08:33:45 +00:00
eafe9e57a8 Revert "Link for documentation directly to the introduction"
This reverts commit 7e5126c042.
2019-12-27 08:30:44 +00:00
7e5126c042 Link for documentation directly to the introduction 2019-12-27 08:29:07 +00:00
81219be062 Update README.md 2019-12-27 08:25:55 +00:00
434aeb768b Add commands for install Docker 2019-12-27 08:18:45 +00:00
bd27e890d1 Add timeout after start qemu for tests 2019-12-27 07:52:26 +00:00
873b35a18d Note about docker 2019-12-27 07:16:53 +00:00
fc2ee93b57 Add installation section 2019-12-27 07:12:09 +00:00
e03dff8409 Should return if error occured 2019-12-26 13:16:38 +00:00
f4a8b75244 GitHub Actions: split jobs, add end-to-end testing 2019-12-26 06:47:37 +00:00
c1a3cb6ce5 Bump version 2019-12-25 14:40:13 +00:00
d58226c22c Do not check for (host) vsyscall support on the non-Linux systems
Fixes #19
2019-12-25 14:38:05 +00:00
9e1d71d1b2 Bump changelog version 2019-11-15 07:51:14 +00:00
9c70af4f6f Add flag for verbose output 2019-11-14 15:38:16 +00:00
7b8cf96b4a Kpti settings was not affected for regular runs 2019-11-14 15:37:34 +00:00
7b6e3a9ad6 Avoid slow mirrors 2019-09-05 18:27:23 +00:00
b117739c49 Add policykit-1 to rootfs 2019-08-31 12:45:55 +00:00
b28c47e64d Fix link to Travis-CI 2019-08-31 10:56:45 +00:00
4b14187dad GitHub Actions: convert case 2019-08-31 10:53:36 +00:00
950b1e5e83 Add Ubuntu build for GitHub Actions 2019-08-31 09:28:36 +00:00
bf90a10692 Add macOS build for GitHub Actions 2019-08-31 08:28:38 +00:00
40 changed files with 2785 additions and 1279 deletions

13
.github/workflows/macos.yml vendored Normal file
View File

@ -0,0 +1,13 @@
name: macOS
on: [push, pull_request]
jobs:
build:
name: Build on macOS
runs-on: macOS-latest
steps:
- uses: actions/checkout@v1
- name: Build
run: go build

130
.github/workflows/ubuntu.yml vendored Normal file
View File

@ -0,0 +1,130 @@
name: Ubuntu
on: [push, pull_request]
jobs:
build:
name: Build on Ubuntu
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Build
run: go build
test-unit:
name: Unit Testing
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Install dependencies for tests
run: |
sudo apt-get update
sudo apt-get install qemu-system-x86
- name: Bootstrap
run: ./tools/qemu-debian-img/bootstrap.sh
- name: Unit Testing
run: go test -parallel 1 -v ./...
test-end-to-end:
name: End-to-End Testing
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Build
run: go build
- name: Install dependencies for tests
run: |
sudo apt-get update
sudo apt-get install qemu-system-x86
- name: End-to-End Testing [Kernel Module]
run: |
cd examples/kernel-module
../../out-of-tree --log-level=debug kernel autogen --max=1
../../out-of-tree --log-level=debug pew --qemu-timeout=10m
- name: End-to-End Testing [Kernel Exploit]
run: |
cd examples/kernel-exploit
../../out-of-tree --log-level=debug kernel autogen --max=1
../../out-of-tree --log-level=debug pew --threshold=0 --qemu-timeout=10m
- name: Archive logs
uses: actions/upload-artifact@v3
with:
name: test-end-to-end-logs
path: /home/runner/.out-of-tree/logs/out-of-tree.log
test-end-to-end-kernels:
name: End-to-End Testing (kernels)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Build
run: go build
- name: Install dependencies for tests
run: |
sudo apt-get update
sudo apt-get install qemu-system-x86
- name: End-to-End Testing [Install one Ubuntu 18.04 kernel]
run: |
./out-of-tree --log-level=debug kernel install --distro=Ubuntu --ver=18.04 --kernel=4.15.0-70-generic
- name: End-to-End Testing [Reinstall one Ubuntu 18.04 kernel]
run: |
./out-of-tree --log-level=debug kernel install --distro=Ubuntu --ver=18.04 --kernel=4.15.0-70-generic --force
- name: End-to-End Testing [Install one Ubuntu 22.04 kernel w/o headers]
run: |
./out-of-tree --log-level=debug kernel install --distro=Ubuntu --ver=22.04 --kernel=5.19.0-28-generic --no-headers
- name: End-to-End Testing [Install one CentOS 7 kernel]
run: |
./out-of-tree --log-level=debug kernel install --distro=CentOS --ver=7 --kernel=3.10.0-862.6.3
- name: End-to-End Testing [Install one CentOS 7 kernel w/o headers]
run: |
./out-of-tree --log-level=debug kernel install --distro=CentOS --ver=7 --kernel=3.10.0-1160.71.1 --no-headers
- name: End-to-End Testing [Install one CentOS 8 kernel]
run: |
./out-of-tree --log-level=debug kernel install --distro=CentOS --ver=8 --kernel=4.18.0-348.7.1
- name: Archive logs
uses: actions/upload-artifact@v3
with:
name: test-end-to-end-kernels-log
path: /home/runner/.out-of-tree/logs/out-of-tree.log
test-end-to-end-genall:
name: End-to-End Testing (genall)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Build
run: go build
- name: Install dependencies for tests
run: |
sudo apt-get update
sudo apt-get install qemu-system-x86
- name: End-to-End Testing [Install all Ubuntu 22.04 kernels]
run: |
./out-of-tree --log-level=debug kernel genall --distro=Ubuntu --ver=22.04
- name: Archive logs
uses: actions/upload-artifact@v3
with:
name: test-end-to-end-genall-logs
path: /home/runner/.out-of-tree/logs/out-of-tree.log

2
.gitignore vendored
View File

@ -10,3 +10,5 @@
# Output of the go coverage tool, specifically when used with LiteIDE # Output of the go coverage tool, specifically when used with LiteIDE
*.out *.out
out-of-tree

View File

@ -1,30 +0,0 @@
language: go
go:
- 1.x
- master
os:
- linux
dist:
- bionic
addons:
apt:
packages:
- qemu
services:
- docker
env:
- GO111MODULE=on
install: true
before_script:
- ./tools/qemu-debian-img/bootstrap.sh
script:
- go test -parallel 1 -v ./...

View File

@ -4,6 +4,109 @@
[Semantic Versioning](https://semver.org/spec/v2.0.0.html). [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.0.0]
### Breaking
- Layers with kernels in containers have been abandoned in favor of
installation to mounted volumes.
- Command line interface has been changed to alecthomas/kong.
### Added
- Command `kernel install` to install specific kernel.
- Command `containers` to manage containers.
- Command `image edit` to edit qemu image.
- Flag `--force` to force reinstallation of the kernel.
- Flag `--artifact-config` to specify the path to .out-of-tree.toml.
- Flag `--no-headers` flag to install kernel and initrd only.
- Flag `--shuffle` to randomize the order of kernels for
installation/testing.
- Support make targets in artifact config.
- Support patches in artifact config.
- Support for copying standard modules to qemu.
- Script artifact type for various automation and information gathering.
- Add TestFiles to artifact config, transfers additional test files to VM.
- Improved logging, with logfile at ~/.out-of-tree/logs/out-of-tree.log
- Kernel installation will retry (10 times by default) in case of
network problems.
- Stdout trace (with --log-level=trace, and always to logfile) for
qemu and container execution.
- Compatibility with Podman.
- Support for Ubuntu 22.04.
## [1.4.0]
### Added
- Parameter `--docker-timeout` may also be set in the artifact
configuration file.
- Preload modules before inserting module or run exploit. Modules can
be specified by git repository path in the `repo` parameter of
section `[[preload]]`. Also, there is a `path` parameter for local
projects. Note that `repo` is using a cache that uses last commit
hash to check is project needs to be rebuilt, so it's not suitable
for local development (except if you will commit each time before
run out-of-tree).
- Flag `--disable-preload` to ignore `[[preload]]` section of
configuration file.
- Now `out-of-tree log dump` will show the last log if no ID
specified.
## [1.3.0] 2020-05-30
### Added
- Support for Ubuntu 20.04 and CentOS 8.
## [1.2.1] 2019-12-25
### Fixed
- macOS support.
## [1.2.0] 2019-11-15
### Added
- Flag for Verbose output. Right now only qemu status messages is
implemented.
### Fixed
- Kpti settings was not affected for regular runs.
## [1.1.2] 2019-09-05
### Added
- Added policykit-1 to rootfs for Ubuntu.
### Fixed
- Avoided slow mirrors with use of mirror://mirrors.ubuntu.com for
Ubuntu 16.04 and newer.
## [1.1.1] 2019-08-31 ## [1.1.1] 2019-08-31
### Fixed ### Fixed
@ -128,7 +231,7 @@
- Temporary files is moved to `~/.out-of-tree/tmp/` to avoid docker - Temporary files is moved to `~/.out-of-tree/tmp/` to avoid docker
mounting issues on some systems. mounting issues on some systems.
## [0.2.0] - 2019-12-01 ## [0.2.0] - 2018-12-01
The main purpose of the release is to simplify installation. The main purpose of the release is to simplify installation.
@ -150,7 +253,7 @@ The main purpose of the release is to simplify installation.
- No warning anymore if test.sh is not exists. - No warning anymore if test.sh is not exists.
## [0.1.0] - 2019-11-20 ## [0.1.0] - 2018-11-20
Initial release that was never tagged. Initial release that was never tagged.

View File

@ -1,9 +1,6 @@
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/aba4aad2046b4d1a9a99cf98e22c018b)](https://app.codacy.com/app/jollheef/out-of-tree?utm_source=github.com&utm_medium=referral&utm_content=jollheef/out-of-tree&utm_campaign=Badge_Grade_Dashboard) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/aba4aad2046b4d1a9a99cf98e22c018b)](https://app.codacy.com/app/jollheef/out-of-tree?utm_source=github.com&utm_medium=referral&utm_content=jollheef/out-of-tree&utm_campaign=Badge_Grade_Dashboard)
[![Build Status](https://travis-ci.com/jollheef/out-of-tree.svg?branch=master)](https://travis-ci.org/jollheef/out-of-tree)
[![Go Report Card](https://goreportcard.com/badge/code.dumpstack.io/tools/out-of-tree)](https://goreportcard.com/report/code.dumpstack.io/tools/out-of-tree) [![Go Report Card](https://goreportcard.com/badge/code.dumpstack.io/tools/out-of-tree)](https://goreportcard.com/report/code.dumpstack.io/tools/out-of-tree)
[![Documentation Status](https://readthedocs.org/projects/out-of-tree/badge/?version=latest)](https://out-of-tree.readthedocs.io/en/latest/?badge=latest) [![Documentation Status](https://readthedocs.org/projects/out-of-tree/badge/?version=latest)](https://out-of-tree.readthedocs.io/en/latest/?badge=latest)
[![Donate](https://img.shields.io/badge/donate-paypal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=R8W2UQPZ5X5JE&source=url)
[![Donate](https://img.shields.io/badge/donate-bitcoin-green.svg)](https://blockchair.com/bitcoin/address/bc1q23fyuq7kmngrgqgp6yq9hk8a5q460f39m8nv87)
# [out-of-tree](https://out-of-tree.io) # [out-of-tree](https://out-of-tree.io)
@ -13,88 +10,40 @@ out-of-tree is for automating some routine actions for creating development envi
![Screenshot](https://cloudflare-ipfs.com/ipfs/Qmb88fgdDjbWkxz91sWsgmoZZNfVThnCtj37u3mF2s3T3T) ![Screenshot](https://cloudflare-ipfs.com/ipfs/Qmb88fgdDjbWkxz91sWsgmoZZNfVThnCtj37u3mF2s3T3T)
## Requirements ## Installation
[Qemu](https://www.qemu.org), [docker](https://docker.com) and [golang](https://golang.org) is required. ### GNU/Linux (with [Nix](https://nixos.org/nix/))
Also do not forget to set GOPATH and PATH e.g.: $ curl -fsSL https://get.docker.com | sh
$ sudo usermod -aG docker user && newgrp docker
$ curl -L https://nixos.org/nix/install | sh
$ nix-env -iA nixpkgs.out-of-tree # Note: may not be up to date immediately, in this case consider installing from source
$ echo 'export GOPATH=$HOME' >> ~/.bashrc Note that adding a user to group *docker* has serious security implications. Check Docker documentation for more information.
$ echo 'export PATH=$PATH:$HOME/bin' >> ~/.bashrc
$ source ~/.bashrc
### Gentoo
# emerge app-emulation/qemu app-emulation/docker dev-lang/go
### macOS ### macOS
$ brew install go qemu
$ brew cask install docker $ brew cask install docker
$ open --background -a Docker && sleep 1m
$ brew tap out-of-tree/repo
$ brew install out-of-tree
### Fedora Read [documentation](https://out-of-tree.readthedocs.io) for further info.
$ sudo dnf install go qemu moby-engine
Also check out [docker post-installation steps](https://docs.docker.com/install/linux/linux-postinstall/).
## Build from source
$ go get -u code.dumpstack.io/tools/out-of-tree
Then you can check it on kernel module example:
$ cd $GOPATH/src/code.dumpstack.io/tools/out-of-tree/examples/kernel-module
$ out-of-tree kernel autogen # generate kernels based on .out-of-tree.toml
$ out-of-tree pew
## Examples ## Examples
Run by absolute path Generate all Ubuntu 22.04 kernels:
$ out-of-tree --path /path/to/exploit/directory pew $ out-of-tree kernel genall --distro=Ubuntu --ver=22.04
Test only with one kernel: Run tests based on .out-of-tree.toml definitions:
$ out-of-tree pew --kernel='Ubuntu:4.10.0-30-generic' $ out-of-tree pew
Test with a specific kernel:
$ out-of-tree pew --kernel='Ubuntu:5.4.0-29-generic'
Run debug environment: Run debug environment:
$ out-of-tree debug --kernel='Ubuntu:4.10.0-30-generic' $ out-of-tree debug --kernel='Ubuntu:5.4.0-29-generic'
Test binary module/exploit with implicit defined test ($BINARY_test)
$ out-of-tree pew --binary /path/to/exploit
Test binary module/exploit with explicit defined test
$ out-of-tree pew --binary /path/to/exploit --test /path/to/exploit_test
Guess work kernels:
$ out-of-tree pew --guess
Use custom kernels config
$ out-of-tree --kernels /path/to/kernels.toml pew
Generate all kernels
$ out-of-tree kernel genall --distro Ubuntu --ver 16.04
## Troubleshooting
If anything happens that you cannot solve -- just remove `$HOME/.out-of-tree`.
But it'll be better if you'll write the bug report.
## Development
Read [Qemu API](qemu/README.md).
### Generate images
$ cd $GOPATH/src/code.dumpstack.io/tools/out-of-tree/tools/qemu-debian-img/
$ docker run --privileged -v $(pwd):/shared -e IMAGE=/shared/ubuntu1404.img -e RELEASE=trusty -t gen-ubuntu1804-image
$ docker run --privileged -v $(pwd):/shared -e IMAGE=/shared/ubuntu1604.img -e RELEASE=xenial -t gen-ubuntu1804-image

View File

@ -49,10 +49,12 @@ const (
KernelModule ArtifactType = iota KernelModule ArtifactType = iota
// KernelExploit is the privilege escalation exploit // KernelExploit is the privilege escalation exploit
KernelExploit KernelExploit
// Script for information gathering or automation
Script
) )
func (at ArtifactType) String() string { func (at ArtifactType) String() string {
return [...]string{"module", "exploit"}[at] return [...]string{"module", "exploit", "script"}[at]
} }
// UnmarshalTOML is for support github.com/naoina/toml // UnmarshalTOML is for support github.com/naoina/toml
@ -63,6 +65,8 @@ func (at *ArtifactType) UnmarshalTOML(data []byte) (err error) {
*at = KernelModule *at = KernelModule
} else if strings.Contains(stypelower, "exploit") { } else if strings.Contains(stypelower, "exploit") {
*at = KernelExploit *at = KernelExploit
} else if strings.Contains(stypelower, "script") {
*at = Script
} else { } else {
err = fmt.Errorf("Type %s is unsupported", stype) err = fmt.Errorf("Type %s is unsupported", stype)
} }
@ -77,6 +81,8 @@ func (at ArtifactType) MarshalTOML() (data []byte, err error) {
s = "module" s = "module"
case KernelExploit: case KernelExploit:
s = "exploit" s = "exploit"
case Script:
s = "script"
default: default:
err = fmt.Errorf("Cannot marshal %d", at) err = fmt.Errorf("Cannot marshal %d", at)
} }
@ -102,25 +108,61 @@ func (d Duration) MarshalTOML() (data []byte, err error) {
return return
} }
type PreloadModule struct {
Repo string
Path string
TimeoutAfterLoad Duration
}
// Extra test files to copy over
type FileTransfer struct {
User string
Local string
Remote string
}
type Patch struct {
Path string
Source string
Script string
}
// Artifact is for .out-of-tree.toml // Artifact is for .out-of-tree.toml
type Artifact struct { type Artifact struct {
Name string Name string
Type ArtifactType Type ArtifactType
TestFiles []FileTransfer
SourcePath string SourcePath string
SupportedKernels []KernelMask SupportedKernels []KernelMask
Script string
Qemu struct { Qemu struct {
Cpus int Cpus int
Memory int Memory int
Timeout Duration Timeout Duration
} }
Docker struct {
Timeout Duration
}
Mitigations struct { Mitigations struct {
DisableSmep bool DisableSmep bool
DisableSmap bool DisableSmap bool
DisableKaslr bool DisableKaslr bool
DisableKpti bool DisableKpti bool
} }
Patches []Patch
Make struct {
Target string
}
StandardModules bool
Preload []PreloadModule
} }
func (ka Artifact) checkSupport(ki KernelInfo, km KernelMask) ( func (ka Artifact) checkSupport(ki KernelInfo, km KernelMask) (
@ -230,9 +272,11 @@ type KernelInfo struct {
ContainerName string ContainerName string
// Runtime information // Runtime information
KernelPath string KernelPath string
InitrdPath string InitrdPath string
RootFS string ModulesPath string
RootFS string
// Debug symbols // Debug symbols
VmlinuxPath string VmlinuxPath string

View File

@ -5,8 +5,13 @@
package config package config
import ( import (
"errors"
"os"
"os/user" "os/user"
"time"
"github.com/alecthomas/kong"
"github.com/mitchellh/go-homedir"
"github.com/naoina/toml" "github.com/naoina/toml"
) )
@ -22,11 +27,11 @@ type OutOfTree struct {
Database string Database string
Qemu struct { Qemu struct {
Timeout string Timeout Duration
} }
Docker struct { Docker struct {
Timeout string Timeout Duration
Registry string Registry string
// Commands that will be executed before // Commands that will be executed before
@ -35,6 +40,30 @@ type OutOfTree struct {
} }
} }
func (c *OutOfTree) Decode(ctx *kong.DecodeContext) (err error) {
if ctx.Value.Set {
return
}
s, err := homedir.Expand(ctx.Scan.Pop().String())
if err != nil {
return
}
defaultValue, err := homedir.Expand(ctx.Value.Default)
if err != nil {
return
}
_, err = os.Stat(s)
if s != defaultValue && errors.Is(err, os.ErrNotExist) {
return errors.New("'" + s + "' does not exist")
}
*c, err = ReadOutOfTreeConf(s)
return
}
func ReadOutOfTreeConf(path string) (c OutOfTree, err error) { func ReadOutOfTreeConf(path string) (c OutOfTree, err error) {
buf, err := readFileAll(path) buf, err := readFileAll(path)
if err == nil { if err == nil {
@ -65,12 +94,12 @@ func ReadOutOfTreeConf(path string) (c OutOfTree, err error) {
c.Database = usr.HomeDir + "/.out-of-tree/db.sqlite" c.Database = usr.HomeDir + "/.out-of-tree/db.sqlite"
} }
if c.Qemu.Timeout == "" { if c.Qemu.Timeout.Duration == 0 {
c.Qemu.Timeout = "1m" c.Qemu.Timeout.Duration = time.Minute
} }
if c.Docker.Timeout == "" { if c.Docker.Timeout.Duration == 0 {
c.Docker.Timeout = "1m" c.Docker.Timeout.Duration = time.Minute
} }
return return

237
container.go Normal file
View File

@ -0,0 +1,237 @@
// 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.
package main
import (
"bufio"
"errors"
"fmt"
"os"
"os/exec"
"os/user"
"regexp"
"strings"
"time"
"github.com/rs/zerolog/log"
"code.dumpstack.io/tools/out-of-tree/config"
)
type ContainerCmd struct {
Filter string `help:"filter by name"`
List ContainerListCmd `cmd:"" help:"list containers"`
Cleanup ContainerCleanupCmd `cmd:"" help:"cleanup containers"`
}
func (cmd ContainerCmd) Containers() (names []string) {
images, err := listContainerImages()
if err != nil {
log.Fatal().Err(err).Msg("")
}
for _, img := range images {
if cmd.Filter != "" && !strings.Contains(img.Name, cmd.Filter) {
continue
}
names = append(names, img.Name)
}
return
}
type ContainerListCmd struct{}
func (cmd ContainerListCmd) Run(containerCmd *ContainerCmd) (err error) {
for _, name := range containerCmd.Containers() {
fmt.Println(name)
}
return
}
type ContainerCleanupCmd struct{}
func (cmd ContainerCleanupCmd) Run(containerCmd *ContainerCmd) (err error) {
var output []byte
for _, name := range containerCmd.Containers() {
output, err = exec.Command("docker", "image", "rm", name).CombinedOutput()
if err != nil {
log.Error().Err(err).Str("output", string(output)).Msg("")
return
}
}
return
}
type containerImageInfo struct {
Name string
DistroType config.DistroType
DistroRelease string // 18.04/7.4.1708/9.1
}
func listContainerImages() (diis []containerImageInfo, err error) {
cmd := exec.Command("docker", "images")
log.Debug().Msgf("%v", cmd)
rawOutput, err := cmd.CombinedOutput()
if err != nil {
return
}
r, err := regexp.Compile("out_of_tree_.*")
if err != nil {
return
}
containers := r.FindAll(rawOutput, -1)
for _, c := range containers {
container := strings.Fields(string(c))[0]
s := strings.Replace(container, "__", ".", -1)
values := strings.Split(s, "_")
distro, ver := values[3], values[4]
dii := containerImageInfo{
Name: container,
DistroRelease: ver,
}
dii.DistroType, err = config.NewDistroType(distro)
if err != nil {
return
}
diis = append(diis, dii)
}
return
}
type container struct {
name string
timeout time.Duration
Volumes struct {
LibModules string
UsrSrc string
Boot string
}
// Additional arguments
Args []string
}
func NewContainer(name string, timeout time.Duration) (c container, err error) {
c.name = name
c.timeout = timeout
usr, err := user.Current()
if err != nil {
return
}
c.Volumes.LibModules = fmt.Sprintf(
"%s/.out-of-tree/volumes/%s/lib/modules", usr.HomeDir, name)
os.MkdirAll(c.Volumes.LibModules, 0777)
c.Volumes.UsrSrc = fmt.Sprintf(
"%s/.out-of-tree/volumes/%s/usr/src", usr.HomeDir, name)
os.MkdirAll(c.Volumes.UsrSrc, 0777)
c.Volumes.Boot = fmt.Sprintf(
"%s/.out-of-tree/volumes/%s/boot", usr.HomeDir, name)
os.MkdirAll(c.Volumes.Boot, 0777)
return
}
func (c container) Build(imagePath string) (output string, err error) {
args := []string{"build"}
args = append(args, "-t", c.name, imagePath)
cmd := exec.Command("docker", args...)
flog := log.With().
Str("command", fmt.Sprintf("%v", cmd)).
Logger()
stdout, err := cmd.StdoutPipe()
if err != nil {
return
}
cmd.Stderr = cmd.Stdout
err = cmd.Start()
if err != nil {
return
}
go func() {
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
m := scanner.Text()
output += m + "\n"
flog.Trace().Str("stdout", m).Msg("")
}
}()
err = cmd.Wait()
return
}
func (c container) Run(workdir string, command string) (output string, err error) {
flog := log.With().
Str("container", c.name).
Str("workdir", workdir).
Str("command", command).
Logger()
var args []string
args = append(args, "run", "--rm")
args = append(args, c.Args...)
args = append(args,
"-v", workdir+":/work",
"-v", c.Volumes.LibModules+":/lib/modules",
"-v", c.Volumes.UsrSrc+":/usr/src",
"-v", c.Volumes.Boot+":/boot")
args = append(args, c.name, "bash", "-c", "cd /work && "+command)
cmd := exec.Command("docker", args...)
log.Debug().Msgf("%v", cmd)
stdout, err := cmd.StdoutPipe()
if err != nil {
return
}
cmd.Stderr = cmd.Stdout
timer := time.AfterFunc(c.timeout, func() {
flog.Info().Msg("killing container by timeout")
cmd.Process.Kill()
})
defer timer.Stop()
err = cmd.Start()
if err != nil {
return
}
go func() {
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
m := scanner.Text()
output += m + "\n"
flog.Trace().Str("stdout", m).Msg("")
}
}()
err = cmd.Wait()
if err != nil {
e := fmt.Sprintf("error `%v` for cmd `%v` with output `%v`",
err, command, output)
err = errors.New(e)
return
}
return
}

18
db.go
View File

@ -254,6 +254,24 @@ func getLogByID(db *sql.DB, id int) (le logEntry, err error) {
return return
} }
func getLastLog(db *sql.DB) (le logEntry, err error) {
err = db.QueryRow("SELECT MAX(id), time, name, type, tag, "+
"distro_type, distro_release, kernel_release, "+
"build_ok, run_ok, test_ok, "+
"build_output, run_output, test_output, "+
"qemu_stdout, qemu_stderr, "+
"kernel_panic, timeout_kill "+
"FROM log").Scan(&le.ID, &le.Timestamp,
&le.Name, &le.Type, &le.Tag,
&le.DistroType, &le.DistroRelease, &le.KernelRelease,
&le.Build.Ok, &le.Run.Ok, &le.Test.Ok,
&le.Build.Output, &le.Run.Output, &le.Test.Output,
&le.Stdout, &le.Stderr,
&le.KernelPanic, &le.KilledByTimeout,
)
return
}
func createSchema(db *sql.DB) (err error) { func createSchema(db *sql.DB) (err error) {
err = createMetadataTable(db) err = createMetadataTable(db)
if err != nil { if err != nil {

333
debug.go
View File

@ -8,17 +8,217 @@ import (
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"strings" "strings"
"time" "time"
"gopkg.in/logrusorgru/aurora.v1" "github.com/rs/zerolog/log"
"gopkg.in/logrusorgru/aurora.v2"
"code.dumpstack.io/tools/out-of-tree/config" "code.dumpstack.io/tools/out-of-tree/config"
"code.dumpstack.io/tools/out-of-tree/qemu" "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"`
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.Print(err)
}
var configPath string
if cmd.ArtifactConfig == "" {
configPath = g.WorkDir + "/.out-of-tree.toml"
} else {
configPath = cmd.ArtifactConfig
}
ka, err := config.ReadArtifactConfig(configPath)
if err != nil {
return
}
if ka.SourcePath == "" {
ka.SourcePath = g.WorkDir
}
ki, err := firstSupported(kcfg, ka, cmd.Kernel)
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 := ioutil.TempDir("/tmp/", "out-of-tree_")
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 = copyStandardModules(q, ki)
if err != nil {
log.Print(err)
return
}
}
err = preloadModules(q, ka, ki, g.Config.Docker.Timeout.Duration)
if err != nil {
log.Print(err)
return
}
var buildDir, outFile, output, remoteFile string
if ka.Type == config.Script {
err = q.CopyFile("root", ka.Script, ka.Script)
if err != nil {
return
}
} else {
buildDir, outFile, output, err = build(tmp, ka, ki, g.Config.Docker.Timeout.Duration)
if err != nil {
log.Print(err, output)
return
}
remoteFile = "/tmp/exploit"
if ka.Type == config.KernelModule {
remoteFile = "/tmp/module.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] != '/' {
f.Local = buildDir + "/" + f.Local
}
err = q.CopyFile(f.User, f.Local, f.Remote)
if err != nil {
log.Print("error copy err:", err, 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 config.Artifact, func firstSupported(kcfg config.KernelConfig, ka config.Artifact,
kernel string) (ki config.KernelInfo, err error) { kernel string) (ki config.KernelInfo, err error) {
@ -57,12 +257,12 @@ func handleLine(q *qemu.System) (err error) {
fmt.Printf("ssh\t: print arguments to ssh command\n") fmt.Printf("ssh\t: print arguments to ssh command\n")
fmt.Printf("quit\t: quit\n") fmt.Printf("quit\t: quit\n")
case "l", "log": case "l", "log":
fmt.Println(string(q.Stdout)) fmt.Println(q.Stdout)
case "cl", "clog": case "cl", "clog":
fmt.Println(string(q.Stdout)) fmt.Println(q.Stdout)
q.Stdout = []byte{} q.Stdout = ""
case "c", "cleanup": case "c", "cleanup":
q.Stdout = []byte{} q.Stdout = ""
case "s", "ssh": case "s", "ssh":
fmt.Println(q.GetSSHCommand()) fmt.Println(q.GetSSHCommand())
case "q", "quit": case "q", "quit":
@ -81,124 +281,3 @@ func interactive(q *qemu.System) (err error) {
} }
} }
} }
func debugHandler(kcfg config.KernelConfig, workPath, kernRegex, gdb string,
dockerTimeout time.Duration, yekaslr, yesmep, yesmap, yekpti,
nokaslr, nosmep, nosmap, nokpti bool) (err error) {
ka, err := config.ReadArtifactConfig(workPath + "/.out-of-tree.toml")
if err != nil {
return
}
if ka.SourcePath == "" {
ka.SourcePath = workPath
}
ki, err := firstSupported(kcfg, ka, kernRegex)
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
}
if ka.Qemu.Cpus != 0 {
q.Cpus = ka.Qemu.Cpus
}
if ka.Qemu.Memory != 0 {
q.Memory = ka.Qemu.Memory
}
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 yekaslr {
q.SetKASLR(true)
} else if nokaslr {
q.SetKASLR(false)
}
if yesmep {
q.SetSMEP(true)
} else if nosmep {
q.SetSMEP(false)
}
if yesmap {
q.SetSMAP(true)
} else if nosmap {
q.SetSMAP(false)
}
if yekpti {
q.SetKPTI(true)
} else if nokpti {
q.SetKPTI(false)
}
redgreen := func(name string, enabled bool) aurora.Value {
if enabled {
return aurora.BgGreen(aurora.Black(name))
}
return aurora.BgRed(aurora.Gray(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(gdb)
coloredGdbAddress := aurora.BgGreen(aurora.Black(gdb))
fmt.Printf("[*] gdb is listening on %s\n", coloredGdbAddress)
err = q.Start()
if err != nil {
return
}
defer q.Stop()
tmp, err := ioutil.TempDir("/tmp/", "out-of-tree_")
if err != nil {
return
}
defer os.RemoveAll(tmp)
outFile, output, err := build(tmp, ka, ki, dockerTimeout)
if err != nil {
log.Println(err, output)
return
}
remoteFile := "/tmp/exploit"
if ka.Type == config.KernelModule {
remoteFile = "/tmp/module.ko"
}
err = q.CopyFile("user", outFile, remoteFile)
if err != nil {
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, gdb)
// TODO set substitute-path /build/.../linux-... /path/to/linux-source
err = interactive(q)
return
}

View File

@ -1,4 +1,4 @@
Installation Installation (from source)
============ ============
OS/Distro-specific OS/Distro-specific
@ -10,7 +10,7 @@ Ubuntu
Install dependencies:: Install dependencies::
$ sudo snap install go --classic $ sudo snap install go --classic
$ sudo snap install docker $ # Install docker: https://docs.docker.com/engine/install/ubuntu/
$ sudo apt install qemu-system-x86 build-essential gdb $ sudo apt install qemu-system-x86 build-essential gdb
macOS macOS
@ -36,18 +36,33 @@ There's a minimal configuration that you need to apply::
]; ];
} }
Gentoo
------
Install dependencies::
$ sudo emerge app-emulation/qemu app-emulation/docker dev-lang/go
Fedora
------
Install dependencies::
$ sudo dnf install go qemu moby-engine
Common Common
====== ======
Setup Go environment:: Setup environment::
$ echo 'export GOPATH=$HOME' >> ~/.bashrc
$ echo 'export PATH=$PATH:$HOME/bin' >> ~/.bashrc $ echo 'export PATH=$PATH:$HOME/bin' >> ~/.bashrc
$ source ~/.bashrc $ source ~/.bashrc
Build *out-of-tree*:: Build *out-of-tree*::
$ go get -u code.dumpstack.io/tools/out-of-tree $ git clone https://code.dumpstack.io/tools/out-of-tree
$ cd out-of-tree
$ CGO_ENABLED=1 go build -o ~/bin/out-of-tree
.. note:: .. note::
On a GNU/Linux you need to add your user to docker group if you want On a GNU/Linux you need to add your user to docker group if you want
@ -57,7 +72,7 @@ Build *out-of-tree*::
Test that everything works:: Test that everything works::
$ cd $GOPATH/src/code.dumpstack.io/tools/out-of-tree/examples/kernel-exploit $ cd out-of-tree/examples/kernel-exploit
$ out-of-tree kernel autogen --max=1 $ out-of-tree kernel autogen --max=1
$ out-of-tree pew --max=1 $ out-of-tree pew --max=1

View File

@ -0,0 +1,12 @@
name = "out-of-tree preload"
type = "module"
[[supported_kernels]]
distro_type = "Ubuntu"
distro_release = "18.04"
release_mask = ".*"
[[preload]]
repo = "https://github.com/openwall/lkrg"
#path = "/local/path/to/lkrg"
timeout_after_load = "1s"

11
examples/preload/Makefile Normal file
View File

@ -0,0 +1,11 @@
KERNEL := /lib/modules/$(shell uname -r)/build
TARGET := module
obj-m += $(TARGET).o
$(TARGET)-objs = module.o
all:
make -C $(KERNEL) M=$(PWD) modules
clean:
make -C $(KERNEL) M=$(PWD) clean

View File

@ -0,0 +1,5 @@
# out-of-tree kernel module preload example
See .out-of-tree.toml
Note that it should fail to insert module if lkrg is enabled in the preload list.

17
examples/preload/module.c Normal file
View File

@ -0,0 +1,17 @@
#include <linux/module.h>
#include <linux/slab.h>
int init_module(void)
{
char *argv[] = { "/bin/sh", "--help", NULL };
char *envp[] = { NULL };
/* trigger lkrg */
return call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC);
}
void cleanup_module(void)
{
}
MODULE_LICENSE("GPL");

View File

@ -0,0 +1,11 @@
# out-of-tree configuration file
# docs at https://out-of-tree.io
name = "out-of-tree script example"
type = "script"
script = "script.sh"
[[supported_kernels]]
distro_type = "Ubuntu"
distro_release = "22.04"
release_mask = ".*"

View File

@ -0,0 +1,3 @@
# out-of-tree script example
See .out-of-tree.toml

View File

@ -0,0 +1,5 @@
#!/bin/sh
uname -a
ls /proc | grep config

20
gen.go
View File

@ -12,6 +12,20 @@ import (
"code.dumpstack.io/tools/out-of-tree/config" "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) { func genConfig(at config.ArtifactType) (err error) {
a := config.Artifact{ a := config.Artifact{
Name: "Put name here", Name: "Put name here",
@ -22,6 +36,12 @@ func genConfig(at config.ArtifactType) (err error) {
DistroRelease: "18.04", DistroRelease: "18.04",
ReleaseMask: ".*", ReleaseMask: ".*",
}) })
a.Preload = append(a.Preload, config.PreloadModule{
Repo: "Repo name (e.g. https://github.com/openwall/lkrg)",
})
a.Patches = append(a.Patches, config.Patch{
Path: "/path/to/profiling.patch",
})
buf, err := toml.Marshal(&a) buf, err := toml.Marshal(&a)
if err != nil { if err != nil {

53
go.mod
View File

@ -1,21 +1,50 @@
module code.dumpstack.io/tools/out-of-tree module code.dumpstack.io/tools/out-of-tree
go 1.17
replace code.dumpstack.io/tools/out-of-tree/qemu => ./qemu replace code.dumpstack.io/tools/out-of-tree/qemu => ./qemu
replace code.dumpstack.io/tools/out-of-tree/config => ./config replace code.dumpstack.io/tools/out-of-tree/config => ./config
require ( require (
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect github.com/alecthomas/kong v0.7.1
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect github.com/go-git/go-git/v5 v5.6.1
github.com/mattn/go-runewidth v0.0.4 // indirect github.com/mattn/go-sqlite3 v1.14.16
github.com/mattn/go-sqlite3 v1.11.0 github.com/mitchellh/go-homedir v1.1.0
github.com/naoina/go-stringutil v0.1.0 // indirect
github.com/naoina/toml v0.1.1 github.com/naoina/toml v0.1.1
github.com/olekukonko/tablewriter v0.0.1 github.com/natefinch/lumberjack v2.0.0+incompatible
github.com/otiai10/copy v1.0.1 github.com/olekukonko/tablewriter v0.0.5
github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce github.com/otiai10/copy v1.10.0
github.com/zcalusic/sysinfo v0.0.0-20190429151633-fbadb57345c2 github.com/remeh/sizedwaitgroup v1.0.0
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 github.com/rs/zerolog v1.29.0
gopkg.in/alecthomas/kingpin.v2 v2.2.6 github.com/zcalusic/sysinfo v0.9.5
gopkg.in/logrusorgru/aurora.v1 v1.0.0-20181002194514-a7b3b318ed4e golang.org/x/crypto v0.7.0
gopkg.in/logrusorgru/aurora.v2 v2.0.3
)
require (
github.com/BurntSushi/toml v1.2.1 // indirect
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect
github.com/acomagu/bufpipe v1.0.4 // indirect
github.com/cloudflare/circl v1.1.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/go-git/gcfg v1.5.0 // indirect
github.com/go-git/go-billy/v5 v5.4.1 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/naoina/go-stringutil v0.1.0 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/sergi/go-diff v1.1.0 // indirect
github.com/skeema/knownhosts v1.1.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/sys v0.6.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
) )

217
go.sum
View File

@ -1,33 +1,198 @@
bou.ke/monkey v1.0.1/go.mod h1:FgHuK96Rv2Nlf+0u1OOVDpCMdsWyOFmeeketDHE7LIg= github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ=
github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/alecthomas/assert/v2 v2.1.0 h1:tbredtNcQnoSd3QBhQWI7QZ3XHOVkw1Moklp2ojoH/0=
github.com/alecthomas/assert/v2 v2.1.0/go.mod h1:b/+1DI2Q6NckYi+3mXyH3wFb8qG37K/DuK80n7WefXA=
github.com/alecthomas/kong v0.7.1 h1:azoTh0IOfwlAX3qN9sHWTxACE2oV8Bg2gAwBsMwDQY4=
github.com/alecthomas/kong v0.7.1/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U=
github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
github.com/alecthomas/repr v0.1.0/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY=
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4=
github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg=
github.com/go-git/go-git-fixtures/v4 v4.3.1 h1:y5z6dd3qi8Hl+stezc8p3JxDkoTRqMAlKnXHuzrfjTQ=
github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo=
github.com/go-git/go-git/v5 v5.6.1 h1:q4ZRqQl4pR/ZJHc1L5CFjGA1a10u76aV1iC+nh+bHsk=
github.com/go-git/go-git/v5 v5.6.1/go.mod h1:mvyoL6Unz0PiTQrGQfSfiLFhBH1c1e84ylC2MDs4ee8=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mmcloughlin/avo v0.5.0/go.mod h1:ChHFdoV7ql95Wi7vuq2YT1bwCJqiWdZrQ1im3VujLYM=
github.com/naoina/go-stringutil v0.1.0 h1:rCUeRUHjBjGTSHl0VC00jUPLz8/F9dDzYI70Hzifhks= github.com/naoina/go-stringutil v0.1.0 h1:rCUeRUHjBjGTSHl0VC00jUPLz8/F9dDzYI70Hzifhks=
github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0=
github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8= github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8=
github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88= github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM=
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk=
github.com/otiai10/copy v1.0.1 h1:gtBjD8aq4nychvRZ2CyJvFWAw0aja+VHazDdruZKGZA= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/otiai10/copy v1.0.1/go.mod h1:8bMCJrAqOtN/d9oyh5HR7HhLQMvcGMpGdwRDYsfOCHc= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/otiai10/mint v1.2.3/go.mod h1:YnfyPNhBvnY8bW4SGQHCs/aAFhkgySlMZbrF5U0bOVw= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce h1:aP+C+YbHZfOQlutA4p4soHi7rVUqHQdWEVMSkHfDTqY= github.com/otiai10/copy v1.10.0 h1:znyI7l134wNg/wDktoVQPxPkgvhDfGCYUasey+h0rDQ=
github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo= github.com/otiai10/copy v1.10.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww=
github.com/zcalusic/sysinfo v0.0.0-20190429151633-fbadb57345c2 h1:uMiaKNX5zFLOa6nNtun+d/lpV5bOBh7BvE4q9jfZacQ= github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
github.com/zcalusic/sysinfo v0.0.0-20190429151633-fbadb57345c2/go.mod h1:zAn3FAIbgZPYnutDND49Ivf8sb/mXYk8UjZdqMswgHg= github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM=
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remeh/sizedwaitgroup v1.0.0 h1:VNGGFwNo/R5+MJBf6yrsr110p0m4/OX4S3DCy7Kyl5E=
github.com/remeh/sizedwaitgroup v1.0.0/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w=
github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ysW0=
github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zcalusic/sysinfo v0.9.5 h1:ivoHyj9aIAYkwzo1+8QgJ5s4oeE6Etx9FmZtqa4wJjQ=
github.com/zcalusic/sysinfo v0.9.5/go.mod h1:Z/gPVufBrFc8X5sef3m6kkw3r3nlNFp+I6bvASfvBZQ=
golang.org/x/arch v0.1.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
gopkg.in/logrusorgru/aurora.v1 v1.0.0-20181002194514-a7b3b318ed4e h1:uKdf1KQDFZDYqNzSDhxB5hFxj5Fq4e3/C/ejtRJxlY0= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
gopkg.in/logrusorgru/aurora.v1 v1.0.0-20181002194514-a7b3b318ed4e/go.mod h1:DGR33jeYG1jxERD2W4hGjuW94Pxf3mkUf/Ddhf5BskA= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/logrusorgru/aurora.v2 v2.0.3 h1:5Hr76hqgwx9PednedYf5Q1dBfiPMZ2IgExR7u3tNXIE=
gopkg.in/logrusorgru/aurora.v2 v2.0.3/go.mod h1:Wm+IEn1fgFp8E2paL93oFVrHZW4toMKARNE85fDY5w8=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View File

@ -5,14 +5,108 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
"os/user"
"time"
"code.dumpstack.io/tools/out-of-tree/config"
"code.dumpstack.io/tools/out-of-tree/qemu"
"github.com/rs/zerolog/log"
) )
type ImageCmd struct {
List ImageListCmd `cmd:"" help:"list images"`
Edit ImageEditCmd `cmd:"" help:"edit image"`
}
type ImageListCmd struct{}
func (cmd *ImageListCmd) Run(g *Globals) (err error) {
usr, err := user.Current()
if err != nil {
return
}
entries, err := os.ReadDir(usr.HomeDir + "/.out-of-tree/images/")
if err != nil {
return
}
for _, e := range entries {
fmt.Println(e.Name())
}
return
}
type ImageEditCmd struct {
Name string `help:"image name" required:""`
}
func (cmd *ImageEditCmd) Run(g *Globals) (err error) {
usr, err := user.Current()
if err != nil {
return
}
image := usr.HomeDir + "/.out-of-tree/images/" + cmd.Name
if !exists(image) {
fmt.Println("image does not exist")
}
kcfg, err := config.ReadKernelConfig(g.Config.Kernels)
if err != nil {
return
}
if len(kcfg.Kernels) == 0 {
return errors.New("No kernels found")
}
ki := config.KernelInfo{}
for _, k := range kcfg.Kernels {
if k.RootFS == image {
ki = k
break
}
}
kernel := qemu.Kernel{
KernelPath: ki.KernelPath,
InitrdPath: ki.InitrdPath,
}
q, err := qemu.NewSystem(qemu.X86x64, kernel, ki.RootFS)
q.Mutable = true
err = q.Start()
if err != nil {
fmt.Println("Qemu start error:", err)
return
}
defer q.Stop()
fmt.Print("ssh command:\n\n\t")
fmt.Println(q.GetSSHCommand())
fmt.Print("\npress enter to stop")
fmt.Scanln()
q.Command("root", "poweroff")
for !q.Died {
time.Sleep(time.Second)
}
return
}
// inspired by Edd Turtle code // inspired by Edd Turtle code
func downloadFile(filepath string, url string) (err error) { func downloadFile(filepath string, url string) (err error) {
out, err := os.Create(filepath) out, err := os.Create(filepath)
@ -53,6 +147,8 @@ func unpackTar(archive, destination string) (err error) {
cmd := exec.Command("tar", "-Sxf", archive) cmd := exec.Command("tar", "-Sxf", archive)
cmd.Dir = destination + "/" cmd.Dir = destination + "/"
log.Debug().Msgf("%v", cmd)
rawOutput, err := cmd.CombinedOutput() rawOutput, err := cmd.CombinedOutput()
if err != nil { if err != nil {
err = fmt.Errorf("%v: %s", err, rawOutput) err = fmt.Errorf("%v: %s", err, rawOutput)

792
kernel.go

File diff suppressed because it is too large Load Diff

View File

@ -1,19 +1,21 @@
// 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 // Use of this source code is governed by a AGPLv3 license
// (or later) that can be found in the LICENSE file. // (or later) that can be found in the LICENSE file.
//go:build linux
// +build linux // +build linux
package main package main
import ( import (
"io/ioutil" "io/ioutil"
"log"
"os/exec" "os/exec"
"strings" "strings"
"code.dumpstack.io/tools/out-of-tree/config" "github.com/rs/zerolog/log"
"github.com/zcalusic/sysinfo" "github.com/zcalusic/sysinfo"
"code.dumpstack.io/tools/out-of-tree/config"
) )
func genHostKernels(download bool) (kcfg config.KernelConfig, err error) { func genHostKernels(download bool) (kcfg config.KernelConfig, err error) {
@ -26,21 +28,23 @@ func genHostKernels(download bool) (kcfg config.KernelConfig, err error) {
} }
cmd := exec.Command("ls", "/lib/modules") cmd := exec.Command("ls", "/lib/modules")
log.Debug().Msgf("%v", cmd)
rawOutput, err := cmd.CombinedOutput() rawOutput, err := cmd.CombinedOutput()
if err != nil { if err != nil {
log.Println(string(rawOutput), err) log.Print(string(rawOutput), err)
return return
} }
kernelsBase := "/boot/" kernelsBase := "/boot/"
files, err := ioutil.ReadDir(kernelsBase) bootfiles, err := ioutil.ReadDir(kernelsBase)
if err != nil { if err != nil {
return return
} }
// only for compatibility, docker is not really used // only for compatibility, docker is not really used
dii := dockerImageInfo{ dii := containerImageInfo{
ContainerName: config.KernelMask{ Name: config.KernelMask{
DistroType: distroType, DistroType: distroType,
DistroRelease: si.OS.Version, DistroRelease: si.OS.Version,
}.DockerName(), }.DockerName(),
@ -51,21 +55,36 @@ func genHostKernels(download bool) (kcfg config.KernelConfig, err error) {
return return
} }
for _, k := range strings.Fields(string(rawOutput)) { for _, krel := range strings.Fields(string(rawOutput)) {
log.Debug().Msgf("generate config entry for %s", krel)
var kernelFile, initrdFile string
kernelFile, err = findKernelFile(bootfiles, krel)
if err != nil {
log.Warn().Msgf("cannot find kernel %s", krel)
continue
}
initrdFile, err = findInitrdFile(bootfiles, krel)
if err != nil {
log.Warn().Msgf("cannot find initrd %s", krel)
continue
}
ki := config.KernelInfo{ ki := config.KernelInfo{
DistroType: distroType, DistroType: distroType,
DistroRelease: si.OS.Version, DistroRelease: si.OS.Version,
KernelRelease: k, KernelRelease: krel,
KernelSource: "/lib/modules/" + k + "/build", KernelSource: "/lib/modules/" + krel + "/build",
KernelPath: kernelsBase + genKernelPath(files, k), KernelPath: kernelsBase + kernelFile,
InitrdPath: kernelsBase + genInitrdPath(files, k), InitrdPath: kernelsBase + initrdFile,
RootFS: rootfs, RootFS: rootfs,
} }
vmlinux := "/usr/lib/debug/boot/vmlinux-" + k vmlinux := "/usr/lib/debug/boot/vmlinux-" + krel
log.Println("vmlinux", vmlinux) log.Print("vmlinux", vmlinux)
if exists(vmlinux) { if exists(vmlinux) {
ki.VmlinuxPath = vmlinux ki.VmlinuxPath = vmlinux
} }

226
log.go
View File

@ -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 // Use of this source code is governed by a AGPLv3 license
// (or later) that can be found in the LICENSE file. // (or later) that can be found in the LICENSE file.
@ -8,66 +8,51 @@ import (
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"fmt" "fmt"
"log"
"math" "math"
"os" "os"
"github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter"
"gopkg.in/logrusorgru/aurora.v1" "github.com/rs/zerolog/log"
"gopkg.in/logrusorgru/aurora.v2"
"code.dumpstack.io/tools/out-of-tree/config" "code.dumpstack.io/tools/out-of-tree/config"
) )
func logLogEntry(l logEntry) { type LogCmd struct {
distroInfo := fmt.Sprintf("%s-%s {%s}", l.DistroType, Query LogQueryCmd `cmd:"" help:"query logs"`
l.DistroRelease, l.KernelRelease) Dump LogDumpCmd `cmd:"" help:"show all info for log entry with ID"`
Json LogJsonCmd `cmd:"" help:"generate json statistics"`
artifactInfo := fmt.Sprintf("{[%s] %s}", l.Type, l.Name) Markdown LogMarkdownCmd `cmd:"" help:"generate markdown statistics"`
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)
}
} }
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 {
panic(err)
}
defer db.Close()
var les []logEntry 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 { if kaErr == nil {
log.Println(".out-of-tree.toml found, filter by artifact name") log.Print(".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 { } else {
les, err = getAllLogs(db, tag, num) les, err = getAllLogs(db, cmd.Tag, cmd.Num)
} }
if err != nil { if err != nil {
return return
} }
s := "\nS" s := "\nS"
if rate { if cmd.Rate {
if kaErr != nil { if kaErr != nil {
err = kaErr err = kaErr
return 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) 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 { if err != nil {
return return
} }
@ -99,8 +84,23 @@ func logHandler(db *sql.DB, path, tag string, num int, rate bool) (err error) {
return return
} }
func logDumpHandler(db *sql.DB, id int) (err error) { type LogDumpCmd struct {
l, err := getLogByID(db, id) ID int `help:"id" default:"-1"`
}
func (cmd *LogDumpCmd) Run(g *Globals) (err error) {
db, err := openDatabase(g.Config.Database)
if err != nil {
panic(err)
}
defer db.Close()
var l logEntry
if cmd.ID > 0 {
l, err = getLogByID(db, cmd.ID)
} else {
l, err = getLastLog(db)
}
if err != nil { if err != nil {
return return
} }
@ -145,6 +145,102 @@ func logDumpHandler(db *sql.DB, id int) (err error) {
return 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 {
panic(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 {
panic(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 { type runstat struct {
All, BuildOK, RunOK, TestOK, Timeout, Panic int All, BuildOK, RunOK, TestOK, Timeout, Panic int
} }
@ -201,45 +297,3 @@ func getStats(db *sql.DB, path, tag string) (
return 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
}

412
main.go
View File

@ -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 // Use of this source code is governed by a AGPLv3 license
// (or later) that can be found in the LICENSE file. // (or later) that can be found in the LICENSE file.
@ -6,344 +6,148 @@ package main
import ( import (
"fmt" "fmt"
"log" "io"
"math/rand" "math/rand"
"os" "os"
"os/exec"
"os/user" "os/user"
"runtime" "runtime/debug"
"sort"
"strconv" "strconv"
"time" "time"
kingpin "gopkg.in/alecthomas/kingpin.v2" "github.com/natefinch/lumberjack"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/alecthomas/kong"
"code.dumpstack.io/tools/out-of-tree/config" "code.dumpstack.io/tools/out-of-tree/config"
) )
func findFallback(kcfg config.KernelConfig, ki config.KernelInfo) (rootfs string) { type Globals struct {
for _, k := range kcfg.Kernels { Config config.OutOfTree `help:"path to out-of-tree configuration" default:"~/.out-of-tree/out-of-tree.toml"`
if !exists(k.RootFS) || k.DistroType != ki.DistroType {
continue WorkDir string `help:"path to work directory" default:"./" type:"path"`
}
if k.RootFS < ki.RootFS {
rootfs = k.RootFS
return
}
}
return
} }
func handleFallbacks(kcfg config.KernelConfig) { type CLI struct {
sort.Sort(sort.Reverse(config.ByRootFS(kcfg.Kernels))) Globals
for i, k := range kcfg.Kernels { Pew PewCmd `cmd:"" help:"build, run, and test module/exploit"`
if !exists(k.RootFS) { Kernel KernelCmd `cmd:"" help:"manipulate kernels"`
newRootFS := findFallback(kcfg, k) 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"`
Image ImageCmd `cmd:"" help:"manage images"`
Container ContainerCmd `cmd:"" help:"manage containers"`
s := k.RootFS + " does not exists " Version VersionFlag `name:"version" help:"print version information and quit"`
if newRootFS != "" {
s += "(fallback to " + newRootFS + ")" LogLevel LogLevelFlag `enum:"trace,debug,info,warn,error" default:"info"`
} else { }
s += "(no fallback found)"
type LogLevelFlag string
func (loglevel LogLevelFlag) AfterApply() error {
switch loglevel {
case "debug", "trace":
zerolog.CallerMarshalFunc = func(pc uintptr, file string, line int) string {
short := file
for i := len(file) - 1; i > 0; i-- {
if file[i] == '/' {
short = file[i+1:]
break
}
} }
file = short
kcfg.Kernels[i].RootFS = newRootFS return file + ":" + strconv.Itoa(line)
log.Println(s)
} }
log.Logger = log.With().Caller().Logger()
} }
return nil
} }
func checkRequiredUtils() (err error) { type VersionFlag string
// Check for required commands
for _, cmd := range []string{"docker", "qemu-system-x86_64"} { func (v VersionFlag) Decode(ctx *kong.DecodeContext) error { return nil }
_, err := exec.Command("which", cmd).CombinedOutput() func (v VersionFlag) IsBool() bool { return true }
if err != nil { func (v VersionFlag) BeforeApply(app *kong.Kong, vars kong.Vars) error {
return fmt.Errorf("Command not found: %s", cmd) fmt.Println(vars["version"])
} app.Exit(0)
} return nil
return
} }
func checkDockerPermissions() (err error) { type LevelWriter struct {
output, err := exec.Command("docker", "ps").CombinedOutput() io.Writer
if err != nil { Level zerolog.Level
err = fmt.Errorf("%s", output) }
func (lw *LevelWriter) WriteLevel(l zerolog.Level, p []byte) (n int, err error) {
if l >= lw.Level {
return lw.Writer.Write(p)
} }
return return len(p), nil
} }
func main() { func main() {
log.SetFlags(log.Lshortfile)
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())
app := kingpin.New( cli := CLI{}
"out-of-tree", ctx := kong.Parse(&cli,
"kernel {module, exploit} development tool", kong.Name("out-of-tree"),
kong.Description("kernel {module, exploit} development tool"),
kong.UsageOnError(),
kong.ConfigureHelp(kong.HelpOptions{
Compact: true,
}),
kong.Vars{
"version": "2.0.5",
},
) )
app.Author("Mikhail Klementev <root@dumpstack.io>") var loglevel zerolog.Level
app.Version("1.1.0") switch cli.LogLevel {
case "trace":
pathFlag := app.Flag("path", "Path to work directory") loglevel = zerolog.TraceLevel
path := pathFlag.Default(".").ExistingDir() case "debug":
loglevel = zerolog.DebugLevel
case "info":
loglevel = zerolog.InfoLevel
case "warn":
loglevel = zerolog.WarnLevel
case "error":
loglevel = zerolog.ErrorLevel
}
usr, err := user.Current() usr, err := user.Current()
if err != nil { if err != nil {
return return
} }
os.MkdirAll(usr.HomeDir+"/.out-of-tree", os.ModePerm)
confPath := usr.HomeDir + "/.out-of-tree/out-of-tree.toml" log.Logger = log.Output(zerolog.MultiLevelWriter(
conf, err := config.ReadOutOfTreeConf(confPath) &LevelWriter{Writer: zerolog.NewConsoleWriter(
if err != nil { func(w *zerolog.ConsoleWriter) {
return w.Out = os.Stderr
},
),
Level: loglevel,
},
&LevelWriter{Writer: &lumberjack.Logger{
Filename: usr.HomeDir + "/.out-of-tree/logs/out-of-tree.log",
},
Level: zerolog.TraceLevel,
},
))
log.Trace().Msg("start out-of-tree")
log.Debug().Msgf("%v", os.Args)
log.Debug().Msgf("%v", cli)
if buildInfo, ok := debug.ReadBuildInfo(); ok {
log.Debug().Msgf("%v", buildInfo.GoVersion)
log.Debug().Msgf("%v", buildInfo.Settings)
} }
kcfgPathFlag := app.Flag("kernels", "Path to main kernels config") err = ctx.Run(&cli.Globals)
kcfgPath := kcfgPathFlag.Default(conf.Kernels).String() ctx.FatalIfErrorf(err)
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()
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()
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", "").Required().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")
}
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)
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)
}
} }

70
pack.go
View File

@ -1,57 +1,83 @@
// 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 // Use of this source code is governed by a AGPLv3 license
// (or later) that can be found in the LICENSE file. // (or later) that can be found in the LICENSE file.
package main package main
import ( import (
"database/sql"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"runtime"
"time" "time"
"code.dumpstack.io/tools/out-of-tree/config" "github.com/rs/zerolog/log"
) )
func packHandler(db *sql.DB, path, registry string, stop time.Time, type PackCmd struct {
commands []config.DockerCommand, kcfg config.KernelConfig, Autogen bool `help:"kernel autogeneration"`
autogen, download bool, exploitRuns, kernelRuns int64) (err error) { UseHost bool `help:"also use host kernels"`
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"`
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:"1"`
dockerTimeout := time.Minute Threads int `help:"threads" default:"4"`
qemuTimeout := time.Minute
threads := runtime.NumCPU()
Tag string `help:"filter tag"`
Timeout time.Duration `help:"timeout after tool will not spawn new tests"`
QemuTimeout time.Duration `help:"timeout for qemu"`
DockerTimeout time.Duration `help:"timeout for docker"`
}
func (cmd *PackCmd) Run(g *Globals) (err error) {
tag := fmt.Sprintf("pack_run_%d", time.Now().Unix()) tag := fmt.Sprintf("pack_run_%d", time.Now().Unix())
log.Println("Tag:", tag) log.Print("Tag:", tag)
files, err := ioutil.ReadDir(path) files, err := ioutil.ReadDir(g.WorkDir)
if err != nil { if err != nil {
return return
} }
for _, f := range files { for _, f := range files {
workPath := path + "/" + f.Name() workPath := g.WorkDir + "/" + f.Name()
if !exists(workPath + "/.out-of-tree.toml") { if !exists(workPath + "/.out-of-tree.toml") {
continue continue
} }
if autogen { if cmd.Autogen {
var perRegex int64 = 1 err = KernelAutogenCmd{Max: cmd.Max}.Run(
err = kernelAutogenHandler(workPath, registry, &KernelCmd{
commands, perRegex, false, download) NoDownload: cmd.NoDownload,
UseHost: cmd.UseHost,
},
&Globals{
Config: g.Config,
WorkDir: workPath,
},
)
if err != nil { if err != nil {
return return
} }
} }
log.Println(f.Name()) log.Print(f.Name())
pewHandler(kcfg, workPath, "", "", "", false, pew := PewCmd{
stop, dockerTimeout, qemuTimeout, Max: cmd.KernelRuns,
kernelRuns, exploitRuns, pathDevNull, tag, threads, db) Runs: cmd.ExploitRuns,
Threads: cmd.Threads,
Tag: tag,
Timeout: cmd.Timeout,
QemuTimeout: cmd.QemuTimeout,
DockerTimeout: cmd.DockerTimeout,
Dist: pathDevNull,
}
pew.Run(&Globals{
Config: g.Config,
WorkDir: workPath,
})
} }
return return

486
pew.go
View File

@ -1,16 +1,16 @@
// 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 // Use of this source code is governed by a AGPLv3 license
// (or later) that can be found in the LICENSE file. // (or later) that can be found in the LICENSE file.
package main package main
import ( import (
"bufio"
"database/sql" "database/sql"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"log"
"math/rand" "math/rand"
"os" "os"
"os/exec" "os/exec"
@ -20,12 +20,119 @@ import (
"github.com/otiai10/copy" "github.com/otiai10/copy"
"github.com/remeh/sizedwaitgroup" "github.com/remeh/sizedwaitgroup"
"gopkg.in/logrusorgru/aurora.v1" "github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"gopkg.in/logrusorgru/aurora.v2"
"code.dumpstack.io/tools/out-of-tree/config" "code.dumpstack.io/tools/out-of-tree/config"
"code.dumpstack.io/tools/out-of-tree/qemu" "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"`
Shuffle bool `help:"randomize kernels test order"`
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" default:"1"`
Tag string `help:"log tagging"`
Timeout time.Duration `help:"timeout after tool will not spawn new tests"`
ArtifactConfig string `help:"path to artifact config" type:"path"`
QemuTimeout time.Duration `help:"timeout for qemu"`
DockerTimeout time.Duration `help:"timeout for docker"`
Threshold float64 `help:"reliablity threshold for exit code" default:"1.00"`
db *sql.DB
kcfg config.KernelConfig
timeoutDeadline time.Time
}
func (cmd *PewCmd) Run(g *Globals) (err error) {
cmd.kcfg, err = config.ReadKernelConfig(g.Config.Kernels)
if err != nil {
log.Fatal().Err(err).Msg("read kernels config")
}
if cmd.Timeout != 0 {
log.Info().Msgf("Set global timeout to %s", cmd.Timeout)
cmd.timeoutDeadline = time.Now().Add(cmd.Timeout)
}
cmd.db, err = openDatabase(g.Config.Database)
if err != nil {
log.Fatal().Err(err).
Msgf("Cannot open database %s", g.Config.Database)
}
defer cmd.db.Close()
var configPath string
if cmd.ArtifactConfig == "" {
configPath = g.WorkDir + "/.out-of-tree.toml"
} else {
configPath = cmd.ArtifactConfig
}
ka, err := config.ReadArtifactConfig(configPath)
if err != nil {
return
}
if ka.SourcePath == "" {
ka.SourcePath = g.WorkDir
}
if cmd.Kernel != "" {
var km config.KernelMask
km, err = kernelMask(cmd.Kernel)
if err != nil {
return
}
ka.SupportedKernels = []config.KernelMask{km}
}
if cmd.Guess {
ka.SupportedKernels, err = genAllKernels()
if err != nil {
return
}
}
if cmd.QemuTimeout != 0 {
log.Info().Msgf("Set qemu timeout to %s", cmd.QemuTimeout)
} else {
cmd.QemuTimeout = g.Config.Qemu.Timeout.Duration
}
if cmd.DockerTimeout != 0 {
log.Info().Msgf("Set docker timeout to %s", cmd.DockerTimeout)
} else {
cmd.DockerTimeout = g.Config.Docker.Timeout.Duration
}
if cmd.Tag == "" {
cmd.Tag = fmt.Sprintf("%d", time.Now().Unix())
}
log.Info().Str("tag", cmd.Tag).Msg("log")
err = cmd.performCI(ka)
if err != nil {
return
}
log.Info().Msgf("Success rate: %.02f, Threshold: %.02f",
successRate(state), cmd.Threshold)
if successRate(state) < cmd.Threshold {
err = errors.New("reliability threshold not met")
}
return
}
type runstate struct { type runstate struct {
Overall, Success float64 Overall, Success float64
} }
@ -40,44 +147,107 @@ func successRate(state runstate) float64 {
const pathDevNull = "/dev/null" const pathDevNull = "/dev/null"
func dockerRun(timeout time.Duration, container, workdir, command string) ( func sh(workdir, command string) (output string, err error) {
output string, err error) { flog := log.With().
Str("workdir", workdir).
Str("command", command).
Logger()
cmd := exec.Command("docker", "run", "-v", workdir+":/work", cmd := exec.Command("sh", "-c", "cd "+workdir+" && "+command)
container, "bash", "-c", "cd /work && "+command)
timer := time.AfterFunc(timeout, func() { flog.Debug().Msgf("%v", cmd)
cmd.Process.Kill()
})
defer timer.Stop()
raw, err := cmd.CombinedOutput() stdout, err := cmd.StdoutPipe()
if err != nil {
return
}
cmd.Stderr = cmd.Stdout
err = cmd.Start()
if err != nil { if err != nil {
e := fmt.Sprintf("error `%v` for cmd `%v` with output `%v`",
err, command, string(raw))
err = errors.New(e)
return return
} }
output = string(raw) go func() {
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
m := scanner.Text()
output += m + "\n"
flog.Trace().Str("stdout", m).Msg("")
}
}()
err = cmd.Wait()
if err != nil {
e := fmt.Sprintf("%v %v output: %v", cmd, err, output)
err = errors.New(e)
}
return
}
func applyPatches(src string, ka config.Artifact) (err error) {
for i, patch := range ka.Patches {
name := fmt.Sprintf("patch_%02d", i)
path := src + "/" + name + ".diff"
if patch.Source != "" && patch.Path != "" {
err = errors.New("path and source are mutually exclusive")
return
} else if patch.Source != "" {
err = os.WriteFile(path, []byte(patch.Source), 0644)
if err != nil {
return
}
} else if patch.Path != "" {
err = copy.Copy(patch.Path, path)
if err != nil {
return
}
}
if patch.Source != "" || patch.Path != "" {
_, err = sh(src, "patch < "+path)
if err != nil {
return
}
}
if patch.Script != "" {
script := src + "/" + name + ".sh"
err = os.WriteFile(script, []byte(patch.Script), 0755)
if err != nil {
return
}
_, err = sh(src, script)
if err != nil {
return
}
}
}
return return
} }
func build(tmp string, ka config.Artifact, ki config.KernelInfo, func build(tmp string, ka config.Artifact, ki config.KernelInfo,
dockerTimeout time.Duration) (outPath, output string, err error) { dockerTimeout time.Duration) (outdir, outpath, output string, err error) {
target := fmt.Sprintf("%d_%s", rand.Int(), ki.KernelRelease) target := fmt.Sprintf("%d_%s", rand.Int(), ki.KernelRelease)
tmpSourcePath := tmp + "/source" outdir = tmp + "/source"
err = copy.Copy(ka.SourcePath, tmpSourcePath) err = copy.Copy(ka.SourcePath, outdir)
if err != nil { if err != nil {
return return
} }
outPath = tmpSourcePath + "/" + target err = applyPatches(outdir, ka)
if err != nil {
return
}
outpath = outdir + "/" + target
if ka.Type == config.KernelModule { if ka.Type == config.KernelModule {
outPath += ".ko" outpath += ".ko"
} }
kernel := "/lib/modules/" + ki.KernelRelease + "/build" kernel := "/lib/modules/" + ki.KernelRelease + "/build"
@ -85,13 +255,25 @@ func build(tmp string, ka config.Artifact, ki config.KernelInfo,
kernel = ki.KernelSource kernel = ki.KernelSource
} }
buildCommand := "make KERNEL=" + kernel + " TARGET=" + target
if ka.Make.Target != "" {
buildCommand += " " + ka.Make.Target
}
if ki.ContainerName != "" { if ki.ContainerName != "" {
output, err = dockerRun(dockerTimeout, ki.ContainerName, var c container
tmpSourcePath, "make KERNEL="+kernel+" TARGET="+target+ c, err = NewContainer(ki.ContainerName, dockerTimeout)
" && chmod -R 777 /work") if err != nil {
log.Fatal().Err(err).Msg("container creation failure")
}
output, err = c.Run(outdir, buildCommand+" && chmod -R 777 /work")
} else { } else {
command := "make KERNEL=" + kernel + " TARGET=" + target cmd := exec.Command("bash", "-c", "cd "+outdir+" && "+
cmd := exec.Command("bash", "-c", "cd "+tmpSourcePath+" && "+command) buildCommand)
log.Debug().Msgf("%v", cmd)
timer := time.AfterFunc(dockerTimeout, func() { timer := time.AfterFunc(dockerTimeout, func() {
cmd.Process.Kill() cmd.Process.Kill()
}) })
@ -101,7 +283,7 @@ func build(tmp string, ka config.Artifact, ki config.KernelInfo,
raw, err = cmd.CombinedOutput() raw, err = cmd.CombinedOutput()
if err != nil { if err != nil {
e := fmt.Sprintf("error `%v` for cmd `%v` with output `%v`", e := fmt.Sprintf("error `%v` for cmd `%v` with output `%v`",
err, command, string(raw)) err, buildCommand, string(raw))
err = errors.New(e) err = errors.New(e)
return return
} }
@ -111,6 +293,10 @@ func build(tmp string, ka config.Artifact, ki config.KernelInfo,
return return
} }
func runScript(q *qemu.System, script string) (output string, err error) {
return q.Command("root", script)
}
func testKernelModule(q *qemu.System, ka config.Artifact, func testKernelModule(q *qemu.System, ka config.Artifact,
test string) (output string, err error) { test string) (output string, err error) {
@ -145,18 +331,23 @@ func testKernelExploit(q *qemu.System, ka config.Artifact,
func genOkFail(name string, ok bool) (aurv aurora.Value) { func genOkFail(name string, ok bool) (aurv aurora.Value) {
state.Overall += 1 state.Overall += 1
s := " " + name
if name == "" {
s = ""
}
if ok { if ok {
state.Success += 1 state.Success += 1
s := " " + name + " SUCCESS " s += " SUCCESS "
aurv = aurora.BgGreen(aurora.Black(s)) aurv = aurora.BgGreen(aurora.Black(s))
} else { } else {
s := " " + name + " FAILURE " s += " FAILURE "
aurv = aurora.BgRed(aurora.Gray(aurora.Bold(s))) aurv = aurora.BgRed(aurora.White(aurora.Bold(s)))
} }
return return
} }
type phasesResult struct { type phasesResult struct {
BuildDir string
BuildArtifact string BuildArtifact string
Build, Run, Test struct { Build, Run, Test struct {
Output string Output string
@ -191,15 +382,19 @@ func dumpResult(q *qemu.System, ka config.Artifact, ki config.KernelInfo,
ki.DistroRelease, ki.KernelRelease) ki.DistroRelease, ki.KernelRelease)
colored := "" colored := ""
if ka.Type == config.KernelExploit { switch ka.Type {
case config.KernelExploit:
colored = aurora.Sprintf("[*] %40s: %s %s", distroInfo, colored = aurora.Sprintf("[*] %40s: %s %s", distroInfo,
genOkFail("BUILD", res.Build.Ok), genOkFail("BUILD", res.Build.Ok),
genOkFail("LPE", res.Test.Ok)) genOkFail("LPE", res.Test.Ok))
} else { case config.KernelModule:
colored = aurora.Sprintf("[*] %40s: %s %s %s", distroInfo, colored = aurora.Sprintf("[*] %40s: %s %s %s", distroInfo,
genOkFail("BUILD", res.Build.Ok), genOkFail("BUILD", res.Build.Ok),
genOkFail("INSMOD", res.Run.Ok), genOkFail("INSMOD", res.Run.Ok),
genOkFail("TEST", res.Test.Ok)) genOkFail("TEST", res.Test.Ok))
case config.Script:
colored = aurora.Sprintf("[*] %40s: %s", distroInfo,
genOkFail("", res.Test.Ok))
} }
additional := "" additional := ""
@ -217,13 +412,13 @@ func dumpResult(q *qemu.System, ka config.Artifact, ki config.KernelInfo,
err := addToLog(db, q, ka, ki, res, tag) err := addToLog(db, q, ka, ki, res, tag)
if err != nil { if err != nil {
log.Println("[db] addToLog (", ka, ") error:", err) log.Warn().Err(err).Msgf("[db] addToLog (%v)", ka)
} }
if binary == "" && dist != pathDevNull { if binary == "" && dist != pathDevNull {
err = os.MkdirAll(dist, os.ModePerm) err = os.MkdirAll(dist, os.ModePerm)
if err != nil { if err != nil {
log.Println("os.MkdirAll (", ka, ") error:", err) log.Warn().Err(err).Msgf("os.MkdirAll (%v)", ka)
} }
path := fmt.Sprintf("%s/%s-%s-%s", dist, ki.DistroType, path := fmt.Sprintf("%s/%s-%s-%s", dist, ki.DistroType,
@ -234,26 +429,40 @@ func dumpResult(q *qemu.System, ka config.Artifact, ki config.KernelInfo,
err = copyFile(res.BuildArtifact, path) err = copyFile(res.BuildArtifact, path)
if err != nil { if err != nil {
log.Println("copyFile (", ka, ") error:", err) log.Warn().Err(err).Msgf("copy file (%v)", ka)
} }
} }
} }
func copyArtifactAndTest(q *qemu.System, ka config.Artifact, func copyArtifactAndTest(slog zerolog.Logger, q *qemu.System, ka config.Artifact,
res *phasesResult, remoteTest string) (err error) { res *phasesResult, remoteTest string) (err error) {
// Copy all test files to the remote machine
for _, f := range ka.TestFiles {
if f.Local[0] != '/' {
if res.BuildDir != "" {
f.Local = res.BuildDir + "/" + f.Local
}
}
err = q.CopyFile(f.User, f.Local, f.Remote)
if err != nil {
slog.Error().Err(err).Msg("copy test file")
return
}
}
switch ka.Type { switch ka.Type {
case config.KernelModule: case config.KernelModule:
res.Run.Output, err = q.CopyAndInsmod(res.BuildArtifact) res.Run.Output, err = q.CopyAndInsmod(res.BuildArtifact)
if err != nil { if err != nil {
log.Println(res.Run.Output, err) slog.Error().Err(err).Msg(res.Run.Output)
return return
} }
res.Run.Ok = true res.Run.Ok = true
res.Test.Output, err = testKernelModule(q, ka, remoteTest) res.Test.Output, err = testKernelModule(q, ka, remoteTest)
if err != nil { if err != nil {
log.Println(res.Test.Output, err) slog.Error().Err(err).Msg(res.Test.Output)
return return
} }
res.Test.Ok = true res.Test.Ok = true
@ -267,13 +476,22 @@ func copyArtifactAndTest(q *qemu.System, ka config.Artifact,
res.Test.Output, err = testKernelExploit(q, ka, remoteTest, res.Test.Output, err = testKernelExploit(q, ka, remoteTest,
remoteExploit) remoteExploit)
if err != nil { if err != nil {
log.Println(res.Test.Output) slog.Error().Err(err).Msg(res.Test.Output)
return return
} }
res.Run.Ok = true // does not really used res.Run.Ok = true // does not really used
res.Test.Ok = true res.Test.Ok = true
case config.Script:
res.Test.Output, err = runScript(q, remoteTest)
if err != nil {
slog.Error().Err(err).Msg(res.Test.Output)
return
}
slog.Info().Msg(res.Test.Output)
res.Run.Ok = true
res.Test.Ok = true
default: default:
log.Println("Unsupported artifact type") slog.Fatal().Msg("Unsupported artifact type")
} }
return return
@ -300,20 +518,53 @@ func copyTest(q *qemu.System, testPath string, ka config.Artifact) (
return return
} }
func whatever(swg *sizedwaitgroup.SizedWaitGroup, ka config.Artifact, func copyStandardModules(q *qemu.System, ki config.KernelInfo) (err error) {
ki config.KernelInfo, binaryPath, testPath string, _, err = q.Command("root", "mkdir -p /lib/modules")
qemuTimeout, dockerTimeout time.Duration, dist, tag string, if err != nil {
db *sql.DB) { return
}
files, err := ioutil.ReadDir(ki.ModulesPath)
if err != nil {
return
}
// FIXME scp cannot ignore symlinks
for _, f := range files {
if f.Mode()&os.ModeSymlink == os.ModeSymlink {
continue
}
path := ki.ModulesPath + "/" + f.Name()
err = q.CopyDirectory("root", path, "/lib/modules/"+ki.KernelRelease+"/")
if err != nil {
return
}
}
return
}
func (cmd PewCmd) testArtifact(swg *sizedwaitgroup.SizedWaitGroup,
ka config.Artifact, ki config.KernelInfo) {
defer swg.Done() defer swg.Done()
slog := log.With().
Str("distro_type", ki.DistroType.String()).
Str("distro_release", ki.DistroRelease).
Str("kernel", ki.KernelRelease).
Logger()
slog.Info().Msg("start")
kernel := qemu.Kernel{KernelPath: ki.KernelPath, InitrdPath: ki.InitrdPath} kernel := qemu.Kernel{KernelPath: ki.KernelPath, InitrdPath: ki.InitrdPath}
q, err := qemu.NewSystem(qemu.X86x64, kernel, ki.RootFS) q, err := qemu.NewSystem(qemu.X86x64, kernel, ki.RootFS)
if err != nil { if err != nil {
log.Println("Qemu creation error:", err) slog.Error().Err(err).Msg("qemu init")
return return
} }
q.Timeout = qemuTimeout q.Timeout = cmd.QemuTimeout
if ka.Qemu.Timeout.Duration != 0 { if ka.Qemu.Timeout.Duration != 0 {
q.Timeout = ka.Qemu.Timeout.Duration q.Timeout = ka.Qemu.Timeout.Duration
@ -325,17 +576,29 @@ func whatever(swg *sizedwaitgroup.SizedWaitGroup, ka config.Artifact,
q.Memory = ka.Qemu.Memory q.Memory = ka.Qemu.Memory
} }
if ka.Docker.Timeout.Duration != 0 {
cmd.DockerTimeout = ka.Docker.Timeout.Duration
}
q.SetKASLR(!ka.Mitigations.DisableKaslr) q.SetKASLR(!ka.Mitigations.DisableKaslr)
q.SetSMEP(!ka.Mitigations.DisableSmep) q.SetSMEP(!ka.Mitigations.DisableSmep)
q.SetSMAP(!ka.Mitigations.DisableSmap) q.SetSMAP(!ka.Mitigations.DisableSmap)
q.SetKPTI(!ka.Mitigations.DisableKpti)
err = q.Start() err = q.Start()
if err != nil { if err != nil {
log.Println("Qemu start error:", err) slog.Error().Err(err).Msg("qemu start")
return return
} }
defer q.Stop() defer q.Stop()
go func() {
for !q.Died {
time.Sleep(time.Minute)
slog.Debug().Msg("still alive")
}
}()
usr, err := user.Current() usr, err := user.Current()
if err != nil { if err != nil {
return return
@ -345,40 +608,73 @@ func whatever(swg *sizedwaitgroup.SizedWaitGroup, ka config.Artifact,
tmp, err := ioutil.TempDir(tmpdir, "out-of-tree_") tmp, err := ioutil.TempDir(tmpdir, "out-of-tree_")
if err != nil { if err != nil {
log.Println("Temporary directory creation error:", err) slog.Error().Err(err).Msg("making tmp directory")
return return
} }
defer os.RemoveAll(tmp) defer os.RemoveAll(tmp)
result := phasesResult{} result := phasesResult{}
defer dumpResult(q, ka, ki, &result, dist, tag, binaryPath, db) defer dumpResult(q, ka, ki, &result, cmd.Dist, cmd.Tag, cmd.Binary, cmd.db)
if binaryPath == "" { if ka.Type == config.Script {
result.BuildArtifact, result.Build.Output, err = build(tmp, ka, result.Build.Ok = true
ki, dockerTimeout) cmd.Test = ka.Script
} else if cmd.Binary == "" {
// TODO: build should return structure
start := time.Now()
result.BuildDir, result.BuildArtifact, result.Build.Output, err =
build(tmp, ka, ki, cmd.DockerTimeout)
slog.Debug().Str("duration", time.Now().Sub(start).String()).
Msg("build done")
if err != nil { if err != nil {
log.Println(err) log.Error().Err(err).Msg("build")
return return
} }
result.Build.Ok = true result.Build.Ok = true
} else { } else {
result.BuildArtifact = binaryPath result.BuildArtifact = cmd.Binary
result.Build.Ok = true result.Build.Ok = true
} }
if testPath == "" { if cmd.Test == "" {
testPath = result.BuildArtifact + "_test" cmd.Test = result.BuildArtifact + "_test"
if !exists(testPath) { if !exists(cmd.Test) {
testPath = tmp + "/" + "test.sh" cmd.Test = tmp + "/source/" + "test.sh"
} }
} }
remoteTest, err := copyTest(q, testPath, ka) err = q.WaitForSSH(cmd.QemuTimeout)
if err != nil { if err != nil {
return return
} }
copyArtifactAndTest(q, ka, &result, remoteTest) remoteTest, err := copyTest(q, cmd.Test, ka)
if err != nil {
return
}
if ka.StandardModules {
// Module depends on one of the standard modules
start := time.Now()
err = copyStandardModules(q, ki)
if err != nil {
slog.Fatal().Err(err).Msg("copy standard modules")
return
}
slog.Debug().Str("duration", time.Now().Sub(start).String()).
Msg("copy standard modules")
}
err = preloadModules(q, ka, ki, cmd.DockerTimeout)
if err != nil {
slog.Error().Err(err).Msg("preload modules")
return
}
start := time.Now()
copyArtifactAndTest(slog, q, ka, &result, remoteTest)
slog.Debug().Str("duration", time.Now().Sub(start).String()).
Msg("test completed")
} }
func shuffleKernels(a []config.KernelInfo) []config.KernelInfo { func shuffleKernels(a []config.KernelInfo) []config.KernelInfo {
@ -390,16 +686,15 @@ func shuffleKernels(a []config.KernelInfo) []config.KernelInfo {
return a return a
} }
func performCI(ka config.Artifact, kcfg config.KernelConfig, binaryPath, func (cmd PewCmd) performCI(ka config.Artifact) (err error) {
testPath string, stop time.Time,
qemuTimeout, dockerTimeout time.Duration,
max, runs int64, dist, tag string, threads int,
db *sql.DB) (err error) {
found := false found := false
max := cmd.Max
swg := sizedwaitgroup.New(threads) swg := sizedwaitgroup.New(cmd.Threads)
for _, kernel := range shuffleKernels(kcfg.Kernels) { if cmd.Shuffle {
cmd.kcfg.Kernels = shuffleKernels(cmd.kcfg.Kernels)
}
for _, kernel := range cmd.kcfg.Kernels {
if max <= 0 { if max <= 0 {
break break
} }
@ -413,14 +708,14 @@ func performCI(ka config.Artifact, kcfg config.KernelConfig, binaryPath,
if supported { if supported {
found = true found = true
max-- max--
for i := int64(0); i < runs; i++ { for i := int64(0); i < cmd.Runs; i++ {
if !stop.IsZero() && time.Now().After(stop) { if !cmd.timeoutDeadline.IsZero() &&
time.Now().After(cmd.timeoutDeadline) {
break break
} }
swg.Add() swg.Add()
go whatever(&swg, ka, kernel, binaryPath, go cmd.testArtifact(&swg, ka, kernel)
testPath, qemuTimeout, dockerTimeout,
dist, tag, db)
} }
} }
} }
@ -435,8 +730,10 @@ func performCI(ka config.Artifact, kcfg config.KernelConfig, binaryPath,
func exists(path string) bool { func exists(path string) bool {
if _, err := os.Stat(path); err != nil { if _, err := os.Stat(path); err != nil {
log.Debug().Msgf("%s does not exist", path)
return false return false
} }
log.Debug().Msgf("%s exist", path)
return true return true
} }
@ -471,46 +768,3 @@ func genAllKernels() (sk []config.KernelMask, err error) {
} }
return return
} }
// TODO: Now too many parameters, move all of them to some structure
func pewHandler(kcfg config.KernelConfig,
workPath, ovrrdKrnl, binary, test string, guess bool,
stop time.Time, qemuTimeout, dockerTimeout time.Duration,
max, runs int64, dist, tag string, threads int,
db *sql.DB) (err error) {
ka, err := config.ReadArtifactConfig(workPath + "/.out-of-tree.toml")
if err != nil {
return
}
if ka.SourcePath == "" {
ka.SourcePath = workPath
}
if ovrrdKrnl != "" {
var km config.KernelMask
km, err = kernelMask(ovrrdKrnl)
if err != nil {
return
}
ka.SupportedKernels = []config.KernelMask{km}
}
if guess {
ka.SupportedKernels, err = genAllKernels()
if err != nil {
return
}
}
err = performCI(ka, kcfg, binary, test,
stop, qemuTimeout, dockerTimeout,
max, runs, dist, tag, threads, db)
if err != nil {
return
}
return
}

View File

@ -1,44 +0,0 @@
// Copyright 2018 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 main
import (
"fmt"
"io/ioutil"
"os"
"strings"
"testing"
"time"
)
func TestDockerRun(t *testing.T) {
tmp, err := ioutil.TempDir("/tmp/", "out-of-tree_test_")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
start := time.Now()
timeout := time.Second
_, err = dockerRun(timeout, "ubuntu", tmp, "sleep 5s")
if err == nil {
t.Fatal("docker is not killed by timeout")
}
if time.Since(start) > 3*time.Second {
t.Fatal(fmt.Sprintf("timeout failed (%v instead of %v)",
time.Since(start), time.Second))
}
output, err := dockerRun(time.Minute, "ubuntu", tmp, "echo hello")
if err != nil {
t.Fatal(err)
}
if !strings.Contains(output, "hello") {
t.Fatal("wrong output (" + output + ")")
}
}

166
preload.go Normal file
View File

@ -0,0 +1,166 @@
// Copyright 2020 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 main
import (
"crypto/sha1"
"encoding/hex"
"errors"
"io/ioutil"
"os"
"os/user"
"path/filepath"
"time"
"github.com/go-git/go-git/v5"
"github.com/rs/zerolog/log"
"code.dumpstack.io/tools/out-of-tree/config"
"code.dumpstack.io/tools/out-of-tree/qemu"
)
func preloadModules(q *qemu.System, ka config.Artifact, ki config.KernelInfo,
dockerTimeout time.Duration) (err error) {
for _, pm := range ka.Preload {
err = preload(q, ki, pm, dockerTimeout)
if err != nil {
return
}
}
return
}
func preload(q *qemu.System, ki config.KernelInfo, pm config.PreloadModule,
dockerTimeout time.Duration) (err error) {
var workPath, cache string
if pm.Path != "" {
log.Print("Use non-git path for preload module (no cache)")
workPath = pm.Path
} else if pm.Repo != "" {
workPath, cache, err = cloneOrPull(pm.Repo, ki)
if err != nil {
return
}
} else {
errors.New("No repo/path in preload entry")
}
err = buildAndInsmod(workPath, q, ki, dockerTimeout, cache)
if err != nil {
return
}
time.Sleep(pm.TimeoutAfterLoad.Duration)
return
}
func buildAndInsmod(workPath string, q *qemu.System, ki config.KernelInfo,
dockerTimeout time.Duration, cache string) (err error) {
tmp, err := ioutil.TempDir("", "out-of-tree_")
if err != nil {
return
}
defer os.RemoveAll(tmp)
var artifact string
if exists(cache) {
artifact = cache
} else {
artifact, err = buildPreload(workPath, tmp, ki, dockerTimeout)
if err != nil {
return
}
if cache != "" {
err = copyFile(artifact, cache)
if err != nil {
return
}
}
}
output, err := q.CopyAndInsmod(artifact)
if err != nil {
log.Print(output)
return
}
return
}
func buildPreload(workPath, tmp string, ki config.KernelInfo,
dockerTimeout time.Duration) (artifact string, err error) {
ka, err := config.ReadArtifactConfig(workPath + "/.out-of-tree.toml")
if err != nil {
return
}
ka.SourcePath = workPath
km := config.KernelMask{DistroType: ki.DistroType,
DistroRelease: ki.DistroRelease,
ReleaseMask: ki.KernelRelease,
}
ka.SupportedKernels = []config.KernelMask{km}
if ka.Docker.Timeout.Duration != 0 {
dockerTimeout = ka.Docker.Timeout.Duration
}
_, artifact, _, err = build(tmp, ka, ki, dockerTimeout)
return
}
func cloneOrPull(repo string, ki config.KernelInfo) (workPath, cache string, err error) {
usr, err := user.Current()
if err != nil {
return
}
base := filepath.Join(usr.HomeDir, "/.out-of-tree/preload/")
workPath = filepath.Join(base, "/repos/", sha1sum(repo))
var r *git.Repository
if exists(workPath) {
r, err = git.PlainOpen(workPath)
if err != nil {
return
}
var w *git.Worktree
w, err = r.Worktree()
if err != nil {
return
}
err = w.Pull(&git.PullOptions{})
if err != nil && err != git.NoErrAlreadyUpToDate {
log.Print(repo, "pull error:", err)
}
} else {
r, err = git.PlainClone(workPath, false, &git.CloneOptions{URL: repo})
if err != nil {
return
}
}
ref, err := r.Head()
if err != nil {
return
}
cachedir := filepath.Join(base, "/cache/")
os.MkdirAll(cachedir, 0700)
filename := sha1sum(repo + ki.KernelPath + ref.Hash().String())
cache = filepath.Join(cachedir, filename)
return
}
func sha1sum(data string) string {
h := sha1.Sum([]byte(data))
return hex.EncodeToString(h[:])
}

View File

@ -5,7 +5,7 @@
package qemu package qemu
import ( import (
"bytes" "bufio"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -18,29 +18,11 @@ import (
"syscall" "syscall"
"time" "time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
func readUntilEOF(pipe io.ReadCloser, buf *[]byte) (err error) {
bufSize := 1024
for err != io.EOF {
stdout := make([]byte, bufSize)
var n int
n, err = pipe.Read(stdout)
if err != nil && err != io.EOF {
return
}
*buf = append(*buf, stdout[:n]...)
}
if err == io.EOF {
err = nil
}
return
}
type arch string type arch string
const ( const (
@ -66,6 +48,8 @@ type System struct {
kernel Kernel kernel Kernel
drivePath string drivePath string
Mutable bool
Cpus int Cpus int
Memory int Memory int
@ -94,18 +78,25 @@ type System struct {
stdout io.ReadCloser stdout io.ReadCloser
} }
Stdout, Stderr []byte Stdout, Stderr string
// accessible after qemu is closed // accessible after qemu is closed
exitErr error exitErr error
log zerolog.Logger
} }
// NewSystem constructor // NewSystem constructor
func NewSystem(arch arch, kernel Kernel, drivePath string) (q *System, err error) { func NewSystem(arch arch, kernel Kernel, drivePath string) (q *System, err error) {
q = &System{}
q.log = log.With().
Str("kernel", kernel.KernelPath).
Logger()
if _, err = exec.LookPath("qemu-system-" + string(arch)); err != nil { if _, err = exec.LookPath("qemu-system-" + string(arch)); err != nil {
return return
} }
q = &System{}
q.arch = arch q.arch = arch
if _, err = os.Stat(kernel.KernelPath); err != nil { if _, err = os.Stat(kernel.KernelPath); err != nil {
@ -125,6 +116,12 @@ func NewSystem(arch arch, kernel Kernel, drivePath string) (q *System, err error
return return
} }
func (q *System) SetSSHAddrPort(addr string, port int) (err error) {
// TODO validate
q.sshAddrPort = fmt.Sprintf("%s:%d", addr, port)
return
}
func getRandomAddrPort() (addr string) { func getRandomAddrPort() (addr string) {
// 127.1-255.0-255.0-255:10000-50000 // 127.1-255.0-255.0-255:10000-50000
ip := fmt.Sprintf("127.%d.%d.%d", ip := fmt.Sprintf("127.%d.%d.%d",
@ -178,11 +175,12 @@ func kvmExists() bool {
func (q *System) panicWatcher() { func (q *System) panicWatcher() {
for { for {
time.Sleep(time.Second) time.Sleep(time.Second)
if bytes.Contains(q.Stdout, []byte("Kernel panic")) { if strings.Contains(q.Stdout, "Kernel panic") {
q.KernelPanic = true
q.log.Debug().Msg("kernel panic")
time.Sleep(time.Second) time.Sleep(time.Second)
// There is no reason to stay alive after kernel panic // There is no reason to stay alive after kernel panic
q.Stop() q.Stop()
q.KernelPanic = true
return return
} }
} }
@ -213,9 +211,11 @@ func (q System) cmdline() (s string) {
// Start qemu process // Start qemu process
func (q *System) Start() (err error) { func (q *System) Start() (err error) {
rand.Seed(time.Now().UnixNano()) // Are you sure? rand.Seed(time.Now().UnixNano()) // Are you sure?
q.sshAddrPort = getFreeAddrPort() if q.sshAddrPort == "" {
q.sshAddrPort = getFreeAddrPort()
}
hostfwd := fmt.Sprintf("hostfwd=tcp:%s-:22", q.sshAddrPort) hostfwd := fmt.Sprintf("hostfwd=tcp:%s-:22", q.sshAddrPort)
qemuArgs := []string{"-snapshot", "-nographic", qemuArgs := []string{"-nographic",
"-hda", q.drivePath, "-hda", q.drivePath,
"-kernel", q.kernel.KernelPath, "-kernel", q.kernel.KernelPath,
"-smp", fmt.Sprintf("%d", q.Cpus), "-smp", fmt.Sprintf("%d", q.Cpus),
@ -224,6 +224,10 @@ func (q *System) Start() (err error) {
"-netdev", "user,id=n1," + hostfwd, "-netdev", "user,id=n1," + hostfwd,
} }
if !q.Mutable {
qemuArgs = append(qemuArgs, "-snapshot")
}
if q.debug { if q.debug {
qemuArgs = append(qemuArgs, "-gdb", q.gdb) qemuArgs = append(qemuArgs, "-gdb", q.gdb)
} }
@ -243,6 +247,7 @@ func (q *System) Start() (err error) {
qemuArgs = append(qemuArgs, "-append", q.cmdline()) qemuArgs = append(qemuArgs, "-append", q.cmdline())
q.cmd = exec.Command("qemu-system-"+string(q.arch), qemuArgs...) q.cmd = exec.Command("qemu-system-"+string(q.arch), qemuArgs...)
q.log.Debug().Msgf("%v", q.cmd)
if q.pipe.stdin, err = q.cmd.StdinPipe(); err != nil { if q.pipe.stdin, err = q.cmd.StdinPipe(); err != nil {
return return
@ -261,8 +266,23 @@ func (q *System) Start() (err error) {
return return
} }
go readUntilEOF(q.pipe.stdout, &q.Stdout) go func() {
go readUntilEOF(q.pipe.stderr, &q.Stderr) scanner := bufio.NewScanner(q.pipe.stdout)
for scanner.Scan() {
m := scanner.Text()
q.Stdout += m + "\n"
q.log.Trace().Str("stdout", m).Msg("")
}
}()
go func() {
scanner := bufio.NewScanner(q.pipe.stderr)
for scanner.Scan() {
m := scanner.Text()
q.Stderr += m + "\n"
q.log.Trace().Str("stderr", m).Msg("")
}
}()
go func() { go func() {
q.exitErr = q.cmd.Wait() q.exitErr = q.cmd.Wait()
@ -301,6 +321,20 @@ func (q *System) Stop() {
} }
} }
func (q System) WaitForSSH(timeout time.Duration) error {
for start := time.Now(); time.Since(start) < timeout; {
client, err := q.ssh("root")
if err != nil {
time.Sleep(time.Second / 10)
continue
}
client.Close()
return nil
}
return errors.New("no ssh (timeout)")
}
func (q System) ssh(user string) (client *ssh.Client, err error) { func (q System) ssh(user string) (client *ssh.Client, err error) {
cfg := &ssh.ClientConfig{ cfg := &ssh.ClientConfig{
User: user, User: user,
@ -313,6 +347,14 @@ func (q System) ssh(user string) (client *ssh.Client, err error) {
// Command executes shell commands on qemu system // Command executes shell commands on qemu system
func (q System) Command(user, cmd string) (output string, err error) { func (q System) Command(user, cmd string) (output string, err error) {
flog := log.With().
Str("kernel", q.kernel.KernelPath).
Str("user", user).
Str("cmd", cmd).
Logger()
flog.Debug().Msg("qemu command")
client, err := q.ssh(user) client, err := q.ssh(user)
if err != nil { if err != nil {
return return
@ -324,8 +366,28 @@ func (q System) Command(user, cmd string) (output string, err error) {
return return
} }
bytesOutput, err := session.CombinedOutput(cmd) stdout, err := session.StdoutPipe()
output = string(bytesOutput) if err != nil {
return
}
session.Stderr = session.Stdout
err = session.Start(cmd)
if err != nil {
return
}
go func() {
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
m := scanner.Text()
output += m + "\n"
flog.Trace().Str("stdout", m).Msg("")
}
output = strings.TrimSuffix(output, "\n")
}()
err = session.Wait()
return return
} }
@ -346,24 +408,70 @@ func (q System) AsyncCommand(user, cmd string) (err error) {
"nohup sh -c '%s' > /dev/null 2> /dev/null < /dev/null &", cmd)) "nohup sh -c '%s' > /dev/null 2> /dev/null < /dev/null &", cmd))
} }
// CopyFile is copy file from local machine to remote through ssh/scp func (q System) scp(user, localPath, remotePath string, recursive bool) (err error) {
func (q *System) CopyFile(user, localPath, remotePath string) (err error) {
addrPort := strings.Split(q.sshAddrPort, ":") addrPort := strings.Split(q.sshAddrPort, ":")
addr := addrPort[0] addr := addrPort[0]
port := addrPort[1] port := addrPort[1]
cmd := exec.Command("scp", "-P", port, args := []string{
"-P", port,
"-o", "StrictHostKeyChecking=no", "-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null",
"-o", "LogLevel=error", "-o", "LogLevel=error",
localPath, user+"@"+addr+":"+remotePath) }
if recursive {
cmd := exec.Command("ssh", "-V")
log.Debug().Msgf("%v", cmd)
var output []byte
output, err = cmd.CombinedOutput()
if err != nil {
return
}
sshVersion := string(output)
log.Debug().Str("ssh version", sshVersion).Msg("")
if strings.Contains(sshVersion, "OpenSSH_9") {
// This release switches scp from using the
// legacy scp/rcp protocol to using the SFTP
// protocol by default.
//
// To keep compatibility with old distros,
// using -O flag to use the legacy scp/rcp.
//
// Note: old ssh doesn't support -O flag
args = append(args, "-O")
}
args = append(args, "-r")
}
args = append(args, localPath, user+"@"+addr+":"+remotePath)
cmd := exec.Command("scp", args...)
log.Debug().Msgf("%v", cmd)
output, err := cmd.CombinedOutput() output, err := cmd.CombinedOutput()
if err != nil { if err != nil || string(output) != "" {
return errors.New(string(output)) return errors.New(string(output))
} }
return return
} }
// CopyFile from local machine to remote via scp
func (q System) CopyFile(user, localPath, remotePath string) (err error) {
return q.scp(user, localPath, remotePath, false)
}
// CopyDirectory from local machine to remote via scp
func (q System) CopyDirectory(user, localPath, remotePath string) (err error) {
return q.scp(user, localPath, remotePath, true)
}
// CopyAndInsmod copy kernel module to temporary file on qemu then insmod it // CopyAndInsmod copy kernel module to temporary file on qemu then insmod it
func (q *System) CopyAndInsmod(localKoPath string) (output string, err error) { func (q *System) CopyAndInsmod(localKoPath string) (output string, err error) {
remoteKoPath := fmt.Sprintf("/tmp/module_%d.ko", rand.Int()) remoteKoPath := fmt.Sprintf("/tmp/module_%d.ko", rand.Int())

View File

@ -116,6 +116,7 @@ func startTestQemu(t *testing.T, timeout time.Duration) (q *System, err error) {
return return
} }
time.Sleep(time.Second)
return return
} }
@ -324,6 +325,8 @@ func TestSystemDebug(t *testing.T) {
return return
} }
time.Sleep(time.Second)
port := 45256 port := 45256
q.Debug(fmt.Sprintf("tcp::%d", port)) q.Debug(fmt.Sprintf("tcp::%d", port))

View File

@ -4,7 +4,7 @@
package qemu package qemu
const testConfigVmlinuz = "../tools/qemu-debian-img/ubuntu1804.vmlinuz" const testConfigVmlinuz = "../tools/qemu-debian-img/ubuntu2204.vmlinuz"
const testConfigInitrd = "../tools/qemu-debian-img/ubuntu1804.initrd" const testConfigInitrd = "../tools/qemu-debian-img/ubuntu2204.initrd"
const testConfigRootfs = "../tools/qemu-debian-img/ubuntu1804.img" const testConfigRootfs = "../tools/qemu-debian-img/ubuntu2204.img"
const testConfigSampleKo = "../tools/qemu-debian-img/ubuntu1804.ko" const testConfigSampleKo = "../tools/qemu-debian-img/ubuntu2204.ko"

5
shell.nix Normal file
View File

@ -0,0 +1,5 @@
{ pkgs ? import <nixpkgs> {} }:
with pkgs; mkShell {
packages = [ go gcc qemu ];
}

View File

@ -0,0 +1,56 @@
# Copyright 2020 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.
#
# Usage:
#
# $ sudo docker build -t gen-centos8-image .
# $ sudo docker run --privileged -v $(pwd):/shared -t gen-centos8-image
# $ tar -Szcf out_of_tree_centos_8.img.tar.gz out_of_tree_centos_8.img
#
# out_of_tree_centos_8.img will be created in current directory.
# You can change $(pwd) to different directory to use different destination
# for image.
#
FROM centos:8
RUN yum -y update
RUN yum -y groupinstall "Development Tools"
RUN yum -y install qemu-img e2fsprogs
ENV TMPDIR=/tmp/centos
RUN yum --installroot=$TMPDIR \
--releasever=8 \
--disablerepo='*' \
--enablerepo=BaseOS \
-y groupinstall Base
RUN yum --installroot=$TMPDIR \
--releasever=8 \
--disablerepo='*' \
--enablerepo=BaseOS \
-y install openssh-server openssh-clients
RUN chroot $TMPDIR /bin/sh -c 'useradd -m user'
RUN sed -i 's/root:\*:/root::/' $TMPDIR/etc/shadow
RUN sed -i 's/user:!!:/user::/' $TMPDIR/etc/shadow
RUN sed -i '/PermitEmptyPasswords/d' $TMPDIR/etc/ssh/sshd_config
RUN echo PermitEmptyPasswords yes >> $TMPDIR/etc/ssh/sshd_config
RUN sed -i '/PermitRootLogin/d' $TMPDIR/etc/ssh/sshd_config
RUN echo PermitRootLogin yes >> $TMPDIR/etc/ssh/sshd_config
# network workaround
RUN chmod +x $TMPDIR/etc/rc.local
RUN echo 'dhclient' >> $TMPDIR/etc/rc.local
ENV IMAGEDIR=/tmp/image
ENV IMAGE=/shared/out_of_tree_centos_8.img
RUN mkdir $IMAGEDIR
# Must be executed with --privileged because of /dev/loop
CMD qemu-img create $IMAGE 2G && \
mkfs.ext4 -F $IMAGE && \
mount -o loop $IMAGE $IMAGEDIR && \
cp -a $TMPDIR/* $IMAGEDIR/ && \
umount $IMAGEDIR

View File

@ -0,0 +1,6 @@
#!/bin/sh
cd "$(dirname "$0")"
sudo docker build -t gen-centos8-image .
sudo docker run --privileged -v $(pwd):/shared -t gen-centos8-image
tar -Szcf out_of_tree_centos_8.img.tar.gz out_of_tree_centos_8.img

View File

@ -25,7 +25,8 @@ ENV RELEASE=trusty
RUN mkdir $IMAGEDIR RUN mkdir $IMAGEDIR
# Must be executed with --privileged because of /dev/loop # Must be executed with --privileged because of /dev/loop
CMD debootstrap --include=openssh-server $RELEASE $TMPDIR $REPOSITORY && \ CMD debootstrap --include=openssh-server,policykit-1 \
$RELEASE $TMPDIR $REPOSITORY && \
/shared/setup.sh $TMPDIR && \ /shared/setup.sh $TMPDIR && \
qemu-img create $IMAGE 2G && \ qemu-img create $IMAGE 2G && \
mkfs.ext4 -F $IMAGE && \ mkfs.ext4 -F $IMAGE && \

View File

@ -4,29 +4,30 @@
# #
# Usage: # Usage:
# #
# $ docker build -t gen-ubuntu1804-image . # $ docker build -t gen-ubuntu2204-image .
# $ docker run --privileged -v $(pwd):/shared -t gen-ubuntu1804-image # $ docker run --privileged -v $(pwd):/shared -t gen-ubuntu2204-image
# #
# ubuntu1804.img will be created in current directory. You can change $(pwd) to # ubuntu2204.img will be created in current directory. You can change $(pwd) to
# different directory to use different destination for image. # different directory to use different destination for image.
# #
FROM ubuntu:18.04 FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive ENV DEBIAN_FRONTEND=noninteractive
RUN apt update RUN apt update
RUN apt install -y debootstrap qemu RUN apt install -y debootstrap qemu-utils
RUN apt install -y linux-image-generic RUN apt install -y linux-image-generic
ENV TMPDIR=/tmp/ubuntu ENV TMPDIR=/tmp/ubuntu
ENV IMAGEDIR=/tmp/image ENV IMAGEDIR=/tmp/image
ENV IMAGE=/shared/ubuntu1804.img ENV IMAGE=/shared/ubuntu2204.img
ENV REPOSITORY=http://archive.ubuntu.com/ubuntu ENV REPOSITORY=http://archive.ubuntu.com/ubuntu
ENV RELEASE=bionic ENV RELEASE=jammy
RUN mkdir $IMAGEDIR RUN mkdir $IMAGEDIR
# Must be executed with --privileged because of /dev/loop # Must be executed with --privileged because of /dev/loop
CMD debootstrap --include=openssh-server $RELEASE $TMPDIR $REPOSITORY && \ CMD debootstrap --include=openssh-server,policykit-1 \
$RELEASE $TMPDIR $REPOSITORY && \
/shared/setup.sh $TMPDIR && \ /shared/setup.sh $TMPDIR && \
qemu-img create $IMAGE 2G && \ qemu-img create $IMAGE 2G && \
mkfs.ext4 -F $IMAGE && \ mkfs.ext4 -F $IMAGE && \

View File

@ -1,9 +1,9 @@
#!/bin/sh -eux #!/bin/sh -eux
cd $(dirname $(realpath $0)) cd $(dirname $(realpath $0))
docker build -t gen-ubuntu1804-image . docker build -t gen-ubuntu2204-image .
docker run --privileged -v $(pwd):/shared -t gen-ubuntu1804-image docker run --privileged -v $(pwd):/shared -t gen-ubuntu2204-image
RUN="docker run -v $(pwd):/shared -t gen-ubuntu1804-image" RUN="docker run -v $(pwd):/shared -t gen-ubuntu2204-image"
$RUN sh -c 'chmod 644 /vmlinuz && cp /vmlinuz /shared/ubuntu1804.vmlinuz' $RUN sh -c 'chmod 644 /boot/vmlinuz && cp /boot/vmlinuz /shared/ubuntu2204.vmlinuz'
$RUN sh -c 'cp /initrd.img /shared/ubuntu1804.initrd' $RUN sh -c 'cp /boot/initrd.img /shared/ubuntu2204.initrd'
$RUN sh -c 'cp $(find /lib/modules -name test_static_key_base.ko) /shared/ubuntu1804.ko' $RUN sh -c 'cp $(find /lib/modules -name test_bpf.ko) /shared/ubuntu2204.ko'