207 コミット
v0.2 ... v1.3.0

作成者 SHA1 メッセージ 日付
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
3e7c564a5a Exclude host kernel generation for macOS 2019-08-31 08:05:43 +00:00
dc73413114 Set version 2019-08-30 17:40:01 +00:00
104e70f861 Implements global timeout 2019-08-30 16:34:14 +00:00
365c9d0e95 Implements reliablity threshold for exit code 2019-08-30 16:33:43 +00:00
5bad772125 Support custom docker commands
Resolves #17
2019-08-30 00:05:50 +00:00
f3b0c07af2 Implements parameter for setting up docker registry server 2019-08-29 22:49:59 +00:00
f3d67cc3c2 Update CHANGELOG 2019-08-29 22:29:23 +00:00
12b5bd2a99 Introduce global configuration file 2019-08-29 21:12:24 +00:00
b05c44ab9d Travis-CI: migrate to travis-ci.com 2019-08-28 03:18:37 +00:00
19535fc75c Do not produce error if existing 2019-08-23 10:46:37 +00:00
5e6a9dec93 Add rootfs generator for Ubuntu 14.04 2019-08-23 10:44:16 +00:00
0f89a868bd Add brief description 2019-08-21 08:08:35 +00:00
14b8010fee Fix spelling 2019-08-21 06:16:25 +00:00
7fd8614e3c Using sed to fix spelling was not a really good idea.
Revert "Fix spelling"

This reverts commit 3d958c1e10.
2019-08-21 06:14:58 +00:00
3d958c1e10 Fix spelling 2019-08-20 23:17:23 +00:00
e4bed2a4c3 Set version 2019-08-20 22:55:56 +00:00
a9d4d64e30 Update changelog 2019-08-20 21:58:42 +00:00
a8b423cddf Troubleshooting 2019-08-20 21:55:40 +00:00
df5d226772 Note about CentOS kernels 2019-08-20 19:22:47 +00:00
72bb8df46b Support CentOS kernels
Resolves #8
2019-08-20 18:58:08 +00:00
1ffd68601c Remove bootstrap, download images on-demand 2019-08-20 09:09:38 +00:00
86ad71f230 Install libseccomp-dev only for 14.04 and later 2019-08-20 07:06:08 +00:00
a08861cc19 Implements KPTI flag 2019-08-20 00:05:19 +00:00
24b2123582 More flexible way to change smep/smap/kaslr while debug 2019-08-19 23:02:34 +00:00
01d6c89d60 Apply qemu cpu/memory settings from artifact config 2019-08-19 22:43:08 +00:00
08ed3461ad Use smep/smap settings from artifact config as default value 2019-08-19 22:37:27 +00:00
d425f455bb Make variable names more toml-friendly 2019-08-19 19:03:59 +00:00
857f398f6b MarshalTOML for Duration 2019-08-19 19:01:29 +00:00
282d99f511 Allow to force some qemu settings from .out-of-tree.toml 2019-08-19 18:34:13 +00:00
338300eeec CPU model 'host' requires KVM 2019-08-19 17:07:29 +00:00
e106eaa3e0 Add libseccomp-dev to ubuntu base layer 2019-08-19 17:07:03 +00:00
89305b7011 Use all host cpu features 2019-08-19 16:16:17 +00:00
54a3704bc2 Do not clean dmesg 2019-08-19 16:15:55 +00:00
f3d932e100 Lowercase donate badges 2019-08-18 18:00:29 +00:00
0daf31e3aa Initial implementation of exploit pack testing 2019-08-18 17:49:11 +00:00
c0aeb01ff7 Test.sh fallback 2019-08-18 15:05:21 +00:00
ddf2fc0d0b Check kvm permissions 2019-08-18 12:53:13 +00:00
735256ff13 Travis-CI: xenial -> bionic 2019-08-18 11:33:58 +00:00
ea5f06334c Fix regexp 2019-08-17 15:30:42 +00:00
f847f0a773 Text wrap 2019-08-17 15:13:21 +00:00
e5856c1931 Implements non-regex way to set kernel version 2019-08-17 15:12:48 +00:00
faf9f9fd8f Do not show warning if test.sh is not exists 2019-08-17 12:42:14 +00:00
6ee5530554 Refactor 2019-08-17 10:04:45 +00:00
844f5a5580 Refactor 2019-08-17 10:00:01 +00:00
1fdf92cc6b Typo 2019-08-17 09:47:13 +00:00
aa08b7a8b2 Fix tests 2019-08-17 09:38:42 +00:00
73b39b5c0d Refactor 2019-08-17 09:35:36 +00:00
b654fb29b9 Fix markdown identation 2019-08-17 09:26:45 +00:00
927fcddebf Add missed va_end call 2019-08-17 09:08:58 +00:00
986a6f55e0 Refactor [2] 2019-08-17 09:05:06 +00:00
e0c0d3a072 Refactor 2019-08-17 08:51:42 +00:00
5ad41bc1c8 Update changelog for next release 2019-08-17 01:30:29 +00:00
b3b1ddcb7d Documentation: fix lists 2019-08-17 00:38:27 +00:00
3789da0579 Documentation: brief introduction and installation steps 2019-08-17 00:33:50 +00:00
c1c5fd4f16 Change bitcoin link 2019-08-16 22:34:37 +00:00
2c341076a0 Add bitcoin donation button 2019-08-16 22:04:46 +00:00
4a55957edb Add donate button 2019-08-16 21:44:16 +00:00
fb750a93e4 Add readthedocs badge 2019-08-16 20:51:45 +00:00
75f9436482 Documentation: Fix heading [2] 2019-08-16 20:45:37 +00:00
7a689d942a Documentation: Fix heading 2019-08-16 20:44:35 +00:00
a4ac4ff798 Init docs 2019-08-16 20:43:15 +00:00
56032241a0 Typo 2019-08-16 20:36:46 +00:00
09087d066f Add CHANGELOG 2019-08-16 20:33:56 +00:00
eb9ed90571 Generate markdown table statistics for tag 2019-08-16 20:11:35 +00:00
f7c884e4f8 Generate json statistics for tag 2019-08-16 19:31:58 +00:00
c2481272e2 Revert "Travis-CI: Test also without go modules"
This reverts commit a0a9333385.
2019-08-16 18:50:47 +00:00
085690697d Flag for number of runs per each kernel 2019-08-16 18:50:34 +00:00
574d5d45c3 Implements tagging 2019-08-16 18:30:46 +00:00
ddec4adf57 Command line flag for changing number of threads 2019-08-16 18:03:30 +00:00
b7d785f0c8 Add TODO regarding kernel source 2019-08-16 16:32:26 +00:00
53a80743ba Use more meaningful name for artifacts 2019-08-16 15:39:40 +00:00
3064dc3a27 Auto detect debug symbols for host kernels 2019-08-16 15:34:10 +00:00
fc50808893 Allow to enable/disable kaslr/smep/smap for debugging 2019-08-16 15:25:16 +00:00
a0a9333385 Travis-CI: Test also without go modules 2019-08-16 14:08:31 +00:00
f2b32d1e27 Update go modules 2019-08-16 14:07:39 +00:00
d035e4f8ad Implements build on host and support for custom kernels
Resolves #6
2019-08-16 14:05:26 +00:00
15a8c6b1e4 Move tmp to avoid tmpfs-related issues with docker 2019-08-16 12:43:29 +00:00
89c3175de4 Add file names to log output 2019-08-16 00:04:57 +00:00
35dfe2a361 Add qemu stdout/stderr to log; Implements database versioning 2019-08-14 22:59:34 +00:00
8430eea47f Implements saving build output
Resolves #9
2019-08-14 17:36:36 +00:00
ecf55a0cdf Fix bootstrap 2019-08-14 13:15:04 +00:00
b7624f0d28 Implements query for build/insmod/test logs 2019-08-13 23:54:45 +00:00
5ed23ee2b0 Show also ID of log entry 2019-08-13 23:39:14 +00:00
9175305cb9 Implements basic logs query, success rate calculation
Closes #10
Closes #11
2019-08-13 23:33:52 +00:00
94be33f869 Refactor 2019-08-13 22:17:24 +00:00
6cebd85535 Missed file for 51fa0851 2019-08-13 22:09:40 +00:00
e63bfa24e9 Remove outdated TODO 2019-08-13 21:58:11 +00:00
23be05969d Change email 2019-08-13 21:57:01 +00:00
51fa085170 Store logs in sqlite3 database 2019-08-13 21:54:59 +00:00
caee1b5756 Implements parameter "--max=X" for pew 2019-08-12 23:21:38 +00:00
5dbbb33297 Implements parameter "--max=X" for autogen 2019-08-12 22:58:47 +00:00
238592e546 Typo 2019-07-12 17:11:16 +00:00
6156947406 Make docker permissions errors more obvious 2019-07-10 22:08:04 +00:00
133b7a9b03 Merge pull request #16 from codacy-badger/codacy-badge
Add a Codacy badge to README.md
2019-06-20 10:12:27 +00:00
a83acbae8b Add Codacy badge 2019-06-20 10:11:30 +00:00
5864109080 Do not fail for kernel module if there is no test script 2019-06-15 06:58:17 +00:00
75f5636d31 Avoid errors in case when no kernels in container 2019-06-15 06:48:53 +00:00
56cdad74b3 Revert "CI: Test also without go modules to catch upstream API changes"
This reverts commit c680099801.
2019-06-11 00:14:23 +00:00
c680099801 CI: Test also without go modules to catch upstream API changes 2019-06-10 23:56:31 +00:00
94706ea8e7 gofmt 2019-06-10 18:55:10 +00:00
24de060a13 Use same version of aurora everywhere 2019-06-10 18:50:30 +00:00
27090f674a Update go modules 2019-06-10 18:35:55 +00:00
80b3ae6912 Merge pull request #14 from chiveson/patch-1
fix dependens in pew.go
2019-06-10 18:28:40 +00:00
7d6806846d Update pew.go 2019-06-10 20:21:34 +03:00
c12daaa1d6 Travis-CI: fix bootstrap.sh path 2019-02-06 06:41:26 +00:00
e0c91f1b59 Travis-CI: use xenial 2019-02-06 06:34:21 +00:00
cf75f4424d Use relative path for bootstrap script 2019-02-06 06:29:08 +00:00
c3af494fa8 Fix bootstrap.sh path 2019-02-06 06:07:21 +00:00
92484bf1d7 Travis-CI: go modules support 2019-02-02 22:15:32 +00:00
983201bb7a Go modules support 2019-02-02 22:04:44 +00:00
b5965e8374 Rename qemu package 2019-02-02 21:33:27 +00:00
144a8547bc Move upstream to code.dumpstack.io 2019-02-02 21:25:09 +00:00
fb9b03029b Update README.md 2018-12-16 23:50:58 +00:00
2e6b983a84 Add requirements for Fedora 2018-12-16 23:45:36 +00:00
ddf9c90ead Fix typo 2018-12-15 15:24:04 +00:00
42bebad9ca Add comments for exported functions 2018-12-10 03:06:26 +00:00
556ead8594 Avoid ineffectual assignment 2018-12-10 03:02:11 +00:00
630f6c7fe1 Move all kernels generation to separate function 2018-12-10 03:00:26 +00:00
094f209791 Remove redundant return statement 2018-12-10 02:56:22 +00:00
d42474892c Reduce main func complexity 2018-12-10 02:51:15 +00:00
18a92703ba Move to fmt.Errorf 2018-12-10 02:45:17 +00:00
e0f0133d42 Refactor: use only one exit for func 2018-12-10 02:41:45 +00:00
1dace23475 Drop useless else block 2018-12-10 02:39:55 +00:00
0b6ae6a23c Refactor 2018-12-10 02:37:40 +00:00
597de7f8c4 Refactor variable names 2018-12-10 02:35:43 +00:00
051d080a67 Typo 2018-12-10 02:34:01 +00:00
1f35eb165d if-else -> switch 2018-12-10 02:32:13 +00:00
db27959c8b Use keyed fields 2018-12-09 22:50:37 +00:00
880af47cc5 Use go timers for kill docker by timeout, fixes #12 2018-12-08 02:53:29 +00:00
49b567cd4b Add test for docker command with timeout 2018-12-08 02:32:44 +00:00
2a3c3ed18e Non-zero exit status if something failed 2018-12-07 03:14:24 +00:00
4dd34fec1d Update README.md 2018-12-02 22:29:06 +02:00
076a5babb9 Fix typo 2018-12-02 20:25:33 +00:00
bc4129e01c Do not check for kernels config exist 2018-12-02 20:23:48 +00:00
6b0301ec45 Do not exit if kernels config does not exists 2018-12-02 20:20:59 +00:00
825d69b770 Implements command for generate all kernels for distro/version 2018-12-02 19:54:09 +00:00
3fdb2736c8 Remove kernel factory 2018-12-02 19:49:27 +00:00
257ff0cb7f Remove snap support 2018-12-02 15:33:13 +00:00
f8b3f5fbaf Update source tree for build 2018-12-02 12:15:39 +00:00
5682dd99c1 Do not run bootstrap automatically, but suggest it to user 2018-12-02 03:39:54 +00:00
cf0e5efe18 Move bootstrap before kernels check 2018-12-02 03:19:41 +00:00
b459f91a22 Run bootstrap automatically on first run 2018-12-02 03:16:34 +00:00
bda5a5764a Improve logging 2018-12-02 03:13:18 +00:00
b0d2c99246 Check for qemu-system-x86_64 not qemu 2018-12-02 03:10:01 +00:00
6188043cef Update README.md 2018-12-02 05:04:15 +02:00
cb93a7df40 Snap: stable/classic 2018-12-02 02:55:56 +00:00
3695e50f35 Update README.md 2018-12-02 04:29:58 +02:00
287ce68c6e Support snap 2018-12-02 02:26:30 +00:00
7199854f44 Travis-CI: add last stable go 1.x version 2018-12-02 00:53:36 +00:00
ec17f97881 Update README.md 2018-12-02 02:27:07 +02:00
b56d718f35 Fix typo 2018-12-02 00:11:06 +00:00
ce4a5c740d Travis-CI: increase timeouts 2018-12-02 00:09:35 +00:00
f6eb95abc0 Travis-CI: disable parallel tests 2018-12-01 23:51:12 +00:00
6e77fc230d Travis-CI: fix current directory for tests 2018-12-01 23:24:47 +00:00
9fc1eff305 Travis-CI: bootstrap qemu images for tests 2018-12-01 23:14:35 +00:00
278c95f55e Travis-CI: add qemu && docker, disable macOS build 2018-12-01 23:04:29 +00:00
e25d5de854 Travis-CI: build for linux and macOS 2018-12-01 22:55:04 +00:00
291715cbf8 Add basic travis-ci configuration 2018-12-01 22:50:16 +00:00
dcc156272c Make executable only if test copied successfully 2018-12-01 20:33:06 +00:00
1488d4b081 Implements fallback if rootfs image not found 2018-12-01 20:18:43 +00:00
b2f50efa2a Add note about PATH/GOPATH 2018-12-01 18:15:34 +00:00
b36956c7a4 Bump version in source code 2018-12-01 18:12:44 +00:00
8225f0044d Add requirements 2018-12-01 17:58:16 +00:00
51個のファイルの変更2998行の追加509行の削除

25
.github/workflows/donate.yml vendored ノーマルファイル
ファイルの表示

@ -0,0 +1,25 @@
name: donate
on:
issues:
types: [opened, closed]
schedule:
- cron: '15 * * * *' # for updating balance
jobs:
comment:
runs-on: ubuntu-latest
steps:
- env:
# https://github.com/jollheef/donate/blob/master/dashboard/whitelist.go
DASHBOARD_ACCESS_TOKEN: ${{ secrets.DONATE_DASHBOARD_ACCESS_TOKEN }}
# the scope is current repository only
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TOOL: "https://github.com/jollheef/donate/archive/master.tar.gz"
run: |
curl https://nixos.org/nix/install | sh
. ~/.nix-profile/etc/profile.d/nix.sh
# Use latest stable nixpkgs channel
nix-channel --add https://nixos.org/channels/nixos-19.09 nixpkgs
nix-channel --update
nix run -f $TOOL -c donate-ci

13
.github/workflows/macos.yml vendored ノーマルファイル
ファイルの表示

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

56
.github/workflows/ubuntu.yml vendored ノーマルファイル
ファイルの表示

@ -0,0 +1,56 @@
name: Ubuntu
on: [push]
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
- 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
- name: End-to-End Testing [Kernel Module]
run: |
cd examples/kernel-module
../../out-of-tree kernel autogen --max=1
../../out-of-tree pew --qemu-timeout=10m
- name: End-to-End Testing [Kernel Exploit]
run: |
cd examples/kernel-exploit
../../out-of-tree kernel autogen --max=1
../../out-of-tree pew --threshold=0 --qemu-timeout=10m

193
CHANGELOG.md ノーマルファイル
ファイルの表示

