From d7d5004f2b9722a78347a268cc8eebd50bd6952b Mon Sep 17 00:00:00 2001 From: Quentin JEROME Date: Thu, 28 Sep 2023 20:49:16 +0200 Subject: [PATCH] implemented a way to test kernel compatibility Signed-off-by: Quentin JEROME --- .gitignore | 1 + kunai/Cargo.toml | 6 +- kunai/src/{ => bin}/main.rs | 138 +++---------------------------- kunai/src/compat.rs | 6 ++ kunai/src/config.rs | 10 +-- kunai/src/info.rs | 87 +++++++++++++++++++- kunai/src/lib.rs | 48 +++++++++++ kunai/src/tests/kernel.rs | 158 ++++++++++++++++++++++++++++++++++++ kunai/src/util.rs | 16 ++-- kunai/src/util/bpf.rs | 7 +- scripts/ci/test_kernel.sh | 67 +++++++++++++++ 11 files changed, 398 insertions(+), 146 deletions(-) rename kunai/src/{ => bin}/main.rs (92%) create mode 100644 kunai/src/lib.rs create mode 100644 kunai/src/tests/kernel.rs create mode 100755 scripts/ci/test_kernel.sh diff --git a/.gitignore b/.gitignore index 08af971..65635ca 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ private **/*.rs.bk target/ .vim +cache diff --git a/kunai/Cargo.toml b/kunai/Cargo.toml index b8ea449..b5a4764 100644 --- a/kunai/Cargo.toml +++ b/kunai/Cargo.toml @@ -52,4 +52,8 @@ clap = { version = "4.3.4", features = ["derive"] } [[bin]] name = "kunai" -path = "src/main.rs" +path = "src/bin/main.rs" + +[[bin]] +name = "tests" +path = "src/tests/kernel.rs" diff --git a/kunai/src/main.rs b/kunai/src/bin/main.rs similarity index 92% rename from kunai/src/main.rs rename to kunai/src/bin/main.rs index d9a35dc..9eb88a3 100644 --- a/kunai/src/main.rs +++ b/kunai/src/bin/main.rs @@ -1,15 +1,15 @@ -mod cache; -mod compat; +/*mod cache; mod config; mod info; -mod util; +mod util;*/ use aya::maps::MapData; use bytes::BytesMut; use clap::Parser; use env_logger::Builder; -use info::{AdditionalFields, StdEventInfo}; use json::{object, JsonValue}; +use kunai::info::{AdditionalFields, CorrInfo, ProcFsInfo, ProcFsTaskInfo, StdEventInfo}; +use kunai::{cache, configure_probes, util}; use kunai_common::cgroup::Cgroup; use kunai_common::config::{BpfConfig, Filter}; @@ -24,9 +24,9 @@ use std::path::PathBuf; use std::sync::mpsc::{channel, Receiver, SendError, Sender}; use std::sync::Arc; +use kunai::util::*; use std::thread; use users::get_current_uid; -use util::*; use aya::{ include_bytes_aligned, @@ -41,20 +41,18 @@ use aya_log::BpfLogger; use kunai_common::{ events::{self, EncodedEvent, Event, *}, inspect_err, - uuid::TaskUuid, }; use log::{debug, error, info, warn}; -//use tokio::sync::{Barrier, Mutex}; use tokio::sync::{Barrier, Mutex}; use tokio::{signal, task, time}; -use cache::*; +use kunai::cache::*; -use crate::compat::{KernelVersion, Programs}; -use crate::config::Config; -use crate::util::namespaces::unshare; +use kunai::compat::{KernelVersion, Programs}; +use kunai::config::Config; +use kunai::util::namespaces::unshare; const PAGE_SIZE: usize = 4096; @@ -64,74 +62,6 @@ macro_rules! format_ptr { }; } -#[derive(Debug, Clone, Copy)] -struct ProcFsTaskInfo { - pid: i32, - uuid: TaskUuid, -} - -impl ProcFsTaskInfo { - fn new(start_time_clk_tck: u64, random: u32, pid: i32) -> Self { - // starttime in procfs is measured in tick count so we need to convert it - let clk_tck = get_clk_tck() as u64; - - Self { - pid, - uuid: TaskUuid::new( - // convert time to the same scale as starttime in task_struct - start_time_clk_tck * 1_000_000_000 / clk_tck, - random, - pid as u32, - ), - } - } -} - -#[derive(Debug, Clone, Copy)] -struct ProcFsInfo { - task: ProcFsTaskInfo, - parent: Option, -} - -#[derive(Debug, Clone)] -enum CorrInfo { - ProcFs(ProcFsInfo), - Event(StdEventInfo), -} - -impl CorrInfo { - fn corr_key(tuuid: TaskUuid) -> u128 { - // in task_struct start_time has a higher resolution so we need to scale it - // down in order to have a comparable value with the procfs one - let start_time_sec = tuuid.start_time_ns / 1_000_000_000; - TaskUuid::new(start_time_sec, tuuid.random, tuuid.pid).into() - } - - #[inline] - fn pid(&self) -> i32 { - match self { - Self::ProcFs(pi) => pi.task.pid, - Self::Event(si) => si.info.process.tgid, - } - } - - #[inline] - fn correlation_key(&self) -> u128 { - match self { - Self::ProcFs(pi) => Self::corr_key(pi.task.uuid), - Self::Event(si) => Self::corr_key(si.info.process.tg_uuid), - } - } - - #[inline] - fn parent_correlation_key(&self) -> Option { - match self { - Self::ProcFs(pi) => Some(Self::corr_key(pi.parent?.uuid)), - Self::Event(si) => Some(Self::corr_key(si.info.parent.tg_uuid)), - } - } -} - #[derive(Debug, Clone)] struct CorrelationData { image: PathBuf, @@ -266,10 +196,7 @@ impl EventProcessor { )); } - let ci = CorrInfo::ProcFs(ProcFsInfo { - task: pi, - parent: ppi, - }); + let ci = CorrInfo::from(ProcFsInfo::new(pi, ppi)); let ck = ci.correlation_key(); @@ -1378,7 +1305,7 @@ async fn main() -> Result<(), anyhow::Error> { BpfLoader::new() .verifier_log_level(verifier_level) .load(include_bytes_aligned!( - "../../target/bpfel-unknown-none/debug/kunai-ebpf" + "../../../target/bpfel-unknown-none/debug/kunai-ebpf" ))?; #[cfg(not(debug_assertions))] @@ -1386,7 +1313,7 @@ async fn main() -> Result<(), anyhow::Error> { BpfLoader::new() .verifier_log_level(verifier_level) .load(include_bytes_aligned!( - "../../target/bpfel-unknown-none/release/kunai-ebpf" + "../../../target/bpfel-unknown-none/release/kunai-ebpf" ))?; if let Err(e) = BpfLogger::init(&mut bpf) { @@ -1418,46 +1345,7 @@ async fn main() -> Result<(), anyhow::Error> { let mut programs = Programs::from_bpf(&mut bpf); - programs.expect_mut("execve.security_bprm_check").prio = 0; - - programs.expect_mut("execve.exit.bprm_execve").prio = 20; - programs - .expect_mut("execve.exit.bprm_execve") - .min_kernel(kernel!(5, 9)); - - programs.expect_mut("syscalls.sys_exit_execve").prio = 20; - programs - .expect_mut("syscalls.sys_exit_execve") - .max_kernel(kernel!(5, 9)); - - programs - .expect_mut("syscalls.sys_exit_execveat") - .max_kernel(kernel!(5, 9)); - - // bpf probes - programs.expect_mut("entry.security_bpf_prog").prio = 90; - programs.expect_mut("exit.bpf_prog_load").prio = 100; - - // fd_install - programs.expect_mut("fd.fd_install").prio = 0; - programs.expect_mut("fd.entry.__fdget").prio = 0; - programs.expect_mut("fd.exit.__fdget").prio = 10; - - // kernel function name changed above 5.9 - if current_kernel < kernel!(5, 9) { - // kernel_clone -> _do_fork - programs - .expect_mut("kprobe.enter.kernel_clone") - .rename("kprobe.enter._do_fork"); - - // path_mount -> do_mount - programs - .expect_mut("fs.exit.path_mount") - .rename("fs.exit.do_mount") - } - - // mmap probe - programs.expect_mut("syscalls.sys_enter_mmap").prio = 90; + configure_probes(&mut programs, current_kernel); // generic program loader for (_, mut p) in programs.into_vec_sorted_by_prio() { diff --git a/kunai/src/compat.rs b/kunai/src/compat.rs index b550c6c..47bfc3f 100644 --- a/kunai/src/compat.rs +++ b/kunai/src/compat.rs @@ -265,6 +265,12 @@ impl<'a> Program<'a> { self.name = new.as_ref().to_string(); } + pub fn rename_if>(&mut self, cond: bool, new: T) { + if cond { + self.rename(new) + } + } + pub fn enable(&mut self) { self.enable = true } diff --git a/kunai/src/config.rs b/kunai/src/config.rs index f5a89cc..c2dd24a 100644 --- a/kunai/src/config.rs +++ b/kunai/src/config.rs @@ -21,15 +21,15 @@ pub struct Event { } impl Event { - pub(crate) fn name(&self) -> &str { + pub fn name(&self) -> &str { &self.name } - pub(crate) fn disable(&mut self) { + pub fn disable(&mut self) { self.enable = false } - pub(crate) fn enable(&mut self) { + pub fn enable(&mut self) { self.enable = true } } @@ -87,11 +87,11 @@ impl Config { Ok(()) } - pub(crate) fn enable_all(&mut self) { + pub fn enable_all(&mut self) { self.events.iter_mut().for_each(|e| e.enable()) } - pub(crate) fn disable_all(&mut self) { + pub fn disable_all(&mut self) { self.events.iter_mut().for_each(|e| e.disable()) } } diff --git a/kunai/src/info.rs b/kunai/src/info.rs index b9d6456..65c1a20 100644 --- a/kunai/src/info.rs +++ b/kunai/src/info.rs @@ -1,8 +1,91 @@ use chrono::{DateTime, SecondsFormat, Utc}; use json::{object, JsonValue}; -use kunai_common::events::{self, EventInfo}; +use kunai_common::{ + events::{self, EventInfo}, + uuid::TaskUuid, +}; -use crate::CorrInfo; +use crate::util::get_clk_tck; + +#[derive(Debug, Clone, Copy)] +pub struct ProcFsTaskInfo { + pid: i32, + uuid: TaskUuid, +} + +impl ProcFsTaskInfo { + pub fn new(start_time_clk_tck: u64, random: u32, pid: i32) -> Self { + // starttime in procfs is measured in tick count so we need to convert it + let clk_tck = get_clk_tck() as u64; + + Self { + pid, + uuid: TaskUuid::new( + // convert time to the same scale as starttime in task_struct + start_time_clk_tck * 1_000_000_000 / clk_tck, + random, + pid as u32, + ), + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct ProcFsInfo { + task: ProcFsTaskInfo, + parent: Option, +} + +impl ProcFsInfo { + pub fn new(task: ProcFsTaskInfo, parent: Option) -> Self { + ProcFsInfo { task, parent } + } +} + +#[derive(Debug, Clone)] +pub enum CorrInfo { + ProcFs(ProcFsInfo), + Event(StdEventInfo), +} + +impl From for CorrInfo { + fn from(value: ProcFsInfo) -> Self { + Self::ProcFs(value) + } +} + +impl CorrInfo { + fn corr_key(tuuid: TaskUuid) -> u128 { + // in task_struct start_time has a higher resolution so we need to scale it + // down in order to have a comparable value with the procfs one + let start_time_sec = tuuid.start_time_ns / 1_000_000_000; + TaskUuid::new(start_time_sec, tuuid.random, tuuid.pid).into() + } + + #[inline] + pub fn pid(&self) -> i32 { + match self { + Self::ProcFs(pi) => pi.task.pid, + Self::Event(si) => si.info.process.tgid, + } + } + + #[inline] + pub fn correlation_key(&self) -> u128 { + match self { + Self::ProcFs(pi) => Self::corr_key(pi.task.uuid), + Self::Event(si) => Self::corr_key(si.info.process.tg_uuid), + } + } + + #[inline] + pub fn parent_correlation_key(&self) -> Option { + match self { + Self::ProcFs(pi) => Some(Self::corr_key(pi.parent?.uuid)), + Self::Event(si) => Some(Self::corr_key(si.info.parent.tg_uuid)), + } + } +} #[derive(Default, Debug, Clone)] pub struct AdditionalFields { diff --git a/kunai/src/lib.rs b/kunai/src/lib.rs new file mode 100644 index 0000000..9790e6c --- /dev/null +++ b/kunai/src/lib.rs @@ -0,0 +1,48 @@ +use compat::{KernelVersion, Programs}; + +pub mod cache; +pub mod compat; +pub mod config; +pub mod info; +pub mod util; + +pub fn configure_probes(programs: &mut Programs, for_kernel: KernelVersion) { + programs.expect_mut("execve.security_bprm_check").prio = 0; + + programs.expect_mut("execve.exit.bprm_execve").prio = 20; + programs + .expect_mut("execve.exit.bprm_execve") + .min_kernel(kernel!(5, 9)); + + programs.expect_mut("syscalls.sys_exit_execve").prio = 20; + programs + .expect_mut("syscalls.sys_exit_execve") + .max_kernel(kernel!(5, 9)); + + programs + .expect_mut("syscalls.sys_exit_execveat") + .max_kernel(kernel!(5, 9)); + + // bpf probes + programs.expect_mut("entry.security_bpf_prog").prio = 90; + programs.expect_mut("exit.bpf_prog_load").prio = 100; + + // fd_install + programs.expect_mut("fd.fd_install").prio = 0; + programs.expect_mut("fd.entry.__fdget").prio = 0; + programs.expect_mut("fd.exit.__fdget").prio = 10; + + // kernel function name changed above 5.9 + // kernel_clone -> _do_fork + programs + .expect_mut("kprobe.enter.kernel_clone") + .rename_if(for_kernel < kernel!(5, 9), "kprobe.enter._do_fork"); + + // path_mount -> do_mount + programs + .expect_mut("fs.exit.path_mount") + .rename_if(for_kernel < kernel!(5, 9), "fs.exit.do_mount"); + + // mmap probe + programs.expect_mut("syscalls.sys_enter_mmap").prio = 90; +} diff --git a/kunai/src/tests/kernel.rs b/kunai/src/tests/kernel.rs new file mode 100644 index 0000000..037d2db --- /dev/null +++ b/kunai/src/tests/kernel.rs @@ -0,0 +1,158 @@ +use anyhow::anyhow; +use aya::{include_bytes_aligned, BpfLoader, Btf, VerifierLogLevel}; +use env_logger::Builder; +use kunai::compat::{KernelVersion, Programs}; +use kunai::configure_probes; +use libc::{rlimit, LINUX_REBOOT_CMD_POWER_OFF, RLIMIT_MEMLOCK, RLIM_INFINITY}; +use log::{error, info, warn}; +use std::{ffi::CString, panic}; + +fn mount(src: &str, target: &str, filesystem_type: &str) -> anyhow::Result<()> { + // Paths and options + let source = CString::new(src).expect("CString creation failed"); + let target = CString::new(target).expect("CString creation failed"); + let filesystem_type = CString::new(filesystem_type).expect("CString creation failed"); + //let flags = MS_NOSUID | MS_RDONLY; + let flags = 0; + + // Mount sysfs + let result = unsafe { + libc::mount( + source.as_ptr(), + target.as_ptr(), + filesystem_type.as_ptr(), + flags, + std::ptr::null(), + ) + }; + + if result != 0 { + return Err(anyhow!("failed to mount sysfs")); + } + + Ok(()) +} + +fn integration() -> anyhow::Result<()> { + let verifier_level = VerifierLogLevel::STATS; + + let current_kernel = KernelVersion::from_sys()?; + info!("linux kernel: {current_kernel}"); + + info!("mounting sysfs"); + // creating /sys mountpoint + std::fs::create_dir_all("/sys")?; + mount("none", "/sys", "sysfs")?; + info!("mounting tracefs"); + mount("none", "/sys/kernel/tracing", "tracefs")?; + + info!("loading ebpf bytes"); + #[cfg(debug_assertions)] + let mut bpf = + BpfLoader::new() + .verifier_log_level(verifier_level) + .load(include_bytes_aligned!( + "../../../target/bpfel-unknown-none/debug/kunai-ebpf" + ))?; + + #[cfg(not(debug_assertions))] + let mut bpf = + BpfLoader::new() + .verifier_log_level(verifier_level) + .load(include_bytes_aligned!( + "../../../target/bpfel-unknown-none/release/kunai-ebpf" + ))?; + + let mut programs = Programs::from_bpf(&mut bpf); + + configure_probes(&mut programs, current_kernel); + + info!("getting BTF"); + let btf = Btf::from_sys_fs()?; + + // generic program loader + for (_, mut p) in programs.into_vec_sorted_by_prio() { + info!( + "loading: {} {:?} with priority={}", + p.name, + p.prog_type(), + p.prio + ); + + if !p.enable { + warn!("{} probe has been disabled", p.name); + continue; + } + + if !p.is_compatible(¤t_kernel) { + warn!( + "{} probe is not compatible with current kernel: min={} max={} current={}", + p.name, + p.compat.min(), + p.compat.max(), + current_kernel + ); + continue; + } + + p.attach(&btf)?; + } + + Ok(()) +} + +fn getrlimit() -> anyhow::Result { + let mut rlim: rlimit = rlimit { + rlim_cur: 0, // Set the soft limit to 0 initially + rlim_max: 0, // Set the hard limit to 0 initially + }; + + // Get the current limit + if unsafe { libc::getrlimit(RLIMIT_MEMLOCK, &mut rlim) } != 0 { + return Err(anyhow!("failed to get rlimit")); + } + + Ok(rlim) +} + +fn setrlimit(rlimit: &rlimit) -> anyhow::Result<()> { + // Set the new limit + if unsafe { libc::setrlimit(RLIMIT_MEMLOCK, rlimit) } != 0 { + return Err(anyhow!("failed to get rlimit")); + } + Ok(()) +} + +fn custom_panic_handler(info: &panic::PanicInfo) { + // Your custom panic handling code goes here + println!("\x1b[1;31m{info}\x1b[0m"); + // we power-off the system + unsafe { libc::reboot(LINUX_REBOOT_CMD_POWER_OFF) }; +} + +fn main() -> ! { + panic::set_hook(Box::new(custom_panic_handler)); + + println!("initializing logger"); + // building the logger + Builder::new().filter_level(log::LevelFilter::Info).init(); + + let mut rlimit = getrlimit().expect("failed to get rlimit"); + info!("cur:{} max:{}", rlimit.rlim_cur, rlimit.rlim_max); + rlimit.rlim_cur = RLIM_INFINITY; + rlimit.rlim_max = RLIM_INFINITY; + + setrlimit(&rlimit).expect("failed to set rlimit"); + getrlimit().expect("failed to get rlimit after update"); + + let res = integration(); + if res.is_err() { + error!("FAILURE: {}", res.err().unwrap()); + } else { + info!("SUCCESS"); + } + + unsafe { libc::reboot(LINUX_REBOOT_CMD_POWER_OFF) }; + + core::unreachable!() +} diff --git a/kunai/src/util.rs b/kunai/src/util.rs index 3a06675..0763c81 100644 --- a/kunai/src/util.rs +++ b/kunai/src/util.rs @@ -10,7 +10,7 @@ pub mod bpf; pub mod namespaces; #[inline] -pub(crate) fn is_public_ip(ip: IpAddr) -> bool { +pub fn is_public_ip(ip: IpAddr) -> bool { let ip_network: IpNetwork = ip.into(); match ip_network { @@ -19,14 +19,14 @@ pub(crate) fn is_public_ip(ip: IpAddr) -> bool { } } -pub(crate) fn get_clk_tck() -> i64 { +pub fn get_clk_tck() -> i64 { unsafe { libc::sysconf(libc::_SC_CLK_TCK) } } // inspired from: https://github.com/itchyny/uptime-rs // the code panics if we cannot retrieve boot time #[allow(dead_code)] -pub(crate) fn get_boot_time() -> DateTime { +pub fn get_boot_time() -> DateTime { let mut info: libc::sysinfo = unsafe { zeroed() }; let ret = unsafe { libc::sysinfo(&mut info) }; if ret != 0 { @@ -51,7 +51,7 @@ pub enum RandError { PartiallyRandomized, } -pub(crate) fn getrandom() -> Result { +pub fn getrandom() -> Result { let mut t = MaybeUninit::::uninit(); let buflen = size_of::(); let rc = unsafe { libc::getrandom(t.as_mut_ptr() as *mut _, buflen, 0) }; @@ -65,28 +65,28 @@ pub(crate) fn getrandom() -> Result { } #[inline] -pub(crate) fn md5_data>(data: T) -> String { +pub fn md5_data>(data: T) -> String { let mut h = Md5::new(); h.update(data.as_ref()); hex::encode(h.finalize()) } #[inline] -pub(crate) fn sha1_data>(data: T) -> String { +pub fn sha1_data>(data: T) -> String { let mut h = Sha1::new(); h.update(data.as_ref()); hex::encode(h.finalize()) } #[inline] -pub(crate) fn sha256_data>(data: T) -> String { +pub fn sha256_data>(data: T) -> String { let mut h = Sha256::new(); h.update(data.as_ref()); hex::encode(h.finalize()) } #[inline] -pub(crate) fn sha512_data>(data: T) -> String { +pub fn sha512_data>(data: T) -> String { let mut h = Sha512::new(); h.update(data.as_ref()); hex::encode(h.finalize()) diff --git a/kunai/src/util/bpf.rs b/kunai/src/util/bpf.rs index a3d93eb..b05a379 100644 --- a/kunai/src/util/bpf.rs +++ b/kunai/src/util/bpf.rs @@ -56,10 +56,7 @@ impl From for BpfError { } } -pub(crate) fn bpf_dump_xlated_by_id_and_tag( - prog_id: u32, - prog_tag: [u8; 8], -) -> Result, BpfError> { +pub fn bpf_dump_xlated_by_id_and_tag(prog_id: u32, prog_tag: [u8; 8]) -> Result, BpfError> { let raw_fd = bpf_prog_get_fd_by_id(prog_id)?; // we first issue a call to get information about program let info = bpf_prog_get_info_by_fd(raw_fd)?; @@ -84,7 +81,7 @@ pub(crate) fn bpf_dump_xlated_by_id_and_tag( Ok(insns) } -pub(crate) fn bpf_type_to_string(t: u32) -> String { +pub fn bpf_type_to_string(t: u32) -> String { if t > 31 { return "unknown".into(); } diff --git a/scripts/ci/test_kernel.sh b/scripts/ci/test_kernel.sh new file mode 100755 index 0000000..af4695c --- /dev/null +++ b/scripts/ci/test_kernel.sh @@ -0,0 +1,67 @@ +#!/bin/bash +set -euxo pipefail + +cargo xtask build --release -- --bin tests + +tmp_dir=$(mktemp -d) +kernel="${!#}" +debian_url="https://ftp.us.debian.org/debian/pool/main/l/linux/" +# there is no https for ubuntu.com !!!! +ubuntu_url="http://security.ubuntu.com/ubuntu/pool/main/l/linux/" +initramfs=${tmp_dir}/initramfs.img + + +# building initramfs with our test binary as init +cp target/x86_64-unknown-linux-musl/release/tests $tmp_dir/init +echo "init" | cpio -D $tmp_dir -o -H newc > $initramfs + +distro="debian" +base_url="$debian_url" + +image=$(curl -k $base_url | grep -oP "linux-image-.*?-cloud-amd64-unsigned.*?.deb" | grep -F "linux-image-${kernel}" | tail -n 1 || true) + +if [[ ! $image ]] +then + distro="ubuntu" + base_url="$ubuntu_url" + image=$(curl $base_url | grep -oP 'linux-image-unsigned-.*?-generic.*?amd64.deb' | grep -F "linux-image-unsigned-${kernel}" | tail -n 1) + + # no kernel found even in ubuntu repo + if [[ ! $image ]] + then + echo "no image found for kernel ${kernel}" + exit 1 + fi +fi + +if [[ $(echo $@ | grep -c -- '--cache-key') > 0 ]] +then + echo "amd64-vmlinuz-$(md5sum <<<$image | cut -d ' ' -f 1)" + exit 0 +fi + +precise_version=$(cut -d '_' -f 2 <<<$image) + +cache_root="cache/vmlinuz" +cache_dir="${cache_root}/amd64/${distro}/${image}" + +if [[ ! -d $cache_dir ]] +then + curl -k ${base_url}${image} | dpkg --fsys-tarfile - | tar -C ${tmp_dir} --wildcards --extract "./boot/*vmlinuz*" + # we create cache dir + mkdir -p $cache_dir + mv ${tmp_dir}/boot/vmlinuz* $cache_dir +fi + +# running qemu +kernel_logs=${tmp_dir}/kernel.log +QEMU_ARGS=() +if [ -c /dev/kvm ] +then + QEMU_ARGS+=(-accel kvm) +fi + +qemu-system-x86_64 -kernel ${cache_dir}/vmlinuz* -m 512m -initrd $initramfs -append console=ttyS0 -nographic ${QEMU_ARGS[@]} | tee ${kernel_logs} + +tail -n 30 $kernel_logs | grep 'SUCCESS' > /dev/null +