diff --git a/src/agent/machine/machine.rs b/src/agent/machine/machine.rs index 491cc96..3a54a31 100644 --- a/src/agent/machine/machine.rs +++ b/src/agent/machine/machine.rs @@ -380,8 +380,6 @@ impl Machine { .collect(), logs_telemetry_config: config.logs_telemetry_config.clone(), }; - let takeoff_args_str = takeoff_args.encode()?; - kernel_cmd.insert_str(format!("takeoff={}", takeoff_args_str))?; let mut io_manager = IoManager::new(); let mut irq_allocator = IrqAllocator::new(SERIAL_IRQ)?; @@ -408,6 +406,7 @@ impl Machine { &config, &kvm, vm_fd.clone(), + &takeoff_args, &guest_memory, &mut irq_allocator, &mut mmio_allocator, diff --git a/src/agent/machine/state_machine.rs b/src/agent/machine/state_machine.rs index d6d02e8..b8c0ab4 100644 --- a/src/agent/machine/state_machine.rs +++ b/src/agent/machine/state_machine.rs @@ -293,18 +293,18 @@ impl MachineStateMachine { async fn handle_user_start(&mut self) -> Result<()> { let is_first_start = self.current_state == MachineState::Idle; + // Reset guest manager for non-first starts + if !is_first_start { + self.resources + .devices + .guest_manager + .lock() + .expect("Failed to lock guest manager") + .set_snapshot_strategy(None); + } + match self.current_state { MachineState::Idle | MachineState::Suspended => { - // Reset guest manager for non-first starts - if !is_first_start { - self.resources - .devices - .guest_manager - .lock() - .expect("Failed to lock guest manager") - .set_snapshot_strategy(None); - } - // Set state to Booting and start VCPUs // For first start: SystemDeviceReady will transition to Ready // For resume from suspend: SystemVcpuRestarted will transition to Ready diff --git a/src/agent/machine/vm/constants.rs b/src/agent/machine/vm/constants.rs index c7ed6d8..10790b4 100644 --- a/src/agent/machine/vm/constants.rs +++ b/src/agent/machine/vm/constants.rs @@ -19,6 +19,7 @@ pub const BLK_SHIFT: u32 = 12; pub const BLK_SIZE: usize = 1 << BLK_SHIFT; pub const CMDLINE_CAPACITY: usize = 4096; +pub const CMDLINE_SAFE_LIMIT: usize = 2048; pub const X86_CR0_PE: u64 = 0x1; pub const X86_CR0_PG: u64 = 0x8000_0000; diff --git a/src/agent/machine/vm/devices/meta/guest_manager.rs b/src/agent/machine/vm/devices/meta/guest_manager.rs index f0ffe93..141ca59 100644 --- a/src/agent/machine/vm/devices/meta/guest_manager.rs +++ b/src/agent/machine/vm/devices/meta/guest_manager.rs @@ -5,6 +5,7 @@ use std::{ }; use tracing::warn; +use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap}; use crate::agent::machine::{ machine::SnapshotStrategy, @@ -35,6 +36,11 @@ const CMD_FLASH_UNLOCK: u8 = CMD_OFFSET + 1; const READ_OFFSET_LAST_BOOT_TIME: u64 = 0; const READ_OFFSET_FIRST_BOOT_TIME: u64 = 8; +const READ_OFFSET_TAKEOFF_ARGS_LEN: u64 = 16; + +const WRITE_OFFSET_TRIGGER: u64 = 0; +const WRITE_OFFSET_CMD: u64 = 8; +const WRITE_OFFSET_TAKEOFF_ARGS: u64 = 16; #[allow(unused)] #[derive(Debug, Clone, Copy)] @@ -131,6 +137,8 @@ impl Cmd { } pub struct GuestManagerDevice { + memory: GuestMemoryMmap, + takeoff_args: Vec, listen_trigger_count: u32, device_event_tx: async_broadcast::Sender, first_boot_duration: Option, @@ -148,10 +156,14 @@ impl GuestManagerDevice { } pub fn new( + memory: GuestMemoryMmap, + takeoff_args: Vec, device_event_tx: async_broadcast::Sender, snapshot_strategy: Option, ) -> Arc> { let guest_manager = Self { + memory, + takeoff_args, snapshot_strategy, listen_trigger_count: 0, first_boot_duration: None, @@ -185,7 +197,8 @@ impl GuestManagerDevice { .map(|duration| duration.as_micros() as u64), READ_OFFSET_FIRST_BOOT_TIME => self .first_boot_duration - .map(|duration| duration.as_micros() as u64), + .map(|duration: Duration| duration.as_micros() as u64), + READ_OFFSET_TAKEOFF_ARGS_LEN => self.process_args_read(), _ => { warn!("unhandled read offset {}", offset); return; @@ -197,13 +210,15 @@ impl GuestManagerDevice { } pub fn mmio_write(&mut self, offset: vm_device::bus::MmioAddressOffset, data: &[u8]) -> bool { - if offset == 0 { - return self.process_trigger(data); - } else if offset == 8 { - return self.process_cmd(data); + match offset { + WRITE_OFFSET_TRIGGER => self.process_trigger(data), + WRITE_OFFSET_CMD => self.process_cmd(data), + WRITE_OFFSET_TAKEOFF_ARGS => self.process_args_write(data), + _ => { + warn!("unhandled write offset {}", offset); + false + } } - - false } fn process_trigger(&mut self, data: &[u8]) -> bool { @@ -275,4 +290,24 @@ impl GuestManagerDevice { return false; } + + fn process_args_read(&mut self) -> Option { + let len = self.takeoff_args.len(); + return Some(len as u64); + } + + fn process_args_write(&mut self, data: &[u8]) -> bool { + let ptr = u64::from_le_bytes([ + data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], + ]); + + if let Err(e) = self + .memory + .write_slice(&self.takeoff_args, GuestAddress(ptr)) + { + warn!("Failed to write hello world! to ptr {}: {:?}", ptr, e); + } + + return false; + } } diff --git a/src/agent/machine/vm/devices/mod.rs b/src/agent/machine/vm/devices/mod.rs index 1fe1399..d899eb3 100644 --- a/src/agent/machine/vm/devices/mod.rs +++ b/src/agent/machine/vm/devices/mod.rs @@ -14,6 +14,7 @@ use event_manager::{EventManager, MutEventSubscriber}; use kvm_bindings::{KVM_PIT_SPEAKER_DUMMY, kvm_pit_config, kvm_userspace_memory_region}; use kvm_ioctls::{Kvm, VmFd}; use linux_loader::loader::Cmdline; +use takeoff_proto::proto::TakeoffInitArgs; use vm_allocator::{AddressAllocator, AllocPolicy}; use vm_device::{ bus::{BusRange, MmioAddress, PioAddress, PioRange}, @@ -57,6 +58,7 @@ pub async fn setup_devices( machine_config: &MachineConfig, kvm: &Kvm, vm_fd: Arc, + takeoff_args: &TakeoffInitArgs, memory: &GuestMemoryMmap, irq_allocator: &mut IrqAllocator, mmio_allocator: &mut AddressAllocator, @@ -79,7 +81,16 @@ pub async fn setup_devices( snapshot_strategy, .. } => Some(snapshot_strategy.clone()), }; - let guest_manager = GuestManagerDevice::new(device_event_tx.clone(), snapshot_strategy); + + let takeoff_args_str = takeoff_args.encode()?; + let takeoff_args_bytes = takeoff_args_str.as_bytes().to_vec(); + + let guest_manager = GuestManagerDevice::new( + memory.clone(), + takeoff_args_bytes, + device_event_tx.clone(), + snapshot_strategy, + ); let net = setup_network_device( vm_fd.clone(), diff --git a/src/agent/machine/vm/kernel.rs b/src/agent/machine/vm/kernel.rs index f71afab..90ec423 100644 --- a/src/agent/machine/vm/kernel.rs +++ b/src/agent/machine/vm/kernel.rs @@ -15,7 +15,7 @@ use vm_memory::{ use crate::agent::machine::{ machine::MachineConfig, vm::constants::{ - CMDLINE_CAPACITY, CMDLINE_START, E820_RAM, EBDA_START, HIGH_RAM_START, + CMDLINE_CAPACITY, CMDLINE_SAFE_LIMIT, CMDLINE_START, E820_RAM, EBDA_START, HIGH_RAM_START, KERNEL_BOOT_FLAG_MAGIC, KERNEL_HDR_MAGIC, KERNEL_LOADER_OTHER, KERNEL_MIN_ALIGNMENT_BYTES, PAGE_SIZE, ZERO_PAGE_START, }, @@ -64,7 +64,18 @@ pub async fn load_kernel( boot_params.hdr.ramdisk_size = initrd_size as u32; boot_params.hdr.cmd_line_ptr = CMDLINE_START as u32; - boot_params.hdr.cmdline_size = kernel_cmd.as_cstring()?.as_bytes().len() as u32; + let cmdline_cstring = kernel_cmd.as_cstring()?; + let cmdline_bytes = cmdline_cstring.as_bytes(); + boot_params.hdr.cmdline_size = cmdline_bytes.len() as u32; + + // Validate command line size against safe kernel limit + if cmdline_bytes.len() > CMDLINE_SAFE_LIMIT { + bail!( + "Command line too large: {} bytes exceeds safe kernel limit of {} bytes.", + cmdline_bytes.len(), + CMDLINE_SAFE_LIMIT + ); + } linux_loader::loader::load_cmdline(memory, GuestAddress(CMDLINE_START), kernel_cmd)?; diff --git a/src/takeoff/src/guest.rs b/src/takeoff/src/guest.rs index cf18d4d..1f7d3dc 100644 --- a/src/takeoff/src/guest.rs +++ b/src/takeoff/src/guest.rs @@ -1,6 +1,7 @@ use std::{ ffi::c_void, fs::File, + io::{Read, Seek, SeekFrom}, os::fd::{AsRawFd, FromRawFd}, }; @@ -12,6 +13,7 @@ use nix::{ stat::Mode, }, }; +use takeoff_proto::proto::TakeoffInitArgs; const PAGE_SIZE: usize = 4096; const MAGIC_MMIO_ADDR: i64 = 0xd0000000; @@ -56,8 +58,6 @@ impl GuestManager { pub fn set_exit_code(&self, code: i32) { unsafe { let ptr = self.map_base.as_ptr() as *mut u64; - // first 4 bytes are the trigger code - // last byte is 0x04 let mut cmd = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04]; cmd[..4].copy_from_slice(&code.to_le_bytes()); @@ -68,6 +68,26 @@ impl GuestManager { } } + pub fn read_takeoff_args(&self) -> Result { + let len = unsafe { + let ptr = self.map_base.as_ptr().add(16) as *const u64; + ptr.read_volatile() + }; + + let mut buffer = vec![0u8; len as usize]; + let buff_slice = buffer.as_mut_slice(); + let val = self.virt_to_phys(buff_slice.as_ptr() as u64)?; + + unsafe { + let ptr: *mut u64 = self.map_base.as_ptr().add(16) as *mut u64; + ptr.write_volatile(val); + } + + let str = String::from_utf8_lossy(&buff_slice[0..len as usize]).to_string(); + + TakeoffInitArgs::decode(&str) + } + #[allow(dead_code)] pub fn trigger_manual_snapshot(&self) { unsafe { @@ -95,4 +115,25 @@ impl GuestManager { time_us } } + + pub fn virt_to_phys(&self, virt_addr: u64) -> Result { + let page_size = 4096; + let virt_pfn = virt_addr / page_size; + + let mut pagemap = File::open("/proc/self/pagemap")?; + pagemap.seek(SeekFrom::Start((virt_pfn * 8) as u64))?; + + let mut page_info = [0u8; 8]; + pagemap.read_exact(&mut page_info)?; + let page_info = u64::from_le_bytes(page_info); + + if (page_info & (1u64 << 63)) == 0 { + anyhow::bail!("Page not present"); + } + + let phys_pfn = page_info & ((1u64 << 55) - 1); + let phys_addr = (phys_pfn * page_size) + (virt_addr % page_size); + + Ok(phys_addr) + } } diff --git a/src/takeoff/src/main.rs b/src/takeoff/src/main.rs index d4f7ba3..45096d8 100644 --- a/src/takeoff/src/main.rs +++ b/src/takeoff/src/main.rs @@ -17,7 +17,7 @@ use nix::{ }; use oci_config::{EnvVar, OciConfig}; use serial::SerialWriter; -use takeoff_proto::proto::{LogsTelemetryConfig, TakeoffInitArgs}; +use takeoff_proto::proto::LogsTelemetryConfig; use tokio::{ fs, @@ -45,10 +45,13 @@ async fn takeoff() -> Result<()> { let guest_manager = Arc::new(GuestManager::new().expect("create guest manager")); + let Ok(args) = guest_manager.read_takeoff_args() else { + bail!("failed to read takeoff init args"); + }; + let cmdline = fs::read_to_string("/proc/cmdline") .await .expect("read cmdline"); - let args = TakeoffInitArgs::try_parse_from_kernel_cmdline(&cmdline)?; configure_dns(&cmdline).await?;