diff --git a/fuzz/fuzz_targets/http_api.rs b/fuzz/fuzz_targets/http_api.rs index 24e7c0fe34..be1c9f17e8 100644 --- a/fuzz/fuzz_targets/http_api.rs +++ b/fuzz/fuzz_targets/http_api.rs @@ -166,12 +166,16 @@ impl RequestHandler for StubApiRequestHandler { mode: ConsoleOutputMode::Null, iommu: false, socket: None, + main_fd: None, + sub_fd: None, }, console: ConsoleConfig { file: None, mode: ConsoleOutputMode::Tty, iommu: false, socket: None, + main_fd: None, + sub_fd: None, }, #[cfg(target_arch = "x86_64")] debug_console: DebugConsoleConfig::default(), diff --git a/src/main.rs b/src/main.rs index 5ec09c940b..6b3fd098a7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -913,12 +913,16 @@ mod unit_tests { mode: ConsoleOutputMode::Null, iommu: false, socket: None, + main_fd: None, + sub_fd: None, }, console: ConsoleConfig { file: None, mode: ConsoleOutputMode::Tty, iommu: false, socket: None, + main_fd: None, + sub_fd: None, }, #[cfg(target_arch = "x86_64")] debug_console: DebugConsoleConfig::default(), diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 4efc055b5a..26e7d42766 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -1693,6 +1693,8 @@ impl ConsoleConfig { mode, iommu, socket, + main_fd: None, + sub_fd: None, }) } } @@ -1746,7 +1748,13 @@ impl DebugConsoleConfig { } } - Ok(Self { file, mode, iobase }) + Ok(Self { + file, + mode, + iobase, + main_fd: None, + sub_fd: None, + }) } } @@ -3268,6 +3276,8 @@ mod tests { iommu: false, file: None, socket: None, + main_fd: None, + sub_fd: None, } ); assert_eq!( @@ -3277,6 +3287,8 @@ mod tests { iommu: false, file: None, socket: None, + main_fd: None, + sub_fd: None, } ); assert_eq!( @@ -3286,6 +3298,8 @@ mod tests { iommu: false, file: None, socket: None, + main_fd: None, + sub_fd: None, } ); assert_eq!( @@ -3295,6 +3309,8 @@ mod tests { iommu: false, file: None, socket: None, + main_fd: None, + sub_fd: None, } ); assert_eq!( @@ -3304,6 +3320,8 @@ mod tests { iommu: false, file: Some(PathBuf::from("/tmp/console")), socket: None, + main_fd: None, + sub_fd: None, } ); assert_eq!( @@ -3313,6 +3331,8 @@ mod tests { iommu: true, file: None, socket: None, + main_fd: None, + sub_fd: None, } ); assert_eq!( @@ -3322,6 +3342,8 @@ mod tests { iommu: true, file: Some(PathBuf::from("/tmp/console")), socket: None, + main_fd: None, + sub_fd: None, } ); assert_eq!( @@ -3331,6 +3353,8 @@ mod tests { iommu: true, file: None, socket: Some(PathBuf::from("/tmp/serial.sock")), + main_fd: None, + sub_fd: None, } ); Ok(()) @@ -3515,12 +3539,16 @@ mod tests { mode: ConsoleOutputMode::Null, iommu: false, socket: None, + main_fd: None, + sub_fd: None, }, console: ConsoleConfig { file: None, mode: ConsoleOutputMode::Tty, iommu: false, socket: None, + main_fd: None, + sub_fd: None, }, #[cfg(target_arch = "x86_64")] debug_console: DebugConsoleConfig::default(), diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 61ff76115d..1ecee8ce0a 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -69,8 +69,10 @@ use std::fs::{read_link, File, OpenOptions}; use std::io::{self, stdout, Seek, SeekFrom}; use std::mem::zeroed; use std::num::Wrapping; +use std::os::fd::IntoRawFd; use std::os::unix::fs::OpenOptionsExt; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; +use std::os::unix::net::UnixListener; use std::path::PathBuf; use std::result; use std::sync::{Arc, Mutex}; @@ -257,16 +259,19 @@ pub enum DeviceManagerError { DebugconOutputFileOpen(io::Error), /// Error creating console output file - ConsoleOutputFileOpen(io::Error), + ConsoleOutputFileCreate(io::Error), /// Error creating serial pty - SerialPtyOpen(io::Error), + SerialPtyCreate(io::Error), /// Error creating console pty - ConsolePtyOpen(io::Error), + ConsolePtyCreate(io::Error), /// Error creating console pty - DebugconPtyOpen(io::Error), + DebugconPtyCreate(io::Error), + + /// Cannot bind to Unix socket + BindUnixSocket(io::Error), /// Error setting pty raw mode SetPtyRaw(vmm_sys_util::errno::Error), @@ -493,6 +498,9 @@ pub enum DeviceManagerError { /// Cannot create a RateLimiterGroup RateLimiterGroupCreate(rate_limiter::group::Error), + + // Missing console device info + MissingConsoleDeviceInfo, } pub type DeviceManagerResult = result::Result; @@ -548,6 +556,76 @@ pub fn create_pty() -> io::Result<(File, File, PathBuf)> { Ok((main, unsafe { File::from_raw_fd(sub_fd) }, path)) } +// Create console devices (debug-console, serial-console, virtio-console) devices +// and save related information in VMconfig during vm_create. Doing this in +// vm_create will allow landlock to add relevant rules for all the console device +// files before starting the guest. +pub fn pre_create_console_devices(vm_config: Arc>) -> DeviceManagerResult<()> { + let mut vmconfig = vm_config.lock().unwrap(); + + match vmconfig.console.mode { + ConsoleOutputMode::File => { + let file = File::create(vmconfig.console.file.as_ref().unwrap()) + .map_err(DeviceManagerError::ConsoleOutputFileCreate)?; + vmconfig.console.main_fd = Some(file.into_raw_fd()); + } + ConsoleOutputMode::Pty => { + let (main_fd, sub_fd, path) = + create_pty().map_err(DeviceManagerError::ConsolePtyCreate)?; + vmconfig.console.main_fd = Some(main_fd.into_raw_fd()); + vmconfig.console.sub_fd = Some(sub_fd.into_raw_fd()); + vmconfig.console.file = Some(path); + } + ConsoleOutputMode::Null + | ConsoleOutputMode::Socket + | ConsoleOutputMode::Tty + | ConsoleOutputMode::Off => {} + } + + match vmconfig.serial.mode { + ConsoleOutputMode::File => { + let file = File::create(vmconfig.serial.file.as_ref().unwrap()) + .map_err(DeviceManagerError::SerialOutputFileOpen)?; + vmconfig.serial.main_fd = Some(file.into_raw_fd()); + } + ConsoleOutputMode::Pty => { + let (main_fd, sub_fd, path) = + create_pty().map_err(DeviceManagerError::SerialPtyCreate)?; + vmconfig.console.main_fd = Some(main_fd.into_raw_fd()); + vmconfig.console.sub_fd = Some(sub_fd.into_raw_fd()); + vmconfig.console.file = Some(path); + } + ConsoleOutputMode::Socket => { + let listener = UnixListener::bind(vmconfig.serial.socket.as_ref().unwrap()) + .map_err(DeviceManagerError::BindUnixSocket)?; + vmconfig.serial.main_fd = Some(listener.into_raw_fd()); + } + ConsoleOutputMode::Null | ConsoleOutputMode::Tty | ConsoleOutputMode::Off => {} + } + + #[cfg(target_arch = "x86_64")] + match vmconfig.debug_console.mode { + ConsoleOutputMode::File => { + let file = File::create(vmconfig.debug_console.file.as_ref().unwrap()) + .map_err(DeviceManagerError::DebugconOutputFileOpen)?; + vmconfig.debug_console.main_fd = Some(file.into_raw_fd()); + } + ConsoleOutputMode::Pty => { + let (main_fd, sub_fd, path) = + create_pty().map_err(DeviceManagerError::DebugconPtyCreate)?; + vmconfig.debug_console.main_fd = Some(main_fd.into_raw_fd()); + vmconfig.debug_console.sub_fd = Some(sub_fd.into_raw_fd()); + vmconfig.debug_console.file = Some(path); + } + ConsoleOutputMode::Null + | ConsoleOutputMode::Socket + | ConsoleOutputMode::Tty + | ConsoleOutputMode::Off => {} + } + + Ok(()) +} + #[derive(Default)] pub struct Console { console_resizer: Option>, @@ -2047,28 +2125,43 @@ impl DeviceManager { let console_config = self.config.lock().unwrap().console.clone(); let endpoint = match console_config.mode { ConsoleOutputMode::File => { - let file = File::create(console_config.file.as_ref().unwrap()) - .map_err(DeviceManagerError::ConsoleOutputFileOpen)?; - Endpoint::File(file) + if console_config.main_fd.is_none() { + return Err(DeviceManagerError::MissingConsoleDeviceInfo); + } + // SAFETY: console_config.main_fd is valid. If not, pre_create_console_devices + // would have errored before reaching here + Endpoint::File(unsafe { File::from_raw_fd(console_config.main_fd.unwrap()) }) } ConsoleOutputMode::Pty => { if let Some(pty) = console_pty { - self.config.lock().unwrap().console.file = Some(pty.path.clone()); let file = pty.main.try_clone().unwrap(); self.console_pty = Some(Arc::new(Mutex::new(pty))); self.console_resize_pipe = resize_pipe.map(Arc::new); Endpoint::PtyPair(file.try_clone().unwrap(), file) } else { - let (main, sub, path) = - create_pty().map_err(DeviceManagerError::ConsolePtyOpen)?; - self.set_raw_mode(&sub) - .map_err(DeviceManagerError::SetPtyRaw)?; - self.config.lock().unwrap().console.file = Some(path.clone()); - let file = main.try_clone().unwrap(); - assert!(resize_pipe.is_none()); - self.listen_for_sigwinch_on_tty(sub).unwrap(); - self.console_pty = Some(Arc::new(Mutex::new(PtyPair { main, path }))); - Endpoint::PtyPair(file.try_clone().unwrap(), file) + // In non-reboot cases pty info should be configured in pre_create_console_devices + if console_config.main_fd.is_none() + || console_config.sub_fd.is_none() + || console_config.file.is_none() + { + return Err(DeviceManagerError::MissingConsoleDeviceInfo); + } else { + // SAFETY: console_config.main_fd is valid. If not, pre_create_console_devices + // would have errored before reaching here + let main = unsafe { File::from_raw_fd(console_config.main_fd.unwrap()) }; + // SAFETY: console_config.sub_fd is valid. If not, pre_create_console_devices + // would have errored before reaching here + let sub = unsafe { File::from_raw_fd(console_config.sub_fd.unwrap()) }; + + let path = console_config.file.unwrap().clone(); + self.set_raw_mode(&sub) + .map_err(DeviceManagerError::SetPtyRaw)?; + let file = main.try_clone().unwrap(); + assert!(resize_pipe.is_none()); + self.listen_for_sigwinch_on_tty(sub).unwrap(); + self.console_pty = Some(Arc::new(Mutex::new(PtyPair { main, path }))); + Endpoint::PtyPair(file.try_clone().unwrap(), file) + } } } ConsoleOutputMode::Tty => { @@ -2175,21 +2268,38 @@ impl DeviceManager { ) -> DeviceManagerResult> { let serial_config = self.config.lock().unwrap().serial.clone(); let serial_writer: Option> = match serial_config.mode { - ConsoleOutputMode::File => Some(Box::new( - File::create(serial_config.file.as_ref().unwrap()) - .map_err(DeviceManagerError::SerialOutputFileOpen)?, - )), + ConsoleOutputMode::File => { + if serial_config.main_fd.is_none() { + return Err(DeviceManagerError::MissingConsoleDeviceInfo); + } + // SAFETY: serial_config.main_fd is valid. If not, pre_create_console_devices + // would have errored before reaching here + Some(Box::new(unsafe { + File::from_raw_fd(serial_config.main_fd.unwrap()) + })) + } ConsoleOutputMode::Pty => { if let Some(pty) = serial_pty.clone() { - self.config.lock().unwrap().serial.file = Some(pty.path.clone()); self.serial_pty = Some(Arc::new(Mutex::new(pty))); } else { - let (main, sub, path) = - create_pty().map_err(DeviceManagerError::SerialPtyOpen)?; - self.set_raw_mode(&sub) - .map_err(DeviceManagerError::SetPtyRaw)?; - self.config.lock().unwrap().serial.file = Some(path.clone()); - self.serial_pty = Some(Arc::new(Mutex::new(PtyPair { main, path }))); + // In non-reboot cases these fds should be configure in pre_create_console_devices + if serial_config.main_fd.is_none() + || serial_config.sub_fd.is_none() + || serial_config.file.is_none() + { + return Err(DeviceManagerError::MissingConsoleDeviceInfo); + } else { + // SAFETY: serial_config.main_fd is valid. If not, pre_create_console_devices + // would have errored before reaching here + let main = unsafe { File::from_raw_fd(serial_config.main_fd.unwrap()) }; + // SAFETY: serial_config.sub_fd is valid. If not, pre_create_console_devices + // would have errored before reaching here + let sub = unsafe { File::from_raw_fd(serial_config.sub_fd.unwrap()) }; + let path = serial_config.file.unwrap().clone(); + self.set_raw_mode(&sub) + .map_err(DeviceManagerError::SetPtyRaw)?; + self.serial_pty = Some(Arc::new(Mutex::new(PtyPair { main, path }))); + } } None } @@ -2209,6 +2319,7 @@ impl DeviceManager { self.serial_pty.clone(), serial_config.mode, serial_config.socket, + serial_config.main_fd, ) .map_err(DeviceManagerError::CreateSerialManager)?; if let Some(mut serial_manager) = serial_manager { @@ -2234,21 +2345,41 @@ impl DeviceManager { let debug_console_writer: Option> = match debug_console_config .mode { - ConsoleOutputMode::File => Some(Box::new( - File::create(debug_console_config.file.as_ref().unwrap()) - .map_err(DeviceManagerError::DebugconOutputFileOpen)?, - )), + ConsoleOutputMode::File => { + if debug_console_config.main_fd.is_none() { + return Err(DeviceManagerError::MissingConsoleDeviceInfo); + } + // SAFETY: debug_console_config.main_fd is valid. If not, pre_create_console_devices + // would have errored before reaching here + Some(Box::new(unsafe { + File::from_raw_fd(debug_console_config.main_fd.unwrap()) + })) + } ConsoleOutputMode::Pty => { if let Some(pty) = debug_console_pty { - self.config.lock().unwrap().debug_console.file = Some(pty.path.clone()); self.debug_console_pty = Some(Arc::new(Mutex::new(pty))); } else { - let (main, sub, path) = - create_pty().map_err(DeviceManagerError::DebugconPtyOpen)?; - self.set_raw_mode(&sub) - .map_err(DeviceManagerError::SetPtyRaw)?; - self.config.lock().unwrap().debug_console.file = Some(path.clone()); - self.debug_console_pty = Some(Arc::new(Mutex::new(PtyPair { main, path }))); + // In non-reboot cases pty should be configured in pre_create_console_devices + if debug_console_config.main_fd.is_none() + || debug_console_config.sub_fd.is_none() + || debug_console_config.file.is_none() + { + return Err(DeviceManagerError::MissingConsoleDeviceInfo); + } else { + // SAFETY: debug_console_config.main_fd is valid. If not, pre_create_console_devices + // would have errored before reaching here + let main = + unsafe { File::from_raw_fd(debug_console_config.main_fd.unwrap()) }; + // SAFETY: debug_console_config.sub_fd is valid. If not, pre_create_console_devices + // would have errored before reaching here + let sub = + unsafe { File::from_raw_fd(debug_console_config.sub_fd.unwrap()) }; + let path = debug_console_config.file.unwrap().clone(); + self.set_raw_mode(&sub) + .map_err(DeviceManagerError::SetPtyRaw)?; + self.debug_console_pty = + Some(Arc::new(Mutex::new(PtyPair { main, path }))); + } } None } diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 60b4feeda9..43a465cb95 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -28,6 +28,7 @@ use anyhow::anyhow; #[cfg(feature = "dbus_api")] use api::dbus::{DBusApiOptions, DBusApiShutdownChannels}; use api::http::HttpApiHandle; +use device_manager::pre_create_console_devices; use libc::{tcsetattr, termios, EFD_NONBLOCK, SIGINT, SIGTERM, TCSANOW}; use memory_manager::MemoryManagerSnapshotData; use pci::PciBdf; @@ -1207,6 +1208,7 @@ impl RequestHandler for Vmm { // We only store the passed VM config. // The VM will be created when being asked to boot it. if self.vm_config.is_none() { + pre_create_console_devices(config.clone()).map_err(VmError::DeviceManager)?; self.vm_config = Some(config); Ok(()) } else { @@ -2130,12 +2132,16 @@ mod unit_tests { mode: ConsoleOutputMode::Null, iommu: false, socket: None, + main_fd: None, + sub_fd: None, }, console: ConsoleConfig { file: None, mode: ConsoleOutputMode::Tty, iommu: false, socket: None, + main_fd: None, + sub_fd: None, }, #[cfg(target_arch = "x86_64")] debug_console: DebugConsoleConfig::default(), diff --git a/vmm/src/serial_manager.rs b/vmm/src/serial_manager.rs index 426a983f0e..a2c5ef2e43 100644 --- a/vmm/src/serial_manager.rs +++ b/vmm/src/serial_manager.rs @@ -14,6 +14,7 @@ use serial_buffer::SerialBuffer; use std::fs::File; use std::io::Read; use std::net::Shutdown; +use std::os::fd::RawFd; use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd}; use std::os::unix::net::{UnixListener, UnixStream}; use std::panic::AssertUnwindSafe; @@ -58,10 +59,6 @@ pub enum Error { #[error("Error spawning SerialManager thread: {0}")] SpawnSerialManager(#[source] io::Error), - /// Cannot bind to Unix socket - #[error("Error binding to socket: {0}")] - BindUnixSocket(#[source] io::Error), - /// Cannot accept connection from Unix socket #[error("Error accepting connection: {0}")] AcceptConnection(#[source] io::Error), @@ -77,6 +74,10 @@ pub enum Error { /// Cannot remove the serial socket #[error("Error removing serial socket: {0}")] RemoveUnixSocket(#[source] io::Error), + + /// Missing socket information + #[error("Missing socekt information")] + MissingSocketInfo, } pub type Result = result::Result; @@ -121,10 +122,9 @@ impl SerialManager { #[cfg(target_arch = "aarch64")] serial: Arc>, pty_pair: Option>>, mode: ConsoleOutputMode, - socket: Option, + socket_path: Option, + socket_fd: Option, ) -> Result> { - let mut socket_path: Option = None; - let in_file = match mode { ConsoleOutputMode::Pty => { if let Some(pty_pair) = pty_pair { @@ -161,14 +161,12 @@ impl SerialManager { } } ConsoleOutputMode::Socket => { - if let Some(path_in_socket) = socket { - socket_path = Some(path_in_socket.clone()); - let listener = UnixListener::bind(path_in_socket.as_path()) - .map_err(Error::BindUnixSocket)?; - // SAFETY: listener is valid and will return valid fd - unsafe { File::from_raw_fd(listener.into_raw_fd()) } + if let Some(socket_fd) = socket_fd { + // SAFETY: socket_fd is valid. If not, pre_create_console_devices + // would have errored before reaching here + unsafe { File::from_raw_fd(socket_fd) } } else { - return Ok(None); + return Err(Error::MissingSocketInfo); } } _ => return Ok(None), diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 826502ddfb..c162030412 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -4,7 +4,7 @@ // use net_util::MacAddr; use serde::{Deserialize, Serialize}; -use std::{net::Ipv4Addr, path::PathBuf}; +use std::{net::Ipv4Addr, os::fd::RawFd, path::PathBuf}; use virtio_devices::RateLimiterConfig; #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] @@ -428,6 +428,11 @@ pub struct ConsoleConfig { #[serde(default)] pub iommu: bool, pub socket: Option, + // main_fd is the fd of the pre-created console device + #[serde(skip)] + pub main_fd: Option, + // sub_fd is used only when ConsoleOutputMode is pty + pub sub_fd: Option, } pub fn default_consoleconfig_file() -> Option { @@ -442,6 +447,9 @@ pub struct DebugConsoleConfig { pub mode: ConsoleOutputMode, /// Optionally dedicated I/O-port, if the default port should not be used. pub iobase: Option, + #[serde(skip)] + pub main_fd: Option, + pub sub_fd: Option, } #[cfg(target_arch = "x86_64")] @@ -451,6 +459,8 @@ impl Default for DebugConsoleConfig { file: None, mode: ConsoleOutputMode::Off, iobase: Some(devices::debug_console::DEFAULT_PORT as u16), + main_fd: None, + sub_fd: None, } } } @@ -565,6 +575,8 @@ pub fn default_serial() -> ConsoleConfig { mode: ConsoleOutputMode::Null, iommu: false, socket: None, + main_fd: None, + sub_fd: None, } } @@ -574,6 +586,8 @@ pub fn default_console() -> ConsoleConfig { mode: ConsoleOutputMode::Tty, iommu: false, socket: None, + main_fd: None, + sub_fd: None, } }