@ -0,0 +1,193 @@
# Changelog
[ISO 8601](https://xkcd.com/1179/).
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [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
### Fixed
- macOS support.
## [1.1.0] 2019-08-30
### Added
- Global configuration file (~/.out-of-tree/out-of-tree.toml) allow to
set up default values for settings.
- rootfs generator for Ubuntu 14.04.
- Parameter for setting up docker registry server.
- Support for (distro-specific) custom docker commands that will be
executed before the base template.
- Parameter for setting up a reliability threshold for exit code.
- Parameter for setting up global timeout, after which no new tasks
will be started.
### Fixed
- Spelling in output.
- Now kernel generation will not fail if there are no directory
/lib/modules inside the container.
## [1.0.0] 2019-08-20
### Added
- New parameter `--max=X` is added for `autogen` (generate kernels
base on `.out-of-tree.toml` definitions) and `pew` (automated
runs) and allows to specify a maximum number of runs per each
supported kernel in module/exploit definition.
- New command `genall` -- generate all kernels for specified
distro/version.
- All logs stores in sqlite3 database. Implemented specific commands
for making simple queries and export data to markdown and json.
- Implemented success rate calculation for previous runs.
- Save of build results supported by parameter `--dist` for `pew`.
- Support for generating kernels info from host system.
- Support for build on host.
- Support for custom kernels.
- Now debugging environment is automatically looking for debug
kernel on the host system.
- Added ability to enable/disable kaslr/smep/smap/kpti for debugging
by command line flags.
- New parameter `--threads=N` is added for `pew` and allows to
specify maximum number of threads that will be used for parallel
build/run/test.
- Tagging for runs. Tags write to log and can be used for
statistics.
- Added non-regex way to set kernel version in .out-of-tree.toml (see
examples).
- New command `pack` that perform tests in subdirectories.
- Added ability to disable kaslr/smep/smap/kpti for in artifact
definition.
- Added ability to change amount of memory/CPUs and set qemu timeout
in artifact definition (`.out-of-tree.toml`).
- Now images downloading while `kernel autogen`, bootstrap is not
required anymore.
- Support CentOS kernels.
### Changed
- Now if there's no base image found — out-of-tree will try to use
an image from closest previous version, e.g. image from Ubuntu
18.04 for Ubuntu 18.10.
- Kernel modules tests will not be failed if there are no tests
exists.
- Now *out-of-tree* will return negative error code if at least one
of the stage was failed.
- Project is switch to use Go modules.
- Now test.sh is used by default if copying is not implemented in
Makefile.
- dmesg is not cleaned before the start of module/exploit anymore.
- qemu/kvm will use all host cpu features.
### Removed
- *Kernel factory* is removed completely in favor of incremental
Dockerfiles.
- `bootstrap` is not doing anything anymore. It'll be removed in next
release.
### Fixed
- Command `timeout` is not required anymore.
- Errors is more meaningful.
- Temporary files is moved to `~/.out-of-tree/tmp/` to avoid docker
mounting issues on some systems.
## [0.2.0] - 2019-12-01
The main purpose of the release is to simplify installation.
### Changes
- All configuration moved to `~/.out-of-tree`.
- Now prebuilt images can be downloaded with bootstrap.
- Ability to generate kernels specific to .out-of-tree.toml in
current directory. So now there's no need to wait for several
hours for start work on specific kernel with module/exploit.
- Now there's no need to keep source tree and _out-of-tree_ can be
distributed in binary form.
- New command: **debug**. Creates interactive environment for kernel
module/exploit development. Still work-in-progress.
- No warning anymore if test.sh is not exists.
## [0.1.0] - 2019-11-20
Initial release that was never tagged.
Refer to state after first public release on ZeroNights 2018
([video](https://youtu.be/2tL7bbCdIio),
[slides](https://2018.zeronights.ru/wp-content/uploads/materials/07-Ways-to-automate-testing-Linux-kernel-exploits.pdf)).

ファイルの表示

@ -1,19 +1,36 @@
[![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)
[![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)
[![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 kernel {module, exploit} development tool
out-of-tree is for automating some routine actions for creating development environments for debugging kernel modules and exploits, generating reliability statistics for exploits, and also provides the ability to easily integrate into CI (Continuous Integration).
![Screenshot](https://cloudflare-ipfs.com/ipfs/Qmb88fgdDjbWkxz91sWsgmoZZNfVThnCtj37u3mF2s3T3T)
## Installation
$ go get github.com/jollheef/out-of-tree
$ out-of-tree bootstrap
### GNU/Linux (with [Nix](https://nixos.org/nix/))
Then you can check it on kernel module example:
$ curl -fsSL https://get.docker.com | sh
$ sudo usermod -aG docker user && newgrp docker
$ curl https://nixos.org/nix/install | sh
$ nix-env -iA nixpkgs.out-of-tree
$ cd $GOPATH/github.com/jollheef/out-of-tree/examples/kernel-module
$ out-of-tree kernel autogen # generate kernels based on .out-of-tree.toml
$ out-of-tree pew
Note that adding a user to group *docker* has serious security implications. Check Docker documentation for more information.
### macOS
$ brew cask install docker
$ open --background -a Docker && sleep 1m
$ brew tap jollheef/repo
$ brew install out-of-tree
Read [documentation](https://out-of-tree.readthedocs.io) for further info.
## Examples
@ -45,13 +62,15 @@ Use custom kernels config
$ out-of-tree --kernels /path/to/kernels.toml pew
## Generate all kernels
Generate all kernels
Does not required if you dont need to use `--guess`.
$ out-of-tree kernel genall --distro Ubuntu --ver 16.04
$ cd $GOPATH/src/github.com/jollheef/out-of-tree/tools/kernel-factory
$ ./bootstrap.sh # more than 6-8 hours for all kernels
$ export OUT_OF_TREE_KCFG=$GOPATH/src/github.com/jollheef/out-of-tree/tools/kernel-factory/output/kernels.toml
## 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
@ -59,6 +78,6 @@ Read [Qemu API](qemu/README.md).
### Generate images
$ cd $GOPATH/src/github.com/jollheef/out-of-tree/tools/qemu-debian-img/
$ 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

ファイルの表示

@ -1,7 +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
const imagesURL = "https://github.com/jollheef/out-of-tree/releases/download/v0.2/images.tar.gz"

ファイルの表示

@ -1,76 +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 (
"io"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"os/user"
)
// inspired by Edd Turtle code
func downloadFile(filepath string, url string) (err error) {
out, err := os.Create(filepath)
if err != nil {
return
}
defer out.Close()
resp, err := http.Get(url)
if err != nil {
return
}
defer resp.Body.Close()
_, err = io.Copy(out, resp.Body)
return
}
func unpackTar(archive, destination string) (err error) {
cmd := exec.Command("tar", "xf", archive)
cmd.Dir = destination + "/"
rawOutput, err := cmd.CombinedOutput()
if err != nil {
// I don't like when some errors printed inside
// So if you know way to do it better - FIXME please
log.Println("Unpack images error:", string(rawOutput), err)
return
}
return
}
func bootstrapHandler() (err error) {
usr, err := user.Current()
if err != nil {
return
}
imagesPath := usr.HomeDir + "/.out-of-tree/images/"
os.MkdirAll(imagesPath, os.ModePerm)
tmp, err := ioutil.TempDir("/tmp/", "out-of-tree_")
if err != nil {
log.Println("Temporary directory creation error:", err)
return
}
defer os.RemoveAll(tmp)
imagesArchive := tmp + "/images.tar.gz"
err = downloadFile(imagesArchive, imagesURL)
if err != nil {
log.Println("Download file error:", err)
return
}
err = unpackTar(imagesArchive, imagesPath)
return
}

ファイルの表示

@ -10,27 +10,44 @@ import (
"io/ioutil"
"os"
"regexp"
"strconv"
"strings"
"time"
"github.com/naoina/toml"
)
type kernel struct {
Version []int
Major []int
Minor []int
Patch []int
}
// KernelMask defines the kernel
type KernelMask struct {
DistroType DistroType
DistroRelease string // 18.04/7.4.1708/9.1
ReleaseMask string
// Overrides ReleaseMask
Kernel kernel
}
// DockerName is returns stable name for docker container
func (km KernelMask) DockerName() string {
distro := strings.ToLower(km.DistroType.String())
release := strings.Replace(km.DistroRelease, ".", "__", -1)
return fmt.Sprintf("out_of_tree_%s_%s", distro, release)
}
// ArtifactType is the kernel module or exploit
type ArtifactType int
const (
// KernelModule is any kind of kernel module
KernelModule ArtifactType = iota
// KernelExploit is the privilege escalation exploit
KernelExploit
)
@ -38,6 +55,7 @@ func (at ArtifactType) String() string {
return [...]string{"module", "exploit"}[at]
}
// UnmarshalTOML is for support github.com/naoina/toml
func (at *ArtifactType) UnmarshalTOML(data []byte) (err error) {
stype := strings.Trim(string(data), `"`)
stypelower := strings.ToLower(stype)
@ -46,11 +64,12 @@ func (at *ArtifactType) UnmarshalTOML(data []byte) (err error) {
} else if strings.Contains(stypelower, "exploit") {
*at = KernelExploit
} else {
err = errors.New(fmt.Sprintf("Type %s is unsupported", stype))
err = fmt.Errorf("Type %s is unsupported", stype)
}
return
}
// MarshalTOML is for support github.com/naoina/toml
func (at ArtifactType) MarshalTOML() (data []byte, err error) {
s := ""
switch at {
@ -59,17 +78,49 @@ func (at ArtifactType) MarshalTOML() (data []byte, err error) {
case KernelExploit:
s = "exploit"
default:
err = errors.New(fmt.Sprintf("Cannot marshal %d", at))
err = fmt.Errorf("Cannot marshal %d", at)
}
data = []byte(`"` + s + `"`)
return
}
// Duration type with toml unmarshalling support
type Duration struct {
time.Duration
}
// UnmarshalTOML for Duration
func (d *Duration) UnmarshalTOML(data []byte) (err error) {
duration := strings.Replace(string(data), "\"", "", -1)
d.Duration, err = time.ParseDuration(duration)
return
}
// MarshalTOML for Duration
func (d Duration) MarshalTOML() (data []byte, err error) {
data = []byte(`"` + d.Duration.String() + `"`)
return
}
// Artifact is for .out-of-tree.toml
type Artifact struct {
Name string
Type ArtifactType
SourcePath string
SupportedKernels []KernelMask
Qemu struct {
Cpus int
Memory int
Timeout Duration
}
Mitigations struct {
DisableSmep bool
DisableSmap bool
DisableKaslr bool
DisableKpti bool
}
}
func (ka Artifact) checkSupport(ki KernelInfo, km KernelMask) (
@ -90,6 +141,7 @@ func (ka Artifact) checkSupport(ki KernelInfo, km KernelMask) (
return
}
// Supported returns true if given kernel is supported by artifact
func (ka Artifact) Supported(ki KernelInfo) (supported bool, err error) {
for _, km := range ka.SupportedKernels {
supported, err = ka.checkSupport(ki, km)
@ -101,16 +153,22 @@ func (ka Artifact) Supported(ki KernelInfo) (supported bool, err error) {
return
}
// DistroType is enum with all supported distros
type DistroType int
const (
// Ubuntu https://ubuntu.com/
Ubuntu DistroType = iota
// CentOS https://www.centos.org/
CentOS
// Debian https://www.debian.org/
Debian
)
// DistroTypeStrings is the string version of enum DistroType
var DistroTypeStrings = [...]string{"Ubuntu", "CentOS", "Debian"}
// NewDistroType is create new Distro object
func NewDistroType(dType string) (dt DistroType, err error) {
err = dt.UnmarshalTOML([]byte(dType))
return
@ -120,6 +178,7 @@ func (dt DistroType) String() string {
return DistroTypeStrings[dt]
}
// UnmarshalTOML is for support github.com/naoina/toml
func (dt *DistroType) UnmarshalTOML(data []byte) (err error) {
sDistro := strings.Trim(string(data), `"`)
if strings.EqualFold(sDistro, "Ubuntu") {
@ -129,11 +188,12 @@ func (dt *DistroType) UnmarshalTOML(data []byte) (err error) {
} else if strings.EqualFold(sDistro, "Debian") {
*dt = Debian
} else {
err = errors.New(fmt.Sprintf("Distro %s is unsupported", sDistro))
err = fmt.Errorf("Distro %s is unsupported", sDistro)
}
return
}
// MarshalTOML is for support github.com/naoina/toml
func (dt DistroType) MarshalTOML() (data []byte, err error) {
s := ""
switch dt {
@ -144,12 +204,20 @@ func (dt DistroType) MarshalTOML() (data []byte, err error) {
case Debian:
s = "Debian"
default:
err = errors.New(fmt.Sprintf("Cannot marshal %d", dt))
err = fmt.Errorf("Cannot marshal %d", dt)
}
data = []byte(`"` + s + `"`)
return
}
// ByRootFS is sorting by .RootFS lexicographically
type ByRootFS []KernelInfo
func (a ByRootFS) Len() int { return len(a) }
func (a ByRootFS) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByRootFS) Less(i, j int) bool { return a[i].RootFS < a[j].RootFS }
// KernelInfo defines kernels.toml entries
type KernelInfo struct {
DistroType DistroType
DistroRelease string // 18.04/7.4.1708/9.1
@ -158,14 +226,19 @@ type KernelInfo struct {
KernelRelease string
// Build-time information
KernelSource string // module/exploit will be build on host
ContainerName string
// Runtime information
KernelPath string
InitrdPath string
RootFS string
// Debug symbols
VmlinuxPath string
}
// KernelConfig is the ~/.out-of-tree/kernels.toml configuration description
type KernelConfig struct {
Kernels []KernelInfo
}
@ -181,6 +254,7 @@ func readFileAll(path string) (buf []byte, err error) {
return
}
// ReadKernelConfig is for read kernels.toml
func ReadKernelConfig(path string) (kernelCfg KernelConfig, err error) {
buf, err := readFileAll(path)
if err != nil {
@ -195,16 +269,93 @@ func ReadKernelConfig(path string) (kernelCfg KernelConfig, err error) {
return
}
func ReadArtifactConfig(path string) (artifactCfg Artifact, err error) {
func rangeRegexp(start, end int) (s string) {
s += "("
for i := start; i <= end; i++ {
s += strconv.Itoa(i)
if i != end {
s += "|"
}
}
s += ")"
return
}
func versionRegexp(l []int) (s string, err error) {
switch len(l) {
case 1:
s += strconv.Itoa(l[0])
case 2:
s += rangeRegexp(l[0], l[1])
default:
err = errors.New("version must contain one value or range")
return
}
return
}
func genReleaseMask(km kernel) (mask string, err error) {
s, err := versionRegexp(km.Version)
if err != nil {
return
}
mask += s + "[.]"
s, err = versionRegexp(km.Major)
if err != nil {
return
}
mask += s + "[.]"
s, err = versionRegexp(km.Minor)
if err != nil {
return
}
mask += s
switch len(km.Patch) {
case 0:
// ok
case 1:
mask += "-" + strconv.Itoa(km.Patch[0]) + "-"
case 2:
mask += "-" + rangeRegexp(km.Patch[0], km.Patch[1]) + "-"
default:
err = errors.New("version must contain one value or range")
return
}
mask += ".*"
return
}
// ReadArtifactConfig is for read .out-of-tree.toml
func ReadArtifactConfig(path string) (ka Artifact, err error) {
buf, err := readFileAll(path)
if err != nil {
return
}
err = toml.Unmarshal(buf, &artifactCfg)
err = toml.Unmarshal(buf, &ka)
if err != nil {
return
}
for i, _ := range ka.SupportedKernels {
km := &ka.SupportedKernels[i]
if len(km.Kernel.Version) != 0 && km.ReleaseMask != "" {
s := "Only one way to define kernel version is allowed"
err = errors.New(s)
return
}
if km.ReleaseMask == "" {
km.ReleaseMask, err = genReleaseMask(km.Kernel)
if err != nil {
return
}
}
}
return
}

ファイルの表示

@ -16,7 +16,7 @@ func TestMarshalUnmarshal(t *testing.T) {
Type: KernelModule,
}
artifactCfg.SupportedKernels = append(artifactCfg.SupportedKernels,
KernelMask{Ubuntu, "18.04", ".*"})
KernelMask{Ubuntu, "18.04", ".*", kernel{}})
buf, err := toml.Marshal(&artifactCfg)
if err != nil {
t.Fatal(err)
@ -28,3 +28,38 @@ func TestMarshalUnmarshal(t *testing.T) {
t.Fatal(err)
}
}
func TestKernelRegex(t *testing.T) {
mask := "4[.]4[.]0-(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59|60|61|62|63|64|65|66|67|68|69|70|71|72|73|74|75|76|77|78|79|80|81|82|83|84|85|86|87|88|89|90|91|92|93|94|95|96|97|98|99|100|101|102|103|104|105|106|107|108|109|110|111|112|113|114|115|116)-.*"
k := kernel{
Version: []int{4},
Major: []int{4},
Minor: []int{0},
Patch: []int{1, 116},
}
gmask, err := genReleaseMask(k)
if err != nil {
t.Fatal(err)
}
if mask != gmask {
t.Fatal("Got", gmask, "instead of", mask)
}
mask = "4[.]4[.]0.*"
k = kernel{
Version: []int{4},
Major: []int{4},
Minor: []int{0},
}
gmask, err = genReleaseMask(k)
if err != nil {
t.Fatal(err)
}
if mask != gmask {
t.Fatal("Got", gmask, "instead of", mask)
}
}

77
config/out-of-tree.go ノーマルファイル
ファイルの表示

@ -0,0 +1,77 @@
// Copyright 2019 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 config
import (
"os/user"
"github.com/naoina/toml"
)
type DockerCommand struct {
DistroType DistroType
Command string
}
type OutOfTree struct {
Kernels string
UserKernels string
Database string
Qemu struct {
Timeout string
}
Docker struct {
Timeout string
Registry string
// Commands that will be executed before
// the base layer of Dockerfile
Commands []DockerCommand
}
}
func ReadOutOfTreeConf(path string) (c OutOfTree, err error) {
buf, err := readFileAll(path)
if err == nil {
err = toml.Unmarshal(buf, &c)
if err != nil {
return
}
} else {
// It's ok if there's no configuration
// then we'll just set default values
err = nil
}
usr, err := user.Current()
if err != nil {
return
}
if c.Kernels == "" {
c.Kernels = usr.HomeDir + "/.out-of-tree/kernels.toml"
}
if c.UserKernels == "" {
c.UserKernels = usr.HomeDir + "/.out-of-tree/kernels.user.toml"
}
if c.Database == "" {
c.Database = usr.HomeDir + "/.out-of-tree/db.sqlite"
}
if c.Qemu.Timeout == "" {
c.Qemu.Timeout = "1m"
}
if c.Docker.Timeout == "" {
c.Docker.Timeout = "1m"
}
return
}

317
db.go ノーマルファイル
ファイルの表示

@ -0,0 +1,317 @@
// Copyright 2019 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 (
"database/sql"
"fmt"
"strconv"
"time"
_ "github.com/mattn/go-sqlite3"
"code.dumpstack.io/tools/out-of-tree/config"
"code.dumpstack.io/tools/out-of-tree/qemu"
)
// Change on ANY database update
const currentDatabaseVersion = 2
const versionField = "db_version"
type logEntry struct {
ID int
Tag string
Timestamp time.Time
qemu.System
config.Artifact
config.KernelInfo
phasesResult
}
func createLogTable(db *sql.DB) (err error) {
_, err = db.Exec(`
CREATE TABLE IF NOT EXISTS log (
id INTEGER PRIMARY KEY,
time DATETIME DEFAULT CURRENT_TIMESTAMP,
tag TEXT,
name TEXT,
type TEXT,
distro_type TEXT,
distro_release TEXT,
kernel_release TEXT,
build_output TEXT,
build_ok BOOLEAN,
run_output TEXT,
run_ok BOOLEAN,
test_output TEXT,
test_ok BOOLEAN,
qemu_stdout TEXT,
qemu_stderr TEXT,
kernel_panic BOOLEAN,
timeout_kill BOOLEAN
)`)
return
}
func createMetadataTable(db *sql.DB) (err error) {
_, err = db.Exec(`
CREATE TABLE IF NOT EXISTS metadata (
id INTEGER PRIMARY KEY,
key TEXT UNIQUE,
value TEXT
)`)
return
}
func metaChkValue(db *sql.DB, key string) (exist bool, err error) {
sql := "SELECT EXISTS(SELECT id FROM metadata WHERE key = $1)"
stmt, err := db.Prepare(sql)
if err != nil {
return
}
defer stmt.Close()
err = stmt.QueryRow(key).Scan(&exist)
return
}
func metaGetValue(db *sql.DB, key string) (value string, err error) {
stmt, err := db.Prepare("SELECT value FROM metadata " +
"WHERE key = $1")
if err != nil {
return
}
defer stmt.Close()
err = stmt.QueryRow(key).Scan(&value)
return
}
func metaSetValue(db *sql.DB, key, value string) (err error) {
stmt, err := db.Prepare("INSERT OR REPLACE INTO metadata " +
"(key, value) VALUES ($1, $2)")
if err != nil {
return
}
defer stmt.Close()
_, err = stmt.Exec(key, value)
return
}
func getVersion(db *sql.DB) (version int, err error) {
s, err := metaGetValue(db, versionField)
if err != nil {
return
}
version, err = strconv.Atoi(s)
return
}
func addToLog(db *sql.DB, q *qemu.System, ka config.Artifact,
ki config.KernelInfo, res *phasesResult, tag string) (err error) {
stmt, err := db.Prepare("INSERT INTO log (name, type, tag, " +
"distro_type, distro_release, kernel_release, " +
"build_output, build_ok, " +
"run_output, run_ok, " +
"test_output, test_ok, " +
"qemu_stdout, qemu_stderr, " +
"kernel_panic, timeout_kill) " +
"VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, " +
"$10, $11, $12, $13, $14, $15, $16);")
if err != nil {
return
}
defer stmt.Close()
_, err = stmt.Exec(
ka.Name, ka.Type, tag,
ki.DistroType, ki.DistroRelease, ki.KernelRelease,
res.Build.Output, res.Build.Ok,
res.Run.Output, res.Run.Ok,
res.Test.Output, res.Test.Ok,
q.Stdout, q.Stderr,
q.KernelPanic, q.KilledByTimeout,
)
if err != nil {
return
}
return
}
func getAllLogs(db *sql.DB, tag string, num int) (les []logEntry, err error) {
stmt, err := db.Prepare("SELECT id, time, name, type, tag, " +
"distro_type, distro_release, kernel_release, " +
"build_ok, run_ok, test_ok, kernel_panic, " +
"timeout_kill FROM log ORDER BY datetime(time) DESC " +
"LIMIT $1")
if err != nil {
return
}
defer stmt.Close()
rows, err := stmt.Query(num)
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
le := logEntry{}
err = rows.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.KernelPanic, &le.KilledByTimeout,
)
if err != nil {
return
}
if tag == "" || tag == le.Tag {
les = append(les, le)
}
}
return
}
func getAllArtifactLogs(db *sql.DB, tag string, num int, ka config.Artifact) (
les []logEntry, err error) {
stmt, err := db.Prepare("SELECT id, time, name, type, tag, " +
"distro_type, distro_release, kernel_release, " +
"build_ok, run_ok, test_ok, kernel_panic, " +
"timeout_kill FROM log WHERE name=$1 AND type=$2 " +
"ORDER BY datetime(time) DESC LIMIT $3")
if err != nil {
return
}
defer stmt.Close()
rows, err := stmt.Query(ka.Name, ka.Type, num)
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
le := logEntry{}
err = rows.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.KernelPanic, &le.KilledByTimeout,
)
if err != nil {
return
}
if tag == "" || tag == le.Tag {
les = append(les, le)
}
}
return
}
func getLogByID(db *sql.DB, id int) (le logEntry, err error) {
stmt, err := db.Prepare("SELECT 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 WHERE id=$1")
if err != nil {
return
}
defer stmt.Close()
err = stmt.QueryRow(id).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) {
err = createMetadataTable(db)
if err != nil {
return
}
err = createLogTable(db)
if err != nil {
return
}
return
}
func openDatabase(path string) (db *sql.DB, err error) {
db, err = sql.Open("sqlite3", path)
if err != nil {
return
}
db.SetMaxOpenConns(1)
exists, _ := metaChkValue(db, versionField)
if !exists {
err = createSchema(db)
if err != nil {
return
}
err = metaSetValue(db, versionField,
strconv.Itoa(currentDatabaseVersion))
return
}
version, err := getVersion(db)
if err != nil {
return
}
if version == 1 {
_, err = db.Exec(`ALTER TABLE log ADD tag TEXT`)
if err != nil {
return
}
err = metaSetValue(db, versionField, "2")
if err != nil {
return
}
version = 2
}
if version != currentDatabaseVersion {
err = fmt.Errorf("Database is not supported (%d instead of %d)",
version, currentDatabaseVersion)
return
}
return
}

ファイルの表示

@ -13,9 +13,10 @@ import (
"strings"
"time"
"github.com/jollheef/out-of-tree/config"
qemu "github.com/jollheef/out-of-tree/qemu"
"github.com/logrusorgru/aurora"
"gopkg.in/logrusorgru/aurora.v2"
"code.dumpstack.io/tools/out-of-tree/config"
"code.dumpstack.io/tools/out-of-tree/qemu"
)
func firstSupported(kcfg config.KernelConfig, ka config.Artifact,
@ -40,7 +41,7 @@ func firstSupported(kcfg config.KernelConfig, ka config.Artifact,
return
}
func handleLine(q *qemu.QemuSystem) (err error) {
func handleLine(q *qemu.System) (err error) {
fmt.Print("out-of-tree> ")
rawLine := "help"
fmt.Scanf("%s", &rawLine)
@ -63,7 +64,7 @@ func handleLine(q *qemu.QemuSystem) (err error) {
case "c", "cleanup":
q.Stdout = []byte{}
case "s", "ssh":
fmt.Println(q.GetSshCommand())
fmt.Println(q.GetSSHCommand())
case "q", "quit":
return errors.New("end of session")
default:
@ -72,7 +73,7 @@ func handleLine(q *qemu.QemuSystem) (err error) {
return
}
func interactive(q *qemu.QemuSystem) (err error) {
func interactive(q *qemu.System) (err error) {
for {
err = handleLine(q)
if err != nil {
@ -82,7 +83,8 @@ func interactive(q *qemu.QemuSystem) (err error) {
}
func debugHandler(kcfg config.KernelConfig, workPath, kernRegex, gdb string,
dockerTimeout time.Duration) (err error) {
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 {
@ -99,13 +101,67 @@ func debugHandler(kcfg config.KernelConfig, workPath, kernRegex, gdb string,
}
kernel := qemu.Kernel{KernelPath: ki.KernelPath, InitrdPath: ki.InitrdPath}
q, err := qemu.NewQemuSystem(qemu.X86_64, kernel, ki.RootFS)
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.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(gdb)
coloredGdbAddress := aurora.BgGreen(aurora.Black(gdb))
fmt.Printf("[*] gdb runned on %s\n", coloredGdbAddress)
fmt.Printf("[*] gdb is listening on %s\n", coloredGdbAddress)
err = q.Start()
if err != nil {
@ -125,9 +181,9 @@ func debugHandler(kcfg config.KernelConfig, workPath, kernRegex, gdb string,
return
}
remoteFile := "/tmp/artifact"
remoteFile := "/tmp/exploit"
if ka.Type == config.KernelModule {
remoteFile += ".ko"
remoteFile = "/tmp/module.ko"
}
err = q.CopyFile("user", outFile, remoteFile)
@ -138,6 +194,11 @@ func debugHandler(kcfg config.KernelConfig, workPath, kernRegex, gdb string,
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
}

30
docs/index.rst ノーマルファイル
ファイルの表示

@ -0,0 +1,30 @@
out-of-tree
===========
*out-of-tree* is the kernel {module, exploit} development tool.
*out-of-tree* was created on the purpose of decreasing complexity of
environment for developing, testing and debugging Linux kernel
exploits and out-of-tree kernel modules (that's why tool got a name
"out-of-tree").
While I'm trying to keep that documentation up-to-date, there may be
some missing information. Use ``out-of-tree --help-long`` for checking
all features.
If you found anything missed here, please make a pull request or send
patches to patch@dumpstack.io.
If you need personal support, your company is interested in the
project or you just want to share some thoughts -- feel free to write
to root@dumpstack.io.
Contents
========
:ref:`Keyword Index <genindex>`
.. toctree::
introduction.rst
installation.rst

78
docs/installation.rst ノーマルファイル
ファイルの表示

@ -0,0 +1,78 @@
Installation (from source)
============
OS/Distro-specific
==================
Ubuntu
------
Install dependencies::
$ sudo snap install go --classic
$ sudo snap install docker
$ sudo apt install qemu-system-x86 build-essential gdb
macOS
-----
Install dependencies::
$ brew install go qemu
$ brew cask install docker
NixOS
-----
There's a minimal configuration that you need to apply::
#!nix
{ config, pkgs, ... }:
{
virtualisation.docker.enable = true;
virtualisation.libvirtd.enable = true;
environment.systemPackages = with pkgs; [
go git
];
}
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
======
Setup Go environment::
$ echo 'export GOPATH=$HOME' >> ~/.bashrc
$ echo 'export PATH=$PATH:$HOME/bin' >> ~/.bashrc
$ source ~/.bashrc
Build *out-of-tree*::
$ go get -u code.dumpstack.io/tools/out-of-tree
.. note::
On a GNU/Linux you need to add your user to docker group if you want
to use *out-of-tree* without sudo. Note that this has a **serious**
security implications. Check *Docker* documentation for more
information.
Test that everything works::
$ cd $GOPATH/src/code.dumpstack.io/tools/out-of-tree/examples/kernel-exploit
$ out-of-tree kernel autogen --max=1
$ out-of-tree pew --max=1
Enjoy!

109
docs/introduction.rst ノーマルファイル
ファイルの表示

@ -0,0 +1,109 @@
Introduction
============
*out-of-tree* is written in *Go*, it uses *Docker* for generating
kernel/filesystem images and *Qemu* for virtualization.
Also it possible to generate kernels from the host system and use the
custom one.
*out-of-tree* supports *GNU/Linux* (usually it's tested on NixOS and
latest Ubuntu LTS) and *macOS*. Technically all systems that supported
by Go, Docker, and Qemu must work well. Create the issue if you'll
notice any issue in integration for your operating system.
All *Qemu* interaction is stateless.
*out-of-tree* is allow and require metadata (``.out-of-tree.toml``)
for work. TOML (Tom's Obvious, Minimal Language) is used for kernel
module/exploit description.
``.out-of-tree.toml`` is mandatory, you need to have in the current
directory (usually, it's a project of kernel module/exploit) or use
the ``--path`` flag.
Files
-----
All data is stored in ``~/.out-of-tree/``.
- *db.sqlite* contains logs related to run with ``out-of-tree pew``,
debug mode (``out-of-tree debug``) is not store any data.
- *images* used for filesystem images (rootfs images that used for
``qemu -hda ...``) that can be generated with the
``tools/qemu-*-img/...``.
- *kernels* stores all kernel ``vmlinuz/initrd/config/...`` files that
generated previously with a some *Docker magic*.
- *kernels.toml* contains metadata for generated kernels. It's not
supposed to be edited by hands.
- *kernels.user.toml* is default path for custom kernels definition.
- *Ubuntu* (or *Centos*/*Debian*/...) is the Dockerfiles tree
(DistroName/DistroVersion/Dockerfile). Each Dockerfile contains a
base layer and incrementally updated list of kernels that must be
installed.
Overview
---------
*out-of-tree* creating debugging environment based on **defined** kernels::
$ out-of-tree debug --kernel 'Ubuntu:4.15.0-58-generic'
[*] KASLR SMEP SMAP
[*] gdb is listening on tcp::1234
[*] build result copied to /tmp/exploit
ssh -o StrictHostKeyChecking=no -p 29308 root@127.133.45.236
gdb /usr/lib/debug/boot/vmlinux-4.15.0-58-generic -ex 'target remote tcp::1234'
out-of-tree> help
help : print this help message
log : print qemu log
clog : print qemu log and cleanup buffer
cleanup : cleanup qemu log buffer
ssh : print arguments to ssh command
quit : quit
out-of-tree>
*out-of-tree* uses three stages for automated runs:
- Build
- Inside the docker container (default).
- Binary version (de facto skip stage).
- On host.
- Run
- Insmod for the kernel module.
- This step is skipped for exploits.
- Test
- Run the test.sh script on the target machine.
- Test script is run from *root* for the kernel module.
- Test script is run from *user* for the kernel exploit.
- Test script for the kernel module is fully custom (only return
value is checked).
- Test script for the kernel exploit receives two parameters:
- Path to exploit
- Path to file that must be created with root privileges.
- If there's no test.sh script then default
(``echo touch FILE | exploit``) one is used.
Security
--------
*out-of-tree* is not supposed to be used on multi-user systems or with
an untrusted input.
Meanwhile, all modern hypervisors are supporting nested
virtualization, which means you can use it for isolating *out-of-tree*
if you want to work with an untrusted input (e.g. with a mass-scale
testing public proofs-of-concept).

ファイルの表示

@ -6,12 +6,12 @@ type = "exploit"
[[supported_kernels]]
distro_type = "Ubuntu"
distro_release = "16.04"
release_mask = "4.4.0-(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59|60|61|62|63|64|65|66|67|68|69|70|71|72|73|74|75|76|77|78|79|80|81|82|83|84|85|86|87|88|89|90|91|92|93|94|95|96|97|98|99|100|101|102|103|104|105|106|107|108|109|110|111|112|113|114|115|116)-.*"
release_mask = "4[.]4[.]0-(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59|60|61|62|63|64|65|66|67|68|69|70|71|72|73|74|75|76|77|78|79|80|81|82|83|84|85|86|87|88|89|90|91|92|93|94|95|96|97|98|99|100|101|102|103|104|105|106|107|108|109|110|111|112|113|114|115|116)-.*"
[[supported_kernels]]
distro_type = "Ubuntu"
distro_release = "16.04"
release_mask = "4.8.0-(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58)-.*"
release_mask = "4[.]8[.]0-(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58)-.*"
[[supported_kernels]]
# Can be Ubuntu/CentOS/Debian/etc.
@ -20,14 +20,19 @@ distro_release = "16.04"
# regex for `uname -r`
# See also: regex-golang.appspot.com
# stupid way to generate: $ echo '4.4.0-('$(seq 44 | xargs echo | sed 's/ /|/g')')-.*'
release_mask = "4.10.0-(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42)-.*"
release_mask = "4[.]10[.]0-(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42)-.*"
[[supported_kernels]]
distro_type = "Ubuntu"
distro_release = "16.04"
release_mask = "4.11.0-(1|2|3|4|5|6|7|8|9|10|11|12|13|14)-.*"
release_mask = "4[.]11[.]0-(1|2|3|4|5|6|7|8|9|10|11|12|13|14)-.*"
[[supported_kernels]]
distro_type = "Ubuntu"
distro_release = "16.04"
release_mask = "4.13.0-(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21)-.*"
# equivalent for "4[.]13[.]0-(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21)-.*"
[supported_kernels.kernel]
version = [ 4 ]
major = [ 13 ]
minor = [ 0 ]
patch = [ 1, 21 ]

ファイルの表示

@ -317,6 +317,7 @@ void redact(const char *fmt, ...) {
va_start(args, fmt);
if(doredact) {
fprintf(stdout, "[!] ( ( R E D A C T E D ) )\n");
va_end(args);
return;
}
fprintf(stdout, "[*] ");

ファイルの表示

@ -2,7 +2,7 @@
# - KERNEL: kernel headers path
# - TARGET: name of exploit binary that MUST be produced by makefile.
# - $(TARGET)_test: name of test binary that MUST be produced by makefile
# and it's will be runned on a LPE stage. TARGET_TEST MUST accept two argument:
# and it's will be executed on a LPE stage. TARGET_TEST MUST accept two argument:
# - Path to exploit binary
# - File that MUST be created with exploit. It uses for test that exploit works
# correctly.

ファイルの表示

@ -9,17 +9,22 @@ distro_type = "Ubuntu"
distro_release = "16.04"
# regex for `uname -r`
# See also: regex-golang.appspot.com
release_mask = "4.4.0-70-.*"
release_mask = "4[.]4[.]0-70-.*"
# [[supported_kernels]] may be defined unlimited number of times
[[supported_kernels]]
distro_type = "Ubuntu"
distro_release = "18.04"
# Also you can use only one kernel
release_mask = "4.15.0-(24|29)-generic"
release_mask = "4[.]15[.]0-(24|29)-generic"
[[supported_kernels]]
distro_type = "Ubuntu"
distro_release = "18.04"
# Also you can use only one kernel
release_mask = "4.15.0-23-generic"
release_mask = "4[.]15[.]0-23-generic"
[[supported_kernels]]
distro_type = "CentOS"
distro_release = "7"
release_mask = "3[.]10[.]0-862.el7.x86_64"

7
gen.go
ファイルの表示

@ -7,8 +7,9 @@ package main
import (
"fmt"
"github.com/jollheef/out-of-tree/config"
"github.com/naoina/toml"
"code.dumpstack.io/tools/out-of-tree/config"
)
func genConfig(at config.ArtifactType) (err error) {
@ -17,7 +18,9 @@ func genConfig(at config.ArtifactType) (err error) {
Type: at,
}
a.SupportedKernels = append(a.SupportedKernels, config.KernelMask{
config.Ubuntu, "18.04", ".*",
DistroType: config.Ubuntu,
DistroRelease: "18.04",
ReleaseMask: ".*",
})
buf, err := toml.Marshal(&a)

26
go.mod ノーマルファイル
ファイルの表示

@ -0,0 +1,26 @@
module code.dumpstack.io/tools/out-of-tree
go 1.14
replace code.dumpstack.io/tools/out-of-tree/qemu => ./qemu
replace code.dumpstack.io/tools/out-of-tree/config => ./config
require (
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mattn/go-runewidth v0.0.4 // indirect
github.com/mattn/go-sqlite3 v1.11.0
github.com/naoina/go-stringutil v0.1.0 // indirect
github.com/naoina/toml v0.1.1
github.com/olekukonko/tablewriter v0.0.1
github.com/otiai10/copy v1.0.1
github.com/otiai10/curr v1.0.0 // indirect
github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce
github.com/stretchr/testify v1.5.1 // indirect
github.com/zcalusic/sysinfo v0.0.0-20190429151633-fbadb57345c2
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5
gopkg.in/alecthomas/kingpin.v2 v2.2.6
gopkg.in/logrusorgru/aurora.v2 v2.0.0-20190417123914-21d75270181e
)

54
go.sum ノーマルファイル
ファイルの表示

@ -0,0 +1,54 @@
bou.ke/monkey v1.0.1 h1:zEMLInw9xvNakzUUPjfS4Ds6jYPqCFx3m7bRmG5NH2U=
bou.ke/monkey v1.0.1/go.mod h1:FgHuK96Rv2Nlf+0u1OOVDpCMdsWyOFmeeketDHE7LIg=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
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/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8=
github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88=
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/otiai10/copy v1.0.1 h1:gtBjD8aq4nychvRZ2CyJvFWAw0aja+VHazDdruZKGZA=
github.com/otiai10/copy v1.0.1/go.mod h1:8bMCJrAqOtN/d9oyh5HR7HhLQMvcGMpGdwRDYsfOCHc=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/curr v1.0.0 h1:TJIWdbX0B+kpNagQrjgq8bCMrbhiuX73M2XwgtDMoOI=
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
github.com/otiai10/mint v1.2.3 h1:PsrRBmrxR68kyNu6YlqYHbNlItc5vOkuS6LBEsNttVA=
github.com/otiai10/mint v1.2.3/go.mod h1:YnfyPNhBvnY8bW4SGQHCs/aAFhkgySlMZbrF5U0bOVw=
github.com/otiai10/mint v1.3.0 h1:Ady6MKVezQwHBkGzLFbrsywyp09Ah7rkmfjV3Bcr5uc=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
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 v0.0.0-20180822144253-5e7302b12cce h1:aP+C+YbHZfOQlutA4p4soHi7rVUqHQdWEVMSkHfDTqY=
github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/zcalusic/sysinfo v0.0.0-20190429151633-fbadb57345c2 h1:uMiaKNX5zFLOa6nNtun+d/lpV5bOBh7BvE4q9jfZacQ=
github.com/zcalusic/sysinfo v0.0.0-20190429151633-fbadb57345c2/go.mod h1:zAn3FAIbgZPYnutDND49Ivf8sb/mXYk8UjZdqMswgHg=
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-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/logrusorgru/aurora.v2 v2.0.0-20190417123914-21d75270181e h1:Wc0601/F/0TByNewL9UAKk18FfwumyYyT8pJMIHcolA=
gopkg.in/logrusorgru/aurora.v2 v2.0.0-20190417123914-21d75270181e/go.mod h1:Wm+IEn1fgFp8E2paL93oFVrHZW4toMKARNE85fDY5w8=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

7
images.config.go ノーマルファイル
ファイルの表示

@ -0,0 +1,7 @@
// Copyright 2019 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
const imagesBaseURL = "https://out-of-tree.fra1.digitaloceanspaces.com/1.0.0/"

82
images.go ノーマルファイル
ファイルの表示

@ -0,0 +1,82 @@
// Copyright 2019 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"
"io/ioutil"
"net/http"
"os"
"os/exec"
)
// inspired by Edd Turtle code
func downloadFile(filepath string, url string) (err error) {
out, err := os.Create(filepath)
if err != nil {
return
}
defer out.Close()
resp, err := http.Get(url)
if err != nil {
return
}
defer resp.Body.Close()
switch resp.StatusCode {
case http.StatusOK:
break
case http.StatusForbidden, http.StatusNotFound:
err = fmt.Errorf("Cannot download %s. It looks like you need "+
"to generate it manually and place it "+
"to ~/.out-of-tree/images/. "+
"Check documentation for additional information.", url)
return
default:
err = fmt.Errorf("Something weird happens while "+
"download file: %d", resp.StatusCode)
return
}
_, err = io.Copy(out, resp.Body)
return
}
func unpackTar(archive, destination string) (err error) {
// NOTE: If you're change anything in tar command please check also
// BSD tar (or if you're using macOS, do not forget to check GNU Tar)
// Also make sure that sparse files are extracting correctly
cmd := exec.Command("tar", "-Sxf", archive)
cmd.Dir = destination + "/"
rawOutput, err := cmd.CombinedOutput()
if err != nil {
err = fmt.Errorf("%v: %s", err, rawOutput)
return
}
return
}
func downloadImage(path, file string) (err error) {
tmp, err := ioutil.TempDir("/tmp/", "out-of-tree_")
if err != nil {
return
}
defer os.RemoveAll(tmp)
archive := tmp + "/" + file + ".tar.gz"
url := imagesBaseURL + file + ".tar.gz"
err = downloadFile(archive, url)
if err != nil {
return
}
err = unpackTar(archive, path)
return
}

361
kernel.go
ファイルの表示

@ -9,16 +9,23 @@ import (
"fmt"
"io/ioutil"
"log"
"math"
"math/rand"
"os"
"os/exec"
"os/user"
"regexp"
"runtime"
"strings"
"time"
"github.com/jollheef/out-of-tree/config"
"github.com/naoina/toml"
"code.dumpstack.io/tools/out-of-tree/config"
)
const kernelsAll int64 = math.MaxInt64
func kernelListHandler(kcfg config.KernelConfig) (err error) {
if len(kcfg.Kernels) == 0 {
return errors.New("No kernels found")
@ -29,34 +36,58 @@ func kernelListHandler(kcfg config.KernelConfig) (err error) {
return
}
func matchDebianKernelPkg(container, mask string, generic bool) (pkgs []string,
err error) {
func matchDebianHeadersPkg(container, mask string, generic bool) (
pkgs []string, err error) {
cmd := "apt-cache search linux-image | cut -d ' ' -f 1"
c := dockerCommand(container, "/tmp", "1m", cmd)
rawOutput, err := c.CombinedOutput()
cmd := "apt-cache search linux-headers | cut -d ' ' -f 1"
output, err := dockerRun(time.Minute, container, "/tmp", cmd)
if err != nil {
return
}
r, err := regexp.Compile("linux-image-" + mask)
r, err := regexp.Compile("linux-headers-" + mask)
if err != nil {
return
}
kernels := r.FindAll(rawOutput, -1)
kernels := r.FindAll([]byte(output), -1)
for _, k := range kernels {
pkg := string(k)
if generic && !strings.HasSuffix(pkg, "generic") {
continue
}
if pkg == "linux-headers-generic" {
continue
}
pkgs = append(pkgs, pkg)
}
return
}
func matchCentOSDevelPkg(container, mask string, generic bool) (
pkgs []string, err error) {
cmd := "yum search kernel-devel --showduplicates | " +
"grep '^kernel-devel' | cut -d ' ' -f 1"
output, err := dockerRun(time.Minute, container, "/tmp", cmd)
if err != nil {
return
}
r, err := regexp.Compile("kernel-devel-" + mask)
if err != nil {
return
}
for _, k := range r.FindAll([]byte(output), -1) {
pkgs = append(pkgs, string(k))
}
return
}
func dockerImagePath(sk config.KernelMask) (path string, err error) {
usr, err := user.Current()
if err != nil {
@ -68,7 +99,27 @@ func dockerImagePath(sk config.KernelMask) (path string, err error) {
return
}
func generateBaseDockerImage(sk config.KernelMask) (err error) {
func vsyscallAvailable() (available bool, err error) {
if runtime.GOOS != "linux" {
// Docker for non-Linux systems is not using the host
// kernel but uses kernel inside a virtual machine, so
// it builds by the Docker team with vsyscall support.
available = true
return
}
buf, err := ioutil.ReadFile("/proc/self/maps")
if err != nil {
return
}
available = strings.Contains(string(buf), "[vsyscall]")
return
}
func generateBaseDockerImage(registry string, commands []config.DockerCommand,
sk config.KernelMask) (err error) {
imagePath, err := dockerImagePath(sk)
if err != nil {
return
@ -81,26 +132,95 @@ func generateBaseDockerImage(sk config.KernelMask) (err error) {
log.Printf("Base image for %s:%s found",
sk.DistroType.String(), sk.DistroRelease)
return
} else {
log.Printf("Base image for %s:%s not found, start generating",
sk.DistroType.String(), sk.DistroRelease)
os.MkdirAll(imagePath, os.ModePerm)
}
d += fmt.Sprintf("FROM %s:%s\n",
log.Printf("Base image for %s:%s not found, start generating",
sk.DistroType.String(), sk.DistroRelease)
os.MkdirAll(imagePath, os.ModePerm)
d += "FROM "
if registry != "" {
d += registry + "/"
}
d += fmt.Sprintf("%s:%s\n",
strings.ToLower(sk.DistroType.String()),
sk.DistroRelease,
)
vsyscall, err := vsyscallAvailable()
if err != nil {
return
}
for _, c := range commands {
switch c.DistroType {
case config.Ubuntu:
d += "RUN " + c.Command + "\n"
case config.CentOS:
d += "RUN " + c.Command + "\n"
case config.Debian:
d += "RUN " + c.Command + "\n"
default:
err = fmt.Errorf("%s not yet supported",
sk.DistroType.String())
return
}
}
switch sk.DistroType {
case config.Ubuntu:
d += "ENV DEBIAN_FRONTEND=noninteractive\n"
if sk.DistroRelease >= "16.04" {
from := "http://.*ubuntu/"
to := "mirror://mirrors.ubuntu.com/mirrors.txt"
file := "/etc/apt/sources.list"
s := fmt.Sprintf("sed -i 's;%s;%s;' %s", from, to, file)
d += "RUN " + s + "\n"
}
d += "RUN apt-get update\n"
d += "RUN apt-get install -y build-essential libelf-dev\n"
d += "RUN apt-get install -y wget git\n"
if sk.DistroRelease >= "14.04" {
d += "RUN apt-get install -y libseccomp-dev\n"
}
d += "RUN mkdir -p /lib/modules\n"
case config.CentOS:
if sk.DistroRelease < "7" && !vsyscall {
log.Println("Old CentOS requires `vsyscall=emulate` " +
"on the latest kernels")
log.Println("Check out `A note about vsyscall` " +
"at https://hub.docker.com/_/centos")
log.Println("See also https://lwn.net/Articles/446528/")
err = fmt.Errorf("vsyscall is not available")
return
}
// enable rpms from old minor releases
d += "RUN sed -i 's/enabled=0/enabled=1/' /etc/yum.repos.d/CentOS-Vault.repo\n"
// do not remove old kernels
d += "RUN sed -i 's;installonly_limit=;installonly_limit=100500;' /etc/yum.conf\n"
d += "RUN yum -y update\n"
if sk.DistroRelease == "8" {
// FIXME CentOS Vault repository list for 8 is empty
// at the time of this fix; check for it and use a
// workaround if it's still empty
d += `RUN grep enabled /etc/yum.repos.d/CentOS-Vault.repo` +
` || echo -e '[8.0.1905]\nbaseurl=http://vault.centos.org/8.0.1905/BaseOS/$basearch/os/'` +
` >> /etc/yum.repos.d/CentOS-Vault.repo` + "\n"
}
d += "RUN yum -y groupinstall 'Development Tools'\n"
if sk.DistroRelease < "8" {
d += "RUN yum -y install deltarpm\n"
} else {
d += "RUN yum -y install drpm grub2-tools-minimal " +
"elfutils-libelf-devel\n"
}
default:
s := fmt.Sprintf("%s not yet supported", sk.DistroType.String())
err = errors.New(s)
err = fmt.Errorf("%s not yet supported", sk.DistroType.String())
return
}
@ -144,11 +264,33 @@ func dockerImageAppend(sk config.KernelMask, pkgname string) (err error) {
return
}
log.Printf("Start adding kernel %s for %s:%s",
pkgname, sk.DistroType.String(), sk.DistroRelease)
var s string
s := fmt.Sprintf("RUN apt-get install -y %s %s\n", pkgname,
strings.Replace(pkgname, "image", "headers", -1))
switch sk.DistroType {
case config.Ubuntu:
imagepkg := strings.Replace(pkgname, "headers", "image", -1)
log.Printf("Start adding kernel %s for %s:%s",
imagepkg, sk.DistroType.String(), sk.DistroRelease)
s = fmt.Sprintf("RUN apt-get install -y %s %s\n", imagepkg,
pkgname)
case config.CentOS:
imagepkg := strings.Replace(pkgname, "-devel", "", -1)
log.Printf("Start adding kernel %s for %s:%s",
imagepkg, sk.DistroType.String(), sk.DistroRelease)
version := strings.Replace(pkgname, "kernel-devel-", "", -1)
s = fmt.Sprintf("RUN yum -y install %s %s\n", imagepkg,
pkgname)
s += fmt.Sprintf("RUN dracut --add-drivers 'e1000 ext4' -f "+
"/boot/initramfs-%s.img %s\n", version, version)
default:
err = fmt.Errorf("%s not yet supported", sk.DistroType.String())
return
}
err = ioutil.WriteFile(imagePath+"/Dockerfile",
append(raw, []byte(s)...), 0644)
@ -226,7 +368,7 @@ func copyKernels(name string) (err error) {
func genKernelPath(files []os.FileInfo, kname string) string {
for _, file := range files {
if strings.Contains(file.Name(), "vmlinuz") {
if strings.HasPrefix(file.Name(), "vmlinuz") {
if strings.Contains(file.Name(), kname) {
return file.Name()
}
@ -237,7 +379,9 @@ func genKernelPath(files []os.FileInfo, kname string) string {
func genInitrdPath(files []os.FileInfo, kname string) string {
for _, file := range files {
if strings.Contains(file.Name(), "initrd") {
if strings.HasPrefix(file.Name(), "initrd") ||
strings.HasPrefix(file.Name(), "initramfs") {
if strings.Contains(file.Name(), kname) {
return file.Name()
}
@ -246,13 +390,24 @@ func genInitrdPath(files []os.FileInfo, kname string) string {
return "unknown"
}
func genRootfsImage(d dockerImageInfo) string {
func genRootfsImage(d dockerImageInfo, download bool) (rootfs string, err error) {
usr, err := user.Current()
if err != nil {
return fmt.Sprintln(err)
return
}
imageFile := d.ContainerName + ".img"
return usr.HomeDir + "/.out-of-tree/images/" + imageFile
imagesPath := usr.HomeDir + "/.out-of-tree/images/"
os.MkdirAll(imagesPath, os.ModePerm)
rootfs = imagesPath + imageFile
if !exists(rootfs) {
if download {
log.Println(imageFile, "not exists, start downloading...")
err = downloadImage(imagesPath, imageFile)
}
}
return
}
type dockerImageInfo struct {
@ -296,16 +451,25 @@ func listDockerImages() (diis []dockerImageInfo, err error) {
return
}
func updateKernelsCfg() (err error) {
func updateKernelsCfg(host, download bool) (err error) {
newkcfg := config.KernelConfig{}
if host {
// Get host kernels
newkcfg, err = genHostKernels(download)
if err != nil {
return
}
}
// Get docker kernels
dockerImages, err := listDockerImages()
if err != nil {
return
}
newkcfg := config.KernelConfig{}
for _, d := range dockerImages {
err = genKernels(d, &newkcfg)
err = genDockerKernels(d, &newkcfg, download)
if err != nil {
log.Println("gen kernels", d.ContainerName, ":", err)
continue
@ -342,8 +506,8 @@ func updateKernelsCfg() (err error) {
return
}
func genKernels(dii dockerImageInfo, newkcfg *config.KernelConfig) (
err error) {
func genDockerKernels(dii dockerImageInfo, newkcfg *config.KernelConfig,
download bool) (err error) {
name := dii.ContainerName
cmd := exec.Command("docker", "run", name, "ls", "/lib/modules")
@ -363,6 +527,11 @@ func genKernels(dii dockerImageInfo, newkcfg *config.KernelConfig) (
return
}
rootfs, err := genRootfsImage(dii, download)
if err != nil {
return
}
for _, k := range strings.Fields(string(rawOutput)) {
ki := config.KernelInfo{
DistroType: dii.DistroType,
@ -372,7 +541,7 @@ func genKernels(dii dockerImageInfo, newkcfg *config.KernelConfig) (
KernelPath: kernelsBase + genKernelPath(files, k),
InitrdPath: kernelsBase + genInitrdPath(files, k),
RootFS: genRootfsImage(dii),
RootFS: rootfs,
}
newkcfg.Kernels = append(newkcfg.Kernels, ki)
}
@ -389,7 +558,81 @@ func hasKernel(ki config.KernelInfo, kcfg config.KernelConfig) bool {
return false
}
func kernelAutogenHandler(workPath string) (err error) {
func shuffle(a []string) []string {
// FisherYates shuffle
for i := len(a) - 1; i > 0; i-- {
j := rand.Intn(i + 1)
a[i], a[j] = a[j], a[i]
}
return a
}
func generateKernels(km config.KernelMask, registry string,
commands []config.DockerCommand, max int64,
download bool) (err error) {
log.Println("Generating for kernel mask", km)
_, err = genRootfsImage(dockerImageInfo{ContainerName: km.DockerName()},
download)
if err != nil {
return
}
err = generateBaseDockerImage(registry, commands, km)
if err != nil {
return
}
var pkgs []string
switch km.DistroType {
case config.Ubuntu:
pkgs, err = matchDebianHeadersPkg(km.DockerName(),
km.ReleaseMask, true)
case config.CentOS:
pkgs, err = matchCentOSDevelPkg(km.DockerName(),
km.ReleaseMask, true)
default:
err = fmt.Errorf("%s not yet supported", km.DistroType.String())
}
if err != nil {
return
}
for i, pkg := range shuffle(pkgs) {
if max <= 0 {
log.Println("Max is reached")
break
}
log.Println(i, "/", len(pkgs), pkg)
err = dockerImageAppend(km, pkg)
if err == nil {
max--
} else {
log.Println("dockerImageAppend", err)
}
}
err = kickImage(km.DockerName())
if err != nil {
log.Println("kick image", km.DockerName(), ":", err)
return
}
err = copyKernels(km.DockerName())
if err != nil {
log.Println("copy kernels", km.DockerName(), ":", err)
return
}
return
}
func kernelAutogenHandler(workPath, registry string,
commands []config.DockerCommand,
max int64, host, download bool) (err error) {
ka, err := config.ReadArtifactConfig(workPath + "/.out-of-tree.toml")
if err != nil {
return
@ -401,40 +644,17 @@ func kernelAutogenHandler(workPath string) (err error) {
return
}
err = generateBaseDockerImage(sk)
err = generateKernels(sk, registry, commands, max, download)
if err != nil {
return
}
var pkgs []string
pkgs, err = matchDebianKernelPkg(sk.DockerName(),
sk.ReleaseMask, true)
if err != nil {
return
}
for _, pkg := range pkgs {
dockerImageAppend(sk, pkg)
}
err = kickImage(sk.DockerName())
if err != nil {
log.Println("kick image", sk.DockerName(), ":", err)
continue
}
err = copyKernels(sk.DockerName())
if err != nil {
log.Println("copy kernels", sk.DockerName(), ":", err)
continue
}
}
err = updateKernelsCfg()
err = updateKernelsCfg(host, download)
return
}
func kernelDockerRegenHandler() (err error) {
func kernelDockerRegenHandler(host, download bool) (err error) {
dockerImages, err := listDockerImages()
if err != nil {
return
@ -472,5 +692,26 @@ func kernelDockerRegenHandler() (err error) {
}
}
return updateKernelsCfg()
return updateKernelsCfg(host, download)
}
func kernelGenallHandler(distro, version, registry string,
commands []config.DockerCommand, host, download bool) (err error) {
distroType, err := config.NewDistroType(distro)
if err != nil {
return
}
km := config.KernelMask{
DistroType: distroType,
DistroRelease: version,
ReleaseMask: ".*",
}
err = generateKernels(km, registry, commands, kernelsAll, download)
if err != nil {
return
}
return updateKernelsCfg(host, download)
}

77
kernel_linux.go ノーマルファイル
ファイルの表示

@ -0,0 +1,77 @@
// 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.
// +build linux
package main
import (
"io/ioutil"
"log"
"os/exec"
"strings"
"code.dumpstack.io/tools/out-of-tree/config"
"github.com/zcalusic/sysinfo"
)
func genHostKernels(download bool) (kcfg config.KernelConfig, err error) {
si := sysinfo.SysInfo{}
si.GetSysInfo()
distroType, err := config.NewDistroType(si.OS.Vendor)
if err != nil {
return
}
cmd := exec.Command("ls", "/lib/modules")
rawOutput, err := cmd.CombinedOutput()
if err != nil {
log.Println(string(rawOutput), err)
return
}
kernelsBase := "/boot/"
files, err := ioutil.ReadDir(kernelsBase)
if err != nil {
return
}
// only for compatibility, docker is not really used
dii := dockerImageInfo{
ContainerName: config.KernelMask{
DistroType: distroType,
DistroRelease: si.OS.Version,
}.DockerName(),
}
rootfs, err := genRootfsImage(dii, download)
if err != nil {
return
}
for _, k := range strings.Fields(string(rawOutput)) {
ki := config.KernelInfo{
DistroType: distroType,
DistroRelease: si.OS.Version,
KernelRelease: k,
KernelSource: "/lib/modules/" + k + "/build",
KernelPath: kernelsBase + genKernelPath(files, k),
InitrdPath: kernelsBase + genInitrdPath(files, k),
RootFS: rootfs,
}
vmlinux := "/usr/lib/debug/boot/vmlinux-" + k
log.Println("vmlinux", vmlinux)
if exists(vmlinux) {
ki.VmlinuxPath = vmlinux
}
kcfg.Kernels = append(kcfg.Kernels, ki)
}
return
}

18
kernel_macos.go ノーマルファイル
ファイルの表示

@ -0,0 +1,18 @@
// 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.
// +build darwin
package main
import (
"errors"
"code.dumpstack.io/tools/out-of-tree/config"
)
func genHostKernels(download bool) (kcfg config.KernelConfig, err error) {
err = errors.New("generate host kernels for macOS is not supported")
return
}

245
log.go ノーマルファイル
ファイルの表示

@ -0,0 +1,245 @@
// Copyright 2019 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 (
"database/sql"
"encoding/json"
"fmt"
"log"
"math"
"os"
"github.com/olekukonko/tablewriter"
"gopkg.in/logrusorgru/aurora.v2"
"code.dumpstack.io/tools/out-of-tree/config"
)
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)
}
}
func logHandler(db *sql.DB, path, tag string, num int, rate bool) (err error) {
var les []logEntry
ka, kaErr := config.ReadArtifactConfig(path + "/.out-of-tree.toml")
if kaErr == nil {
log.Println(".out-of-tree.toml found, filter by artifact name")
les, err = getAllArtifactLogs(db, tag, num, ka)
} else {
les, err = getAllLogs(db, tag, num)
}
if err != nil {
return
}
s := "\nS"
if rate {
if kaErr != nil {
err = kaErr
return
}
s = fmt.Sprintf("{[%s] %s} Overall s", ka.Type, ka.Name)
les, err = getAllArtifactLogs(db, tag, math.MaxInt64, ka)
if err != nil {
return
}
} else {
for _, l := range les {
logLogEntry(l)
}
}
success := 0
for _, l := range les {
if l.Test.Ok {
success++
}
}
overall := float64(success) / float64(len(les))
fmt.Printf("%success rate: %.04f (~%.0f%%)\n",
s, overall, overall*100)
return
}
func logDumpHandler(db *sql.DB, id int) (err error) {
l, err := getLogByID(db, id)
if err != nil {
return
}
fmt.Println("ID:", l.ID)
fmt.Println("Date:", l.Timestamp)
fmt.Println("Tag:", l.Tag)
fmt.Println()
fmt.Println("Type:", l.Type.String())
fmt.Println("Name:", l.Name)
fmt.Println()
fmt.Println("Distro:", l.DistroType.String(), l.DistroRelease)
fmt.Println("Kernel:", l.KernelRelease)
fmt.Println()
fmt.Println("Build ok:", l.Build.Ok)
if l.Type == config.KernelModule {
fmt.Println("Insmod ok:", l.Run.Ok)
}
fmt.Println("Test ok:", l.Test.Ok)
fmt.Println()
fmt.Printf("Build output:\n%s\n", l.Build.Output)
fmt.Println()
if l.Type == config.KernelModule {
fmt.Printf("Insmod output:\n%s\n", l.Run.Output)
fmt.Println()
}
fmt.Printf("Test output:\n%s\n", l.Test.Output)
fmt.Println()
fmt.Printf("Qemu stdout:\n%s\n", l.Stdout)
fmt.Println()
fmt.Printf("Qemu stderr:\n%s\n", l.Stderr)
fmt.Println()
return
}
type runstat struct {
All, BuildOK, RunOK, TestOK, Timeout, Panic int
}
func getStats(db *sql.DB, path, tag string) (
distros map[string]map[string]map[string]runstat, err error) {
var les []logEntry
ka, kaErr := config.ReadArtifactConfig(path + "/.out-of-tree.toml")
if kaErr == nil {
les, err = getAllArtifactLogs(db, tag, -1, ka)
} else {
les, err = getAllLogs(db, tag, -1)
}
if err != nil {
return
}
distros = make(map[string]map[string]map[string]runstat)
for _, l := range les {
_, ok := distros[l.DistroType.String()]
if !ok {
distros[l.DistroType.String()] = make(map[string]map[string]runstat)
}
_, ok = distros[l.DistroType.String()][l.DistroRelease]
if !ok {
distros[l.DistroType.String()][l.DistroRelease] = make(map[string]runstat)
}
rs := distros[l.DistroType.String()][l.DistroRelease][l.KernelRelease]
rs.All++
if l.Build.Ok {
rs.BuildOK++
}
if l.Run.Ok {
rs.RunOK++
}
if l.Test.Ok {
rs.TestOK++
}
if l.KernelPanic {
rs.Panic++
}
if l.KilledByTimeout {
rs.Timeout++
}
distros[l.DistroType.String()][l.DistroRelease][l.KernelRelease] = rs
}
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
}

267
main.go
ファイルの表示

@ -5,24 +5,86 @@
package main
import (
"fmt"
"log"
"math/rand"
"os"
"os/exec"
"os/user"
"runtime"
"sort"
"strconv"
"time"
kingpin "gopkg.in/alecthomas/kingpin.v2"
"github.com/jollheef/out-of-tree/config"
"code.dumpstack.io/tools/out-of-tree/config"
)
func findFallback(kcfg config.KernelConfig, ki config.KernelInfo) (rootfs string) {
for _, k := range kcfg.Kernels {
if !exists(k.RootFS) || k.DistroType != ki.DistroType {
continue
}
if k.RootFS < ki.RootFS {
rootfs = k.RootFS
return
}
}
return
}
func handleFallbacks(kcfg config.KernelConfig) {
sort.Sort(sort.Reverse(config.ByRootFS(kcfg.Kernels)))
for i, k := range kcfg.Kernels {
if !exists(k.RootFS) {
newRootFS := findFallback(kcfg, k)
s := k.RootFS + " does not exists "
if newRootFS != "" {
s += "(fallback to " + newRootFS + ")"
} else {
s += "(no fallback found)"
}
kcfg.Kernels[i].RootFS = newRootFS
log.Println(s)
}
}
}
func checkRequiredUtils() (err error) {
// Check for required commands
for _, cmd := range []string{"docker", "qemu-system-x86_64"} {
_, err := exec.Command("which", cmd).CombinedOutput()
if err != nil {
return fmt.Errorf("Command not found: %s", cmd)
}
}
return
}
func checkDockerPermissions() (err error) {
output, err := exec.Command("docker", "ps").CombinedOutput()
if err != nil {
err = fmt.Errorf("%s", output)
}
return
}
func main() {
log.SetFlags(log.Lshortfile)
rand.Seed(time.Now().UnixNano())
app := kingpin.New(
"out-of-tree",
"kernel {module, exploit} development tool",
)
app.Author("Mikhail Klementev <jollheef@riseup.net>")
app.Version("0.1.0")
app.Author("Mikhail Klementev <root@dumpstack.io>")
app.Version("1.3.0")
pathFlag := app.Flag("path", "Path to work directory")
path := pathFlag.Default(".").ExistingDir()
@ -31,22 +93,47 @@ func main() {
if err != nil {
return
}
defaultKcfgPath := usr.HomeDir + "/.out-of-tree/kernels.toml"
os.MkdirAll(usr.HomeDir+"/.out-of-tree", os.ModePerm)
confPath := usr.HomeDir + "/.out-of-tree/out-of-tree.toml"
conf, err := config.ReadOutOfTreeConf(confPath)
if err != nil {
return
}
kcfgPathFlag := app.Flag("kernels", "Path to main kernels config")
kcfgPath := kcfgPathFlag.Default(defaultKcfgPath).ExistingFile()
kcfgPath := kcfgPathFlag.Default(conf.Kernels).String()
dbPathFlag := app.Flag("db", "Path to database")
dbPath := dbPathFlag.Default(conf.Database).String()
defaultUserKcfgPath := usr.HomeDir + "/.out-of-tree/kernels.user.toml"
userKcfgPathFlag := app.Flag("user-kernels", "User kernels config")
userKcfgPathEnv := userKcfgPathFlag.Envar("OUT_OF_TREE_KCFG")
userKcfgPath := userKcfgPathEnv.Default(defaultUserKcfgPath).String()
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("1m").Duration()
qemuTimeout := qemuTimeoutFlag.Default(conf.Qemu.Timeout).Duration()
dockerTimeoutFlag := app.Flag("docker-timeout", "Timeout for docker")
dockerTimeout := dockerTimeoutFlag.Default("1m").Duration()
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()
@ -59,12 +146,40 @@ func main() {
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()
pewVerboseFlag := pewCommand.Flag("verbose", "Show more information")
pewVerbose := pewVerboseFlag.Bool()
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",
@ -78,22 +193,87 @@ func main() {
debugFlagGDB := debugCommand.Flag("gdb", "Set gdb listen address")
debugGDB := debugFlagGDB.Default("tcp::1234").String()
bootstrapCommand := app.Command("bootstrap",
"Create directories && download images")
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()
// Check for required commands
for _, cmd := range []string{"timeout", "docker", "qemu"} {
_, err := exec.Command("which", cmd).CombinedOutput()
if err != nil {
log.Fatalln("Command not found:", cmd)
}
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")
}
if *yekpti && *nokpti {
log.Fatalln("Only one of disable/enable can be used at once")
}
kcfg, err := config.ReadKernelConfig(*kcfgPath)
if err != nil {
log.Fatalln(err)
log.Println(err)
}
if exists(*userKcfgPath) {
@ -109,28 +289,69 @@ func main() {
}
}
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, *qemuTimeout, *dockerTimeout)
*pewTest, *pewGuess, stop, *qemuTimeout, *dockerTimeout,
*pewMax, *pewRuns, *pewDist, *pewTag, *pewThreads,
db, *pewVerbose)
case kernelListCommand.FullCommand():
err = kernelListHandler(kcfg)
case kernelAutogenCommand.FullCommand():
err = kernelAutogenHandler(*path)
err = kernelAutogenHandler(*path, *dockerRegistry,
conf.Docker.Commands, *kernelAutogenMax,
*kernelUseHost, !*kernelNoDownload)
case kernelDockerRegenCommand.FullCommand():
err = kernelDockerRegenHandler()
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)
*dockerTimeout, *yekaslr, *yesmep, *yesmap, *yekpti,
*nokaslr, *nosmep, *nosmap, *nokpti)
case bootstrapCommand.FullCommand():
err = bootstrapHandler()
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)
}
}

59
pack.go ノーマルファイル
ファイルの表示

@ -0,0 +1,59 @@
// Copyright 2019 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 (
"database/sql"
"fmt"
"io/ioutil"
"log"
"runtime"
"time"
"code.dumpstack.io/tools/out-of-tree/config"
)
func packHandler(db *sql.DB, path, registry string, stop time.Time,
commands []config.DockerCommand, kcfg config.KernelConfig,
autogen, download bool, exploitRuns, kernelRuns int64) (err error) {
dockerTimeout := time.Minute
qemuTimeout := time.Minute
threads := runtime.NumCPU()
tag := fmt.Sprintf("pack_run_%d", time.Now().Unix())
log.Println("Tag:", tag)
files, err := ioutil.ReadDir(path)
if err != nil {
return
}
for _, f := range files {
workPath := path + "/" + f.Name()
if !exists(workPath + "/.out-of-tree.toml") {
continue
}
if autogen {
var perRegex int64 = 1
err = kernelAutogenHandler(workPath, registry,
commands, perRegex, false, download)
if err != nil {
return
}
}
log.Println(f.Name())
pewHandler(kcfg, workPath, "", "", "", false,
stop, dockerTimeout, qemuTimeout,
kernelRuns, exploitRuns, pathDevNull,
tag, threads, db, false)
}
return
}

422
pew.go
ファイルの表示

@ -5,29 +5,62 @@
package main
import (
"database/sql"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"math/rand"
"os"
"os/exec"
"runtime"
"os/user"
"strings"
"time"
"github.com/logrusorgru/aurora"
"github.com/otiai10/copy"
"github.com/remeh/sizedwaitgroup"
"gopkg.in/logrusorgru/aurora.v2"
"github.com/jollheef/out-of-tree/config"
qemu "github.com/jollheef/out-of-tree/qemu"
"code.dumpstack.io/tools/out-of-tree/config"
"code.dumpstack.io/tools/out-of-tree/qemu"
)
func dockerCommand(container, workdir, timeout, command string) *exec.Cmd {
return exec.Command("timeout", "-k", timeout, timeout, "docker", "run",
"-v", workdir+":/work", container,
"bash", "-c", "cd /work && "+command)
type runstate struct {
Overall, Success float64
}
var (
state runstate
)
func successRate(state runstate) float64 {
return state.Success / state.Overall
}
const pathDevNull = "/dev/null"
func dockerRun(timeout time.Duration, container, workdir, command string) (
output string, err error) {
cmd := exec.Command("docker", "run", "-v", workdir+":/work",
container, "bash", "-c", "cd /work && "+command)
timer := time.AfterFunc(timeout, func() {
cmd.Process.Kill()
})
defer timer.Stop()
raw, err := cmd.CombinedOutput()
if err != nil {
e := fmt.Sprintf("error `%v` for cmd `%v` with output `%v`",
err, command, string(raw))
err = errors.New(e)
return
}
output = string(raw)
return
}
func build(tmp string, ka config.Artifact, ki config.KernelInfo,
@ -48,38 +81,37 @@ func build(tmp string, ka config.Artifact, ki config.KernelInfo,
}
kernel := "/lib/modules/" + ki.KernelRelease + "/build"
seconds := fmt.Sprintf("%ds", dockerTimeout/time.Second)
cmd := dockerCommand(ki.ContainerName, tmpSourcePath, seconds,
"make KERNEL="+kernel+" TARGET="+target)
rawOutput, err := cmd.CombinedOutput()
output = string(rawOutput)
if err != nil {
err = errors.New("make execution error")
return
if ki.KernelSource != "" {
kernel = ki.KernelSource
}
return
}
if ki.ContainerName != "" {
output, err = dockerRun(dockerTimeout, ki.ContainerName,
tmpSourcePath, "make KERNEL="+kernel+" TARGET="+target+
" && chmod -R 777 /work")
} else {
command := "make KERNEL=" + kernel + " TARGET=" + target
cmd := exec.Command("bash", "-c", "cd "+tmpSourcePath+" && "+command)
timer := time.AfterFunc(dockerTimeout, func() {
cmd.Process.Kill()
})
defer timer.Stop()
func cleanDmesg(q *qemu.QemuSystem) (err error) {
start := time.Now()
for {
_, err = q.Command("root", "dmesg -c")
if err == nil {
break
var raw []byte
raw, err = cmd.CombinedOutput()
if err != nil {
e := fmt.Sprintf("error `%v` for cmd `%v` with output `%v`",
err, command, string(raw))
err = errors.New(e)
return
}
time.Sleep(time.Second)
if time.Now().After(start.Add(time.Minute)) {
err = errors.New("Can't connect to qemu")
break
}
output = string(raw)
}
return
}
func testKernelModule(q *qemu.QemuSystem, ka config.Artifact,
func testKernelModule(q *qemu.System, ka config.Artifact,
test string) (output string, err error) {
output, err = q.Command("root", test)
@ -87,7 +119,7 @@ func testKernelModule(q *qemu.QemuSystem, ka config.Artifact,
return
}
func testKernelExploit(q *qemu.QemuSystem, ka config.Artifact,
func testKernelExploit(q *qemu.System, ka config.Artifact,
test, exploit string) (output string, err error) {
output, err = q.Command("user", "chmod +x "+exploit)
@ -111,18 +143,49 @@ func testKernelExploit(q *qemu.QemuSystem, ka config.Artifact,
return
}
func genOkFail(name string, ok bool) aurora.Value {
func genOkFail(name string, ok bool) (aurv aurora.Value) {
state.Overall += 1
if ok {
state.Success += 1
s := " " + name + " SUCCESS "
return aurora.BgGreen(aurora.Black(s))
aurv = aurora.BgGreen(aurora.Black(s))
} else {
s := " " + name + " FAILURE "
return aurora.BgRed(aurora.Gray(aurora.Bold(s)))
aurv = aurora.BgRed(aurora.White(aurora.Bold(s)))
}
return
}
type phasesResult struct {
BuildArtifact string
Build, Run, Test struct {
Output string
Ok bool
}
}
func dumpResult(q *qemu.QemuSystem, ka config.Artifact, ki config.KernelInfo,
build_ok, run_ok, test_ok *bool) {
func copyFile(sourcePath, destinationPath string) (err error) {
sourceFile, err := os.Open(sourcePath)
if err != nil {
return
}
defer sourceFile.Close()
destinationFile, err := os.Create(destinationPath)
if err != nil {
return err
}
if _, err := io.Copy(destinationFile, sourceFile); err != nil {
destinationFile.Close()
return err
}
return destinationFile.Close()
}
func dumpResult(q *qemu.System, ka config.Artifact, ki config.KernelInfo,
res *phasesResult, dist, tag, binary string, db *sql.DB) {
// TODO merge (problem is it's not 100% same) with log.go:logLogEntry
distroInfo := fmt.Sprintf("%s-%s {%s}", ki.DistroType,
ki.DistroRelease, ki.KernelRelease)
@ -130,13 +193,13 @@ func dumpResult(q *qemu.QemuSystem, ka config.Artifact, ki config.KernelInfo,
colored := ""
if ka.Type == config.KernelExploit {
colored = aurora.Sprintf("[*] %40s: %s %s", distroInfo,
genOkFail("BUILD", *build_ok),
genOkFail("LPE", *test_ok))
genOkFail("BUILD", res.Build.Ok),
genOkFail("LPE", res.Test.Ok))
} else {
colored = aurora.Sprintf("[*] %40s: %s %s %s", distroInfo,
genOkFail("BUILD", *build_ok),
genOkFail("INSMOD", *run_ok),
genOkFail("TEST", *test_ok))
genOkFail("BUILD", res.Build.Ok),
genOkFail("INSMOD", res.Run.Ok),
genOkFail("TEST", res.Test.Ok))
}
additional := ""
@ -151,22 +214,122 @@ func dumpResult(q *qemu.QemuSystem, ka config.Artifact, ki config.KernelInfo,
} else {
fmt.Println(colored)
}
err := addToLog(db, q, ka, ki, res, tag)
if err != nil {
log.Println("[db] addToLog (", ka, ") error:", err)
}
if binary == "" && dist != pathDevNull {
err = os.MkdirAll(dist, os.ModePerm)
if err != nil {
log.Println("os.MkdirAll (", ka, ") error:", err)
}
path := fmt.Sprintf("%s/%s-%s-%s", dist, ki.DistroType,
ki.DistroRelease, ki.KernelRelease)
if ka.Type != config.KernelExploit {
path += ".ko"
}
err = copyFile(res.BuildArtifact, path)
if err != nil {
log.Println("copyFile (", ka, ") error:", err)
}
}
}
func copyArtifactAndTest(q *qemu.System, ka config.Artifact,
res *phasesResult, remoteTest string) (err error) {
switch ka.Type {
case config.KernelModule:
res.Run.Output, err = q.CopyAndInsmod(res.BuildArtifact)
if err != nil {
log.Println(res.Run.Output, err)
return
}
res.Run.Ok = true
res.Test.Output, err = testKernelModule(q, ka, remoteTest)
if err != nil {
log.Println(res.Test.Output, err)
return
}
res.Test.Ok = true
case config.KernelExploit:
remoteExploit := fmt.Sprintf("/tmp/exploit_%d", rand.Int())
err = q.CopyFile("user", res.BuildArtifact, remoteExploit)
if err != nil {
return
}
res.Test.Output, err = testKernelExploit(q, ka, remoteTest,
remoteExploit)
if err != nil {
log.Println(res.Test.Output)
return
}
res.Run.Ok = true // does not really used
res.Test.Ok = true
default:
log.Println("Unsupported artifact type")
}
return
}
func copyTest(q *qemu.System, testPath string, ka config.Artifact) (
remoteTest string, err error) {
remoteTest = fmt.Sprintf("/tmp/test_%d", rand.Int())
err = q.CopyFile("user", testPath, remoteTest)
if err != nil {
if ka.Type == config.KernelExploit {
q.Command("user",
"echo -e '#!/bin/sh\necho touch $2 | $1' "+
"> "+remoteTest+
" && chmod +x "+remoteTest)
} else {
q.Command("user", "echo '#!/bin/sh' "+
"> "+remoteTest+" && chmod +x "+remoteTest)
}
}
_, err = q.Command("root", "chmod +x "+remoteTest)
return
}
func whatever(swg *sizedwaitgroup.SizedWaitGroup, ka config.Artifact,
ki config.KernelInfo, binaryPath, testPath string,
qemuTimeout, dockerTimeout time.Duration) {
qemuTimeout, dockerTimeout time.Duration, dist, tag string,
db *sql.DB, verbose bool) {
defer swg.Done()
kernel := qemu.Kernel{KernelPath: ki.KernelPath, InitrdPath: ki.InitrdPath}
q, err := qemu.NewQemuSystem(qemu.X86_64, kernel, ki.RootFS)
q, err := qemu.NewSystem(qemu.X86x64, kernel, ki.RootFS)
if err != nil {
log.Println("Qemu creation error:", err)
return
}
q.Timeout = qemuTimeout
if ka.Qemu.Timeout.Duration != 0 {
q.Timeout = ka.Qemu.Timeout.Duration
}
if ka.Qemu.Cpus != 0 {
q.Cpus = ka.Qemu.Cpus
}
if ka.Qemu.Memory != 0 {
q.Memory = ka.Qemu.Memory
}
q.SetKASLR(!ka.Mitigations.DisableKaslr)
q.SetSMEP(!ka.Mitigations.DisableSmep)
q.SetSMAP(!ka.Mitigations.DisableSmap)
q.SetKPTI(!ka.Mitigations.DisableKpti)
err = q.Start()
if err != nil {
log.Println("Qemu start error:", err)
@ -174,106 +337,86 @@ func whatever(swg *sizedwaitgroup.SizedWaitGroup, ka config.Artifact,
}
defer q.Stop()
tmp, err := ioutil.TempDir("/tmp/", "out-of-tree_")
if verbose {
go func() {
for !q.Died {
time.Sleep(time.Minute)
log.Println(ka.Name, ki.DistroType,
ki.DistroRelease, ki.KernelRelease,
"still alive")
}
}()
}
usr, err := user.Current()
if err != nil {
return
}
tmpdir := usr.HomeDir + "/.out-of-tree/tmp"
os.MkdirAll(tmpdir, os.ModePerm)
tmp, err := ioutil.TempDir(tmpdir, "out-of-tree_")
if err != nil {
log.Println("Temporary directory creation error:", err)
return
}
defer os.RemoveAll(tmp)
build_ok := false
run_ok := false
test_ok := false
defer dumpResult(q, ka, ki, &build_ok, &run_ok, &test_ok)
result := phasesResult{}
defer dumpResult(q, ka, ki, &result, dist, tag, binaryPath, db)
var outFile, output string
if binaryPath == "" {
// TODO Write build log to file or database
outFile, output, err = build(tmp, ka, ki, dockerTimeout)
result.BuildArtifact, result.Build.Output, err = build(tmp, ka,
ki, dockerTimeout)
if err != nil {
log.Println(output)
log.Println(err)
return
}
build_ok = true
result.Build.Ok = true
} else {
outFile = binaryPath
build_ok = true
}
err = cleanDmesg(q)
if err != nil {
return
result.BuildArtifact = binaryPath
result.Build.Ok = true
}
if testPath == "" {
testPath = outFile + "_test"
}
remoteTest := fmt.Sprintf("/tmp/test_%d", rand.Int())
err = q.CopyFile("user", testPath, remoteTest)
if err != nil {
if ka.Type == config.KernelExploit {
log.Println("Use `echo touch FILE | exploit` for test")
q.Command("user",
"echo -e '#!/bin/sh\necho touch $2 | $1' "+
"> "+remoteTest+
" && chmod +x "+remoteTest)
} else {
log.Println("copy file err", err)
// we should not exit because of testing 'insmod' part
// for kernel module
testPath = result.BuildArtifact + "_test"
if !exists(testPath) {
testPath = tmp + "/" + "test.sh"
}
}
_, err = q.Command("root", "chmod +x "+remoteTest)
remoteTest, err := copyTest(q, testPath, ka)
if err != nil {
return
}
if ka.Type == config.KernelModule {
// TODO Write insmod log to file or database
output, err := q.CopyAndInsmod(outFile)
if err != nil {
log.Println(output, err)
return
}
run_ok = true
copyArtifactAndTest(q, ka, &result, remoteTest)
}
// TODO Write test results to file or database
output, err = testKernelModule(q, ka, remoteTest)
if err != nil {
log.Println(output, err)
return
}
test_ok = true
} else if ka.Type == config.KernelExploit {
remoteExploit := fmt.Sprintf("/tmp/exploit_%d", rand.Int())
err = q.CopyFile("user", outFile, remoteExploit)
if err != nil {
return
}
// TODO Write test results to file or database
output, err = testKernelExploit(q, ka, remoteTest, remoteExploit)
if err != nil {
log.Println(output)
return
}
run_ok = true // does not really used
test_ok = true
} else {
err = errors.New("Unsupported artifact type")
func shuffleKernels(a []config.KernelInfo) []config.KernelInfo {
// FisherYates shuffle
for i := len(a) - 1; i > 0; i-- {
j := rand.Intn(i + 1)
a[i], a[j] = a[j], a[i]
}
return
return a
}
func performCI(ka config.Artifact, kcfg config.KernelConfig, binaryPath,
testPath string, qemuTimeout, dockerTimeout time.Duration) (err error) {
testPath string, stop time.Time,
qemuTimeout, dockerTimeout time.Duration,
max, runs int64, dist, tag string, threads int,
db *sql.DB, verbose bool) (err error) {
found := false
swg := sizedwaitgroup.New(runtime.NumCPU())
for _, kernel := range kcfg.Kernels {
swg := sizedwaitgroup.New(threads)
for _, kernel := range shuffleKernels(kcfg.Kernels) {
if max <= 0 {
break
}
var supported bool
supported, err = ka.Supported(kernel)
if err != nil {
@ -282,9 +425,16 @@ func performCI(ka config.Artifact, kcfg config.KernelConfig, binaryPath,
if supported {
found = true
swg.Add()
go whatever(&swg, ka, kernel, binaryPath, testPath,
qemuTimeout, dockerTimeout)
max--
for i := int64(0); i < runs; i++ {
if !stop.IsZero() && time.Now().After(stop) {
break
}
swg.Add()
go whatever(&swg, ka, kernel, binaryPath,
testPath, qemuTimeout, dockerTimeout,
dist, tag, db, verbose)
}
}
}
swg.Wait()
@ -319,9 +469,28 @@ func kernelMask(kernel string) (km config.KernelMask, err error) {
return
}
func genAllKernels() (sk []config.KernelMask, err error) {
for _, dType := range config.DistroTypeStrings {
var dt config.DistroType
dt, err = config.NewDistroType(dType)
if err != nil {
return
}
sk = append(sk, config.KernelMask{
DistroType: dt,
ReleaseMask: ".*",
})
}
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,
qemuTimeout, dockerTimeout time.Duration) (err error) {
stop time.Time, qemuTimeout, dockerTimeout time.Duration,
max, runs int64, dist, tag string, threads int,
db *sql.DB, verbose bool) (err error) {
ka, err := config.ReadArtifactConfig(workPath + "/.out-of-tree.toml")
if err != nil {
@ -343,20 +512,15 @@ func pewHandler(kcfg config.KernelConfig,
}
if guess {
ka.SupportedKernels = []config.KernelMask{}
for _, dType := range config.DistroTypeStrings {
var dt config.DistroType
dt, err = config.NewDistroType(dType)
if err != nil {
return
}
km := config.KernelMask{DistroType: dt, ReleaseMask: ".*"}
ka.SupportedKernels = append(ka.SupportedKernels, km)
ka.SupportedKernels, err = genAllKernels()
if err != nil {
return
}
}
err = performCI(ka, kcfg, binary, test, qemuTimeout, dockerTimeout)
err = performCI(ka, kcfg, binary, test,
stop, qemuTimeout, dockerTimeout,
max, runs, dist, tag, threads, db, verbose)
if err != nil {
return
}

44
pew_test.go ノーマルファイル
ファイルの表示

@ -0,0 +1,44 @@
// 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 + ")")
}
}

ファイルの表示

@ -9,7 +9,7 @@ Features:
## Installation
$ go get github.com/jollheef/out-of-tree/qemu
$ go get code.dumpstack.io/tools/out-of-tree/qemu
### Generate root image
@ -30,12 +30,12 @@ Note: qemu on macOS since v2.12 (24 April 2018) supports Hypervisor.framework.
#### Generate image
$ cd $GOPATH/src/github.com/jollheef/out-of-tree/tools/qemu-debian-img
$ cd $GOPATH/src/code.dumpstack.io/tools/out-of-tree/tools/qemu-debian-img
$ ./bootstrap.sh
### Fill configuration file
$ $EDITOR $GOPATH/src/github.com/jollheef/out-of-tree/qemu/test.config.go
$ $EDITOR $GOPATH/src/code.dumpstack.io/tools/out-of-tree/qemu/test.config.go
### Run tests
@ -43,7 +43,7 @@ Note: qemu on macOS since v2.12 (24 April 2018) supports Hypervisor.framework.
## Usage
$ go get github.com/jollheef/out-of-tree/qemu
$ go get code.dumpstack.io/tools/out-of-tree/qemu
Minimal example:
@ -52,7 +52,7 @@ Minimal example:
KernelPath: "/path/to/vmlinuz",
InitrdPath: "/path/to/initrd", // if required
}
q, err := qemu.NewQemuSystem(qemu.X86_64, kernel, "/path/to/qcow2")
q, err := qemu.NewSystem(qemu.X86_64, kernel, "/path/to/qcow2")
if err != nil {
log.Fatal(err)
}
@ -71,4 +71,4 @@ Minimal example:
More information and list of all functions see at go documentation project, or just run locally:
$ godoc github.com/jollheef/out-of-tree/qemu
$ godoc code.dumpstack.io/tools/out-of-tree/qemu

ファイルの表示

@ -2,7 +2,7 @@
// Use of this source code is governed by a AGPLv3 license
// (or later) that can be found in the LICENSE file.
package qemukernel
package qemu
import (
"bytes"
@ -44,8 +44,10 @@ func readUntilEOF(pipe io.ReadCloser, buf *[]byte) (err error) {
type arch string
const (
X86_64 arch = "x86_64"
I386 = "i386"
// X86x64 is the qemu-system-x86_64
X86x64 arch = "x86_64"
// X86x32 is the qemu-system-i386
X86x32 = "i386"
// TODO add other
unsupported = "unsupported" // for test purposes
@ -58,8 +60,8 @@ type Kernel struct {
InitrdPath string
}
// QemuSystem describe qemu parameters and runned process
type QemuSystem struct {
// System describe qemu parameters and executed process
type System struct {
arch arch
kernel Kernel
drivePath string
@ -70,6 +72,11 @@ type QemuSystem struct {
debug bool
gdb string // tcp::1234
noKASLR bool
noSMEP bool
noSMAP bool
noKPTI bool
// Timeout works after Start invocation
Timeout time.Duration
KilledByTimeout bool
@ -79,7 +86,7 @@ type QemuSystem struct {
Died bool
sshAddrPort string
// accessible while qemu is runned
// accessible while qemu is running
cmd *exec.Cmd
pipe struct {
stdin io.WriteCloser
@ -93,12 +100,12 @@ type QemuSystem struct {
exitErr error
}
// NewQemuSystem constructor
func NewQemuSystem(arch arch, kernel Kernel, drivePath string) (q *QemuSystem, err error) {
// NewSystem constructor
func NewSystem(arch arch, kernel Kernel, drivePath string) (q *System, err error) {
if _, err = exec.LookPath("qemu-system-" + string(arch)); err != nil {
return
}
q = &QemuSystem{}
q = &System{}
q.arch = arch
if _, err = os.Stat(kernel.KernelPath); err != nil {
@ -156,10 +163,19 @@ func kvmExists() bool {
if _, err := os.Stat("/dev/kvm"); err != nil {
return false
}
file, err := os.OpenFile("/dev/kvm", os.O_WRONLY, 0666)
if err != nil {
if os.IsPermission(err) {
return false
}
}
file.Close()
return true
}
func (q *QemuSystem) panicWatcher() {
func (q *System) panicWatcher() {
for {
time.Sleep(time.Second)
if bytes.Contains(q.Stdout, []byte("Kernel panic")) {
@ -172,15 +188,36 @@ func (q *QemuSystem) panicWatcher() {
}
}
func (q System) cmdline() (s string) {
s = "root=/dev/sda ignore_loglevel console=ttyS0 rw"
if q.noKASLR {
s += " nokaslr"
}
if q.noSMEP {
s += " nosmep"
}
if q.noSMAP {
s += " nosmap"
}
if q.noKPTI {
s += " nokpti"
}
return
}
// Start qemu process
func (q *QemuSystem) Start() (err error) {
func (q *System) Start() (err error) {
rand.Seed(time.Now().UnixNano()) // Are you sure?
q.sshAddrPort = getFreeAddrPort()
hostfwd := fmt.Sprintf("hostfwd=tcp:%s-:22", q.sshAddrPort)
qemuArgs := []string{"-snapshot", "-nographic",
"-hda", q.drivePath,
"-kernel", q.kernel.KernelPath,
"-append", "root=/dev/sda ignore_loglevel console=ttyS0 rw",
"-smp", fmt.Sprintf("%d", q.Cpus),
"-m", fmt.Sprintf("%d", q.Memory),
"-device", "e1000,netdev=n1",
@ -195,14 +232,16 @@ func (q *QemuSystem) Start() (err error) {
qemuArgs = append(qemuArgs, "-initrd", q.kernel.InitrdPath)
}
if (q.arch == X86_64 || q.arch == I386) && kvmExists() {
qemuArgs = append(qemuArgs, "-enable-kvm")
if (q.arch == X86x64 || q.arch == X86x32) && kvmExists() {
qemuArgs = append(qemuArgs, "-enable-kvm", "-cpu", "host")
}
if q.arch == X86_64 && runtime.GOOS == "darwin" {
if q.arch == X86x64 && runtime.GOOS == "darwin" {
qemuArgs = append(qemuArgs, "-accel", "hvf", "-cpu", "host")
}
qemuArgs = append(qemuArgs, "-append", q.cmdline())
q.cmd = exec.Command("qemu-system-"+string(q.arch), qemuArgs...)
if q.pipe.stdin, err = q.cmd.StdinPipe(); err != nil {
@ -250,7 +289,7 @@ func (q *QemuSystem) Start() (err error) {
}
// Stop qemu process
func (q *QemuSystem) Stop() {
func (q *System) Stop() {
// 1 00/01 01 01 SOH (Ctrl-A) START OF HEADING
fmt.Fprintf(q.pipe.stdin, "%cx", 1)
// wait for die
@ -262,7 +301,7 @@ func (q *QemuSystem) Stop() {
}
}
func (q QemuSystem) ssh(user string) (client *ssh.Client, err error) {
func (q System) ssh(user string) (client *ssh.Client, err error) {
cfg := &ssh.ClientConfig{
User: user,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
@ -273,7 +312,7 @@ func (q QemuSystem) ssh(user string) (client *ssh.Client, err error) {
}
// Command executes shell commands on qemu system
func (q QemuSystem) Command(user, cmd string) (output string, err error) {
func (q System) Command(user, cmd string) (output string, err error) {
client, err := q.ssh(user)
if err != nil {
return
@ -291,7 +330,7 @@ func (q QemuSystem) Command(user, cmd string) (output string, err error) {
}
// AsyncCommand executes command on qemu system but does not wait for exit
func (q QemuSystem) AsyncCommand(user, cmd string) (err error) {
func (q System) AsyncCommand(user, cmd string) (err error) {
client, err := q.ssh(user)
if err != nil {
return
@ -308,7 +347,7 @@ func (q QemuSystem) AsyncCommand(user, cmd string) (err error) {
}
// CopyFile is copy file from local machine to remote through ssh/scp
func (q *QemuSystem) CopyFile(user, localPath, remotePath string) (err error) {
func (q *System) CopyFile(user, localPath, remotePath string) (err error) {
addrPort := strings.Split(q.sshAddrPort, ":")
addr := addrPort[0]
port := addrPort[1]
@ -325,7 +364,8 @@ func (q *QemuSystem) CopyFile(user, localPath, remotePath string) (err error) {
return
}
func (q *QemuSystem) CopyAndInsmod(localKoPath string) (output string, err error) {
// CopyAndInsmod copy kernel module to temporary file on qemu then insmod it
func (q *System) CopyAndInsmod(localKoPath string) (output string, err error) {
remoteKoPath := fmt.Sprintf("/tmp/module_%d.ko", rand.Int())
err = q.CopyFile("root", localKoPath, remoteKoPath)
if err != nil {
@ -336,7 +376,7 @@ func (q *QemuSystem) CopyAndInsmod(localKoPath string) (output string, err error
}
// CopyAndRun is copy local file to qemu vm then run it
func (q *QemuSystem) CopyAndRun(user, path string) (output string, err error) {
func (q *System) CopyAndRun(user, path string) (output string, err error) {
remotePath := fmt.Sprintf("/tmp/executable_%d", rand.Int())
err = q.CopyFile(user, path, remotePath)
if err != nil {
@ -346,12 +386,54 @@ func (q *QemuSystem) CopyAndRun(user, path string) (output string, err error) {
return q.Command(user, "chmod +x "+remotePath+" && "+remotePath)
}
func (q *QemuSystem) Debug(conn string) {
// Debug is for enable qemu debug and set hostname and port for listen
func (q *System) Debug(conn string) {
q.debug = true
q.gdb = conn
}
func (q QemuSystem) GetSshCommand() (cmd string) {
// SetKASLR is changing KASLR state through kernel boot args
func (q *System) SetKASLR(state bool) {
q.noKASLR = !state
}
// SetSMEP is changing SMEP state through kernel boot args
func (q *System) SetSMEP(state bool) {
q.noSMEP = !state
}
// SetSMAP is changing SMAP state through kernel boot args
func (q *System) SetSMAP(state bool) {
q.noSMAP = !state
}
// SetKPTI is changing KPTI state through kernel boot args
func (q *System) SetKPTI(state bool) {
q.noKPTI = !state
}
// GetKASLR is retrieve KASLR settings
func (q *System) GetKASLR() bool {
return !q.noKASLR
}
// GetSMEP is retrieve SMEP settings
func (q *System) GetSMEP() bool {
return !q.noSMEP
}
// GetSMAP is retrieve SMAP settings
func (q *System) GetSMAP() bool {
return !q.noSMAP
}
// GetKPTI is retrieve KPTI settings
func (q *System) GetKPTI() bool {
return !q.noKPTI
}
// GetSSHCommand returns command for connect to qemu machine over ssh
func (q System) GetSSHCommand() (cmd string) {
addrPort := strings.Split(q.sshAddrPort, ":")
addr := addrPort[0]
port := addrPort[1]

ファイルの表示

@ -2,7 +2,7 @@
// Use of this source code is governed by a AGPLv3 license
// (or later) that can be found in the LICENSE file.
package qemukernel
package qemu
import (
"crypto/sha512"
@ -20,46 +20,46 @@ func init() {
rand.Seed(time.Now().UnixNano())
}
func TestQemuSystemNew_InvalidKernelPath(t *testing.T) {
func TestSystemNew_InvalidKernelPath(t *testing.T) {
kernel := Kernel{Name: "Invalid", KernelPath: "/invalid/path"}
if _, err := NewQemuSystem(X86_64, kernel, "/bin/sh"); err == nil {
if _, err := NewSystem(X86x64, kernel, "/bin/sh"); err == nil {
t.Fatal(err)
}
}
func TestQemuSystemNew_InvalidQemuArch(t *testing.T) {
func TestSystemNew_InvalidQemuArch(t *testing.T) {
kernel := Kernel{Name: "Valid path", KernelPath: testConfigVmlinuz}
if _, err := NewQemuSystem(unsupported, kernel, "/bin/sh"); err == nil {
if _, err := NewSystem(unsupported, kernel, "/bin/sh"); err == nil {
t.Fatal(err)
}
}
func TestQemuSystemNew_InvalidQemuDrivePath(t *testing.T) {
func TestSystemNew_InvalidQemuDrivePath(t *testing.T) {
kernel := Kernel{Name: "Valid path", KernelPath: testConfigVmlinuz}
if _, err := NewQemuSystem(X86_64, kernel, "/invalid/path"); err == nil {
if _, err := NewSystem(X86x64, kernel, "/invalid/path"); err == nil {
t.Fatal(err)
}
}
func TestQemuSystemNew(t *testing.T) {
func TestSystemNew(t *testing.T) {
kernel := Kernel{Name: "Valid path", KernelPath: testConfigVmlinuz}
if _, err := NewQemuSystem(X86_64, kernel, "/bin/sh"); err != nil {
if _, err := NewSystem(X86x64, kernel, "/bin/sh"); err != nil {
t.Fatal(err)
}
}
func TestQemuSystemStart(t *testing.T) {
func TestSystemStart(t *testing.T) {
kernel := Kernel{Name: "Test kernel", KernelPath: testConfigVmlinuz}
qemu, err := NewQemuSystem(X86_64, kernel, "/bin/sh")
q, err := NewSystem(X86x64, kernel, "/bin/sh")
if err != nil {
t.Fatal(err)
}
if err = qemu.Start(); err != nil {
if err = q.Start(); err != nil {
t.Fatal(err)
}
qemu.Stop()
q.Stop()
}
func TestGetFreeAddrPort(t *testing.T) {
@ -71,39 +71,39 @@ func TestGetFreeAddrPort(t *testing.T) {
ln.Close()
}
func TestQemuSystemStart_Timeout(t *testing.T) {
func TestSystemStart_Timeout(t *testing.T) {
t.Parallel()
kernel := Kernel{Name: "Test kernel", KernelPath: testConfigVmlinuz}
qemu, err := NewQemuSystem(X86_64, kernel, "/bin/sh")
q, err := NewSystem(X86x64, kernel, "/bin/sh")
if err != nil {
t.Fatal(err)
}
qemu.Timeout = time.Second
q.Timeout = time.Second
if err = qemu.Start(); err != nil {
if err = q.Start(); err != nil {
t.Fatal(err)
}
time.Sleep(2 * time.Second)
if !qemu.Died {
if !q.Died {
t.Fatal("qemu does not died :c")
}
if !qemu.KilledByTimeout {
if !q.KilledByTimeout {
t.Fatal("qemu died not because of timeout O_o")
}
}
func startTestQemu(t *testing.T, timeout time.Duration) (q *QemuSystem, err error) {
func startTestQemu(t *testing.T, timeout time.Duration) (q *System, err error) {
t.Parallel()
kernel := Kernel{
Name: "Test kernel",
KernelPath: testConfigVmlinuz,
InitrdPath: testConfigInitrd,
}
q, err = NewQemuSystem(X86_64, kernel, testConfigRootfs)
q, err = NewSystem(X86x64, kernel, testConfigRootfs)
if err != nil {
return
}
@ -116,17 +116,18 @@ func startTestQemu(t *testing.T, timeout time.Duration) (q *QemuSystem, err erro
return
}
time.Sleep(time.Second)
return
}
func TestQemuSystemCommand(t *testing.T) {
qemu, err := startTestQemu(t, 0)
func TestSystemCommand(t *testing.T) {
q, err := startTestQemu(t, 0)
if err != nil {
t.Fatal(err)
}
defer qemu.Stop()
defer q.Stop()
output, err := qemu.Command("root", "cat /etc/shadow")
output, err := q.Command("root", "cat /etc/shadow")
if err != nil {
t.Fatal(err)
}
@ -134,7 +135,7 @@ func TestQemuSystemCommand(t *testing.T) {
t.Fatal("Wrong output from `cat /etc/shadow` by root")
}
output, err = qemu.Command("user", "cat /etc/passwd")
output, err = q.Command("user", "cat /etc/passwd")
if err != nil {
t.Fatal(err)
}
@ -142,18 +143,19 @@ func TestQemuSystemCommand(t *testing.T) {
t.Fatal("Wrong output from `cat /etc/passwd` by user")
}
output, err = qemu.Command("user", "cat /etc/shadow")
if err == nil { // unsucessful is good because user must not read /etc/shadow
_, err = q.Command("user", "cat /etc/shadow")
// unsuccessful is good because user must not read /etc/shadow
if err == nil {
t.Fatal("User have rights for /etc/shadow. WAT?!")
}
}
func TestQemuSystemCopyFile(t *testing.T) {
qemu, err := startTestQemu(t, 0)
func TestSystemCopyFile(t *testing.T) {
q, err := startTestQemu(t, 0)
if err != nil {
t.Fatal(err)
}
defer qemu.Stop()
defer q.Stop()
localPath := "/bin/sh"
@ -162,30 +164,31 @@ func TestQemuSystemCopyFile(t *testing.T) {
return
}
sha_local := fmt.Sprintf("%x", sha512.Sum512(content))
shaLocal := fmt.Sprintf("%x", sha512.Sum512(content))
err = qemu.CopyFile("user", localPath, "/tmp/test")
err = q.CopyFile("user", localPath, "/tmp/test")
if err != nil {
t.Fatal(err)
}
sha_remote, err := qemu.Command("user", "sha512sum /tmp/test")
shaRemote, err := q.Command("user", "sha512sum /tmp/test")
if err != nil {
t.Fatal(err)
}
sha_remote = strings.Split(sha_remote, " ")[0]
shaRemote = strings.Split(shaRemote, " ")[0]
if sha_local != sha_remote {
t.Fatal(fmt.Sprintf("Broken file (%s instead of %s)", sha_remote, sha_local))
if shaLocal != shaRemote {
t.Fatal(fmt.Sprintf("Broken file (%s instead of %s)",
shaRemote, shaLocal))
}
}
func TestQemuSystemCopyAndRun(t *testing.T) {
qemu, err := startTestQemu(t, 0)
func TestSystemCopyAndRun(t *testing.T) {
q, err := startTestQemu(t, 0)
if err != nil {
t.Fatal(err)
}
defer qemu.Stop()
defer q.Stop()
randStr := fmt.Sprintf("%d", rand.Int())
content := []byte("#!/bin/sh\n echo -n " + randStr + "\n")
@ -203,34 +206,35 @@ func TestQemuSystemCopyAndRun(t *testing.T) {
t.Fatal(err)
}
output, err := qemu.CopyAndRun("user", tmpfile.Name())
output, err := q.CopyAndRun("user", tmpfile.Name())
if err != nil {
t.Fatal(err)
}
if output != randStr {
t.Fatal("Wrong output from copyied executable (" + output + "," + randStr + ")")
t.Fatal("Wrong output from copyied executable (" +
output + "," + randStr + ")")
}
}
func TestQemuSystemCopyAndInsmod(t *testing.T) {
qemu, err := startTestQemu(t, 0)
func TestSystemCopyAndInsmod(t *testing.T) {
q, err := startTestQemu(t, 0)
if err != nil {
t.Fatal(err)
}
defer qemu.Stop()
defer q.Stop()
lsmodBefore, err := qemu.Command("root", "lsmod | wc -l")
lsmodBefore, err := q.Command("root", "lsmod | wc -l")
if err != nil {
t.Fatal(err)
}
_, err = qemu.CopyAndInsmod(testConfigSampleKo)
_, err = q.CopyAndInsmod(testConfigSampleKo)
if err != nil {
t.Fatal(err)
}
lsmodAfter, err := qemu.Command("root", "lsmod | wc -l")
lsmodAfter, err := q.Command("root", "lsmod | wc -l")
if err != nil {
t.Fatal(err)
}
@ -240,21 +244,21 @@ func TestQemuSystemCopyAndInsmod(t *testing.T) {
}
}
func TestQemuSystemKernelPanic(t *testing.T) {
qemu, err := startTestQemu(t, time.Minute)
func TestSystemKernelPanic(t *testing.T) {
q, err := startTestQemu(t, 5*time.Minute)
if err != nil {
t.Fatal(err)
}
defer qemu.Stop()
defer q.Stop()
// Enable sysrq
_, err = qemu.Command("root", "echo 1 > /proc/sys/kernel/sysrq")
_, err = q.Command("root", "echo 1 > /proc/sys/kernel/sysrq")
if err != nil {
t.Fatal(err)
}
// Trigger kernel panic
err = qemu.AsyncCommand("root", "sleep 1s && echo c > /proc/sysrq-trigger")
err = q.AsyncCommand("root", "sleep 1s && echo c > /proc/sysrq-trigger")
if err != nil {
t.Fatal(err)
}
@ -262,40 +266,41 @@ func TestQemuSystemKernelPanic(t *testing.T) {
// Wait for panic watcher timeout
time.Sleep(5 * time.Second)
if qemu.KilledByTimeout {
if q.KilledByTimeout {
t.Fatal("qemu is killed by timeout, not because of panic")
}
if !qemu.Died {
if !q.Died {
t.Fatal("qemu is not killed after kernel panic")
}
if !qemu.KernelPanic {
if !q.KernelPanic {
t.Fatal("qemu is died but there's no information about panic")
}
}
func TestQemuSystemRun(t *testing.T) {
qemu, err := startTestQemu(t, 0)
func TestSystemRun(t *testing.T) {
q, err := startTestQemu(t, 0)
if err != nil {
t.Fatal(err)
}
defer qemu.Stop()
defer q.Stop()
for {
_, err := qemu.Command("root", "echo")
_, err := q.Command("root", "echo")
if err == nil {
break
}
}
start := time.Now()
err = qemu.AsyncCommand("root", "sleep 10s")
err = q.AsyncCommand("root", "sleep 1m")
if err != nil {
t.Fatal(err)
}
if time.Since(start) > time.Second {
t.Fatalf("qemu.Run does not async (waited %s)", +time.Since(start))
if time.Since(start) > 10*time.Second {
t.Fatalf("q.AsyncCommand does not async (waited %s)",
time.Since(start))
}
}
@ -309,17 +314,19 @@ func openedPort(port int) bool {
return true
}
func TestQemuSystemDebug(t *testing.T) {
func TestSystemDebug(t *testing.T) {
t.Parallel()
kernel := Kernel{
KernelPath: testConfigVmlinuz,
InitrdPath: testConfigInitrd,
}
q, err := NewQemuSystem(X86_64, kernel, testConfigRootfs)
q, err := NewSystem(X86x64, kernel, testConfigRootfs)
if err != nil {
return
}
time.Sleep(time.Second)
port := 45256
q.Debug(fmt.Sprintf("tcp::%d", port))

ファイルの表示

@ -2,9 +2,9 @@
// Use of this source code is governed by a AGPLv3 license
// (or later) that can be found in the LICENSE file.
package qemukernel
package qemu
const testConfigVmlinuz = "../tools/qemu-debian-img/ubuntu1804.vmlinuz"
const testConfigInitrd = "../tools/qemu-debian-img/ubuntu1804.initrd"
const testConfigRootfs = "../tools/qemu-debian-img/ubuntu1804.img"
const testConfigSampleKo = "../tools/qemu-debian-img/ubuntu1804.ko"
const testConfigVmlinuz = "../tools/qemu-debian-img/ubuntu2004.vmlinuz"
const testConfigInitrd = "../tools/qemu-debian-img/ubuntu2004.initrd"
const testConfigRootfs = "../tools/qemu-debian-img/ubuntu2004.img"
const testConfigSampleKo = "../tools/qemu-debian-img/ubuntu2004.ko"

ファイルの表示

@ -1 +0,0 @@
output

ファイルの表示

@ -1,9 +0,0 @@
FROM ubuntu:14.04
RUN apt-get update
# for linux-image-3*-generic, so... CRUTCHES!
# E: Unable to locate package linux-image-3*-generic
ENV DEBIAN_FRONTEND=noninteractive
RUN apt search linux-image | grep -e linux-image-3 -e linux-image-4 | grep generic | cut -d '/' -f 1 | xargs apt install -y
RUN apt search linux-headers | grep -e linux-headers-3 -e linux-headers-4 | grep generic | cut -d '/' -f 1 | xargs apt install -y

ファイルの表示

@ -1 +0,0 @@
../../../qemu-debian-img/ubuntu1404.img

ファイルの表示

@ -1,4 +0,0 @@
FROM ubuntu:16.04
RUN apt-get update
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y linux-image-4*-generic linux-headers-*-generic build-essential wget git

ファイルの表示

@ -1 +0,0 @@
../../../qemu-debian-img/ubuntu1604.img

ファイルの表示

@ -1,4 +0,0 @@
FROM ubuntu:18.04
RUN apt-get update
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y linux-image-4*-generic linux-headers-*-generic build-essential wget git libelf-dev

ファイルの表示

@ -1 +0,0 @@
../../../qemu-debian-img/ubuntu1804.img

ファイルの表示

@ -1,29 +0,0 @@
#!/bin/sh -eux
mkdir -p output
echo > output/kernels.toml
find | grep Docker | sed 's/Dockerfile//' | while read DOCKER; do
CONTAINER_NAME=$(echo $DOCKER | sed -e 's;/;;g' -e 's;\.;;g' -e 's;\(.*\);\L\1;')
docker build -t ${CONTAINER_NAME} ${DOCKER}
docker run ${CONTAINER_NAME} bash -c 'ls /boot'
CONTAINER_ID=$(docker ps -a | grep ${CONTAINER_NAME} | awk '{print $1}' | head -n 1)
docker cp ${CONTAINER_ID}:/boot/. output/
DISTRO_NAME=$(echo $DOCKER | cut -d '/' -f 2)
DISTRO_VER=$(echo $DOCKER | cut -d '/' -f 3)
BOOT_FILES="$(docker run $CONTAINER_NAME ls /boot)"
for KERNEL_RELEASE in $(docker run $CONTAINER_NAME ls /lib/modules); do
echo '[[Kernels]]' >> output/kernels.toml
echo 'distro_type =' \"$DISTRO_NAME\" >> output/kernels.toml
echo 'distro_release =' \"$DISTRO_VER\" >> output/kernels.toml
echo 'kernel_release =' \"$KERNEL_RELEASE\" >> output/kernels.toml
echo 'container_name =' \"$CONTAINER_NAME\" >> output/kernels.toml
KERNEL_PATH=$(echo $BOOT_FILES | sed 's/ /\n/g' | grep $KERNEL_RELEASE | grep vmlinuz)
echo 'kernel_path =' \"$(realpath output/$KERNEL_PATH)\" >> output/kernels.toml
INITRD_PATH=$(echo $BOOT_FILES | sed 's/ /\n/g' | grep $KERNEL_RELEASE | grep init)
echo 'initrd_path =' \"$(realpath output/$INITRD_PATH)\" >> output/kernels.toml
ROOTFS_PATH=$(realpath $DOCKER/Image)
echo 'root_f_s =' \"$ROOTFS_PATH\" >> output/kernels.toml
echo >> output/kernels.toml
done
done
rm -rf output/grub

ファイルの表示

@ -1,4 +1,4 @@
# Copyright 2018 Mikhail Klementev. All rights reserved.
# Copyright 2019 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.
#
@ -7,11 +7,13 @@
# $ docker build -t gen-centos7-image .
# $ docker run --privileged -v $(pwd):/shared -t gen-centos7-image
#
# centos7.img will be created in current directory. You can change $(pwd) to
# different directory to use different destination for image.
# out_of_tree_centos_7.img will be created in current directory.
# You can change $(pwd) to different directory to use different destination
# for image.
#
FROM centos:7
RUN yum -y update
RUN yum -y groupinstall "Development Tools"
RUN yum -y install qemu-img e2fsprogs
@ -26,7 +28,7 @@ RUN yum --installroot=$TMPDIR \
--releasever=7 \
--disablerepo='*' \
--enablerepo=base \
-y install openssh-server
-y install openssh-server openssh-clients
RUN chroot $TMPDIR /bin/sh -c 'useradd -m user'
RUN sed -i 's/root:\*:/root::/' $TMPDIR/etc/shadow
@ -37,17 +39,15 @@ RUN sed -i '/PermitRootLogin/d' $TMPDIR/etc/ssh/sshd_config
RUN echo PermitRootLogin yes >> $TMPDIR/etc/ssh/sshd_config
# network workaround
# FIXME kernel module compatibility issues
RUN chmod +x $TMPDIR/etc/rc.local
RUN echo 'find /lib/modules | grep e1000.ko | xargs insmod -f' >> $TMPDIR/etc/rc.local
RUN echo 'dhclient' >> $TMPDIR/etc/rc.local
ENV IMAGEDIR=/tmp/image
ENV IMAGE=/shared/centos7.img
ENV IMAGE=/shared/out_of_tree_centos_7.img
RUN mkdir $IMAGEDIR
# Must be runned with --privileged because of /dev/loop
# Must be executed with --privileged because of /dev/loop
CMD qemu-img create $IMAGE 2G && \
mkfs.ext4 -F $IMAGE && \
mount -o loop $IMAGE $IMAGEDIR && \

56
tools/qemu-centos-img/8/Dockerfile ノーマルファイル
ファイルの表示

@ -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

6
tools/qemu-centos-img/8/generate.sh 実行可能ファイル
ファイルの表示

@ -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

35
tools/qemu-debian-img/14.04/Dockerfile ノーマルファイル
ファイルの表示

@ -0,0 +1,35 @@
# 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.
#
# Usage:
#
# $ docker build -t gen-ubuntu1404-image .
# $ docker run --privileged -v $(pwd):/shared -t gen-ubuntu1404-image
#
# ubuntu1404.img will be created in current directory. You can change $(pwd) to
# different directory to use different destination for image.
#
FROM ubuntu:14.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update
RUN apt-get install -y debootstrap qemu
ENV TMPDIR=/tmp/ubuntu
ENV IMAGEDIR=/tmp/image
ENV IMAGE=/shared/out_of_tree_ubuntu_14__04.img
ENV REPOSITORY=http://archive.ubuntu.com/ubuntu
ENV RELEASE=trusty
RUN mkdir $IMAGEDIR
# Must be executed with --privileged because of /dev/loop
CMD debootstrap --include=openssh-server,policykit-1 \
$RELEASE $TMPDIR $REPOSITORY && \
/shared/setup.sh $TMPDIR && \
qemu-img create $IMAGE 2G && \
mkfs.ext4 -F $IMAGE && \
mount -o loop $IMAGE $IMAGEDIR && \
cp -a $TMPDIR/* $IMAGEDIR/ && \
umount $IMAGEDIR

17
tools/qemu-debian-img/14.04/setup.sh 実行可能ファイル
ファイルの表示

@ -0,0 +1,17 @@
#!/bin/sh -eux
# 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.
TMPDIR=$1
chroot $TMPDIR /bin/sh -c 'useradd -m user'
sed -i 's/root:\*:/root::/' $TMPDIR/etc/shadow
sed -i 's/user:!!:/user::/' $TMPDIR/etc/shadow
echo auth sufficient pam_permit.so > $TMPDIR/etc/pam.d/sshd
sed -i '/PermitEmptyPasswords/d' $TMPDIR/etc/ssh/sshd_config
echo PermitEmptyPasswords yes >> $TMPDIR/etc/ssh/sshd_config
sed -i '/PermitRootLogin/d' $TMPDIR/etc/ssh/sshd_config
echo PermitRootLogin yes >> $TMPDIR/etc/ssh/sshd_config
echo '#!/bin/sh' > $TMPDIR/etc/rc.local
echo 'dhclient eth0' >> $TMPDIR/etc/rc.local
chmod +x $TMPDIR/etc/rc.local

ファイルの表示

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

ファイルの表示

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