Mercurial > crates > systemd-socket
comparison src/lib.rs @ 24:1941e9d9819c
Fix unsound manipulation of env vars
Modifying env vars in multi-threaded process is unsound but this crate
was neither checking the number of threads nor mark its functions as
`unsafe`. This change fixes it by both adding a check and adding an
`unsafe` function that can bypass that check if needed.
| author | Martin Habovstiak <martin.habovstiak@gmail.com> |
|---|---|
| date | Fri, 28 Feb 2025 13:52:31 +0100 |
| parents | f33e2c048104 |
| children | 8e20daee41ed |
comparison
equal
deleted
inserted
replaced
| 23:729392c49b46 | 24:1941e9d9819c |
|---|---|
| 1 //! A convenience crate for optionally supporting systemd socket activation. | 1 //! A convenience crate for optionally supporting systemd socket activation. |
| 2 //! | 2 //! |
| 3 //! ## About | 3 //! ## About |
| 4 //! | |
| 5 //! **Important:** because of various reasons it is recommended to call the [`init`] function at | |
| 6 //! the start of your program! | |
| 4 //! | 7 //! |
| 5 //! The goal of this crate is to make socket activation with systemd in your project trivial. | 8 //! The goal of this crate is to make socket activation with systemd in your project trivial. |
| 6 //! It provides a replacement for `std::net::SocketAddr` that allows parsing the bind address from string just like the one from `std` | 9 //! It provides a replacement for `std::net::SocketAddr` that allows parsing the bind address from string just like the one from `std` |
| 7 //! but on top of that also allows `systemd://socket_name` format that tells it to use systemd activation with given socket name. | 10 //! but on top of that also allows `systemd://socket_name` format that tells it to use systemd activation with given socket name. |
| 8 //! Then it provides a method to bind the address which will return the socket from systemd if available. | 11 //! Then it provides a method to bind the address which will return the socket from systemd if available. |
| 9 //! | 12 //! |
| 10 //! The provided type supports conversions from various types of strings and also `serde` and `parse_arg` via feature flag. | 13 //! The provided type supports conversions from various types of strings and also `serde` and `parse_arg` via feature flag. |
| 11 //! Thanks to this the change to your code should be minimal - parsing will continue to work, it'll just allow a new format. | 14 //! Thanks to this the change to your code should be minimal - parsing will continue to work, it'll just allow a new format. |
| 12 //! You only need to change the code to use `SocketAddr::bind()` instead of `TcpListener::bind()` for binding. | 15 //! You only need to change the code to use `SocketAddr::bind()` instead of `TcpListener::bind()` for binding. |
| 13 //! | 16 //! |
| 14 //! You also don't need to worry about conditional compilation to ensure OS compatibility. | 17 //! You also don't need to worry about conditional compilation to ensure OS compatibility. |
| 22 //! ```no_run | 25 //! ```no_run |
| 23 //! use systemd_socket::SocketAddr; | 26 //! use systemd_socket::SocketAddr; |
| 24 //! use std::convert::TryFrom; | 27 //! use std::convert::TryFrom; |
| 25 //! use std::io::Write; | 28 //! use std::io::Write; |
| 26 //! | 29 //! |
| 30 //! systemd_socket::init().expect("Failed to initialize systemd sockets"); | |
| 27 //! let mut args = std::env::args_os(); | 31 //! let mut args = std::env::args_os(); |
| 28 //! let program_name = args.next().expect("unknown program name"); | 32 //! let program_name = args.next().expect("unknown program name"); |
| 29 //! let socket_addr = args.next().expect("missing socket address"); | 33 //! let socket_addr = args.next().expect("missing socket address"); |
| 30 //! let socket_addr = SocketAddr::try_from(socket_addr).expect("failed to parse socket address"); | 34 //! let socket_addr = SocketAddr::try_from(socket_addr).expect("failed to parse socket address"); |
| 31 //! let socket = socket_addr.bind().expect("failed to bind socket"); | 35 //! let socket = socket_addr.bind().expect("failed to bind socket"); |
| 50 //! * `tokio` - adds `bind_tokio` method to `SocketAddr` (tokio 1.0) | 54 //! * `tokio` - adds `bind_tokio` method to `SocketAddr` (tokio 1.0) |
| 51 //! * `tokio_0_2` - adds `bind_tokio_0_2` method to `SocketAddr` | 55 //! * `tokio_0_2` - adds `bind_tokio_0_2` method to `SocketAddr` |
| 52 //! * `tokio_0_3` - adds `bind_tokio_0_3` method to `SocketAddr` | 56 //! * `tokio_0_3` - adds `bind_tokio_0_3` method to `SocketAddr` |
| 53 //! * `async_std` - adds `bind_async_std` method to `SocketAddr` | 57 //! * `async_std` - adds `bind_async_std` method to `SocketAddr` |
| 54 //! | 58 //! |
| 59 //! ## Soundness | |
| 60 //! | |
| 61 //! The systemd file descriptors are transferred using environment variables and since they are | |
| 62 //! file descriptors, they should have move semantics. However environment variables in Rust do not | |
| 63 //! have move semantics and even modifying them is very dangerous. | |
| 64 //! | |
| 65 //! Because of this, the crate only allows initialization when there's only one thread running. | |
| 66 //! However that still doesn't prevent all possible problems: if some other code closes file | |
| 67 //! descriptors stored in those environment variables you can get an invalid socket. | |
| 68 //! | |
| 69 //! This situation is obviously ridiculous because there shouldn't be a reason to use another | |
| 70 //! library to do the same thing. It could also be argued that whichever code doesn't clear the | |
| 71 //! evironment variable is broken (even though understandably) and it's not a fault of this library. | |
| 72 //! | |
| 55 //! ## MSRV | 73 //! ## MSRV |
| 56 //! | 74 //! |
| 57 //! This crate must always compile with the latest Rust available in the latest Debian stable. | 75 //! This crate must always compile with the latest Rust available in the latest Debian stable. |
| 58 //! That is currently Rust 1.41.1. (Debian 10 - Buster) | 76 //! That is currently Rust 1.41.1. (Debian 10 - Buster) |
| 59 | 77 |
| 77 pub(crate) mod systemd_sockets { | 95 pub(crate) mod systemd_sockets { |
| 78 use std::fmt; | 96 use std::fmt; |
| 79 use std::sync::Mutex; | 97 use std::sync::Mutex; |
| 80 use libsystemd::activation::FileDescriptor; | 98 use libsystemd::activation::FileDescriptor; |
| 81 use libsystemd::errors::SdError as LibSystemdError; | 99 use libsystemd::errors::SdError as LibSystemdError; |
| 82 type LibSystemdResult<T> = Result<T, LibSystemdError>; | |
| 83 | 100 |
| 84 #[derive(Debug)] | 101 #[derive(Debug)] |
| 85 pub(crate) struct Error(&'static Mutex<LibSystemdError>); | 102 pub(crate) struct Error(&'static Mutex<InitError>); |
| 86 | 103 |
| 87 impl fmt::Display for Error { | 104 impl fmt::Display for Error { |
| 88 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | 105 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 89 fmt::Display::fmt(&*self.0.lock().expect("mutex poisoned"), f) | 106 use std::error::Error as _; |
| 107 | |
| 108 let guard = self.0.lock().expect("mutex poisoned"); | |
| 109 fmt::Display::fmt(&*guard, f)?; | |
| 110 let mut source_opt = guard.source(); | |
| 111 while let Some(source) = source_opt { | |
| 112 write!(f, ": {}", source)?; | |
| 113 source_opt = source.source(); | |
| 114 } | |
| 115 Ok(()) | |
| 90 } | 116 } |
| 91 } | 117 } |
| 92 | 118 |
| 93 // No source we can't keep the mutex locked | 119 // No source we can't keep the mutex locked |
| 94 impl std::error::Error for Error {} | 120 impl std::error::Error for Error {} |
| 95 | 121 |
| 122 pub(crate) unsafe fn init(protected: bool) -> Result<(), InitError> { | |
| 123 SYSTEMD_SOCKETS.get_or_try_init(|| SystemdSockets::new(protected, true).map(Ok)).map(drop) | |
| 124 } | |
| 125 | |
| 96 pub(crate) fn take(name: &str) -> Result<Option<FileDescriptor>, Error> { | 126 pub(crate) fn take(name: &str) -> Result<Option<FileDescriptor>, Error> { |
| 97 match &*SYSTEMD_SOCKETS { | 127 let sockets = SYSTEMD_SOCKETS.get_or_init(|| SystemdSockets::new_protected(false).map_err(Mutex::new)); |
| 128 match sockets { | |
| 98 Ok(sockets) => Ok(sockets.take(name)), | 129 Ok(sockets) => Ok(sockets.take(name)), |
| 99 Err(error) => Err(Error(error)) | 130 Err(error) => Err(Error(error)) |
| 100 } | 131 } |
| 101 } | 132 } |
| 102 | 133 |
| 134 #[derive(Debug)] | |
| 135 pub(crate) enum InitError { | |
| 136 OpenStatus(std::io::Error), | |
| 137 ReadStatus(std::io::Error), | |
| 138 ThreadCountNotFound, | |
| 139 MultipleThreads, | |
| 140 LibSystemd(LibSystemdError), | |
| 141 } | |
| 142 | |
| 143 impl From<LibSystemdError> for InitError { | |
| 144 fn from(value: LibSystemdError) -> Self { | |
| 145 Self::LibSystemd(value) | |
| 146 } | |
| 147 } | |
| 148 | |
| 149 impl fmt::Display for InitError { | |
| 150 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
| 151 match self { | |
| 152 Self::OpenStatus(_) => write!(f, "failed to open /proc/self/status"), | |
| 153 Self::ReadStatus(_) => write!(f, "failed to read /proc/self/status"), | |
| 154 Self::ThreadCountNotFound => write!(f, "/proc/self/status doesn't contain Threads entry"), | |
| 155 Self::MultipleThreads => write!(f, "there is more than one thread running"), | |
| 156 // We have nothing to say about the error, let's flatten it | |
| 157 Self::LibSystemd(error) => fmt::Display::fmt(error, f), | |
| 158 } | |
| 159 } | |
| 160 } | |
| 161 | |
| 162 impl std::error::Error for InitError { | |
| 163 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { | |
| 164 match self { | |
| 165 Self::OpenStatus(error) => Some(error), | |
| 166 Self::ReadStatus(error) => Some(error), | |
| 167 Self::ThreadCountNotFound => None, | |
| 168 Self::MultipleThreads => None, | |
| 169 // We have nothing to say about the error, let's flatten it | |
| 170 Self::LibSystemd(error) => error.source(), | |
| 171 } | |
| 172 } | |
| 173 } | |
| 174 | |
| 103 struct SystemdSockets(std::sync::Mutex<std::collections::HashMap<String, FileDescriptor>>); | 175 struct SystemdSockets(std::sync::Mutex<std::collections::HashMap<String, FileDescriptor>>); |
| 104 | 176 |
| 105 impl SystemdSockets { | 177 impl SystemdSockets { |
| 106 fn new() -> LibSystemdResult<Self> { | 178 fn new_protected(explicit: bool) -> Result<Self, InitError> { |
| 179 unsafe { Self::new(true, explicit) } | |
| 180 } | |
| 181 | |
| 182 unsafe fn new(protected: bool, explicit: bool) -> Result<Self, InitError> { | |
| 183 if explicit { | |
| 184 if std::env::var_os("LISTEN_PID").is_none() && std::env::var_os("LISTEN_FDS").is_none() && std::env::var_os("LISTEN_FDNAMES").is_none() { | |
| 185 // Systemd is not used - make the map empty | |
| 186 return Ok(SystemdSockets(Mutex::new(Default::default()))); | |
| 187 } | |
| 188 } | |
| 189 | |
| 190 if protected { Self::check_single_thread()? } | |
| 107 // MUST BE true FOR SAFETY!!! | 191 // MUST BE true FOR SAFETY!!! |
| 108 let map = libsystemd::activation::receive_descriptors_with_names(/*unset env = */ true)?.into_iter().map(|(fd, name)| (name, fd)).collect(); | 192 let map = libsystemd::activation::receive_descriptors_with_names(/*unset env = */ protected)?.into_iter().map(|(fd, name)| (name, fd)).collect(); |
| 109 Ok(SystemdSockets(Mutex::new(map))) | 193 Ok(SystemdSockets(Mutex::new(map))) |
| 194 } | |
| 195 | |
| 196 fn check_single_thread() -> Result<(), InitError> { | |
| 197 use std::io::BufRead; | |
| 198 | |
| 199 let status = std::fs::File::open("/proc/self/status").map_err(InitError::OpenStatus)?; | |
| 200 let mut status = std::io::BufReader::new(status); | |
| 201 let mut line = String::new(); | |
| 202 loop { | |
| 203 if status.read_line(&mut line).map_err(InitError::ReadStatus)? == 0 { | |
| 204 return Err(InitError::ThreadCountNotFound); | |
| 205 } | |
| 206 if let Some(threads) = line.strip_prefix("Threads:") { | |
| 207 if threads.trim() == "1" { | |
| 208 break; | |
| 209 } else { | |
| 210 return Err(InitError::MultipleThreads); | |
| 211 } | |
| 212 } | |
| 213 line.clear(); | |
| 214 } | |
| 215 Ok(()) | |
| 110 } | 216 } |
| 111 | 217 |
| 112 fn take(&self, name: &str) -> Option<FileDescriptor> { | 218 fn take(&self, name: &str) -> Option<FileDescriptor> { |
| 113 // MUST remove THE SOCKET FOR SAFETY!!! | 219 // MUST remove THE SOCKET FOR SAFETY!!! |
| 114 self.0.lock().expect("poisoned mutex").remove(name) | 220 self.0.lock().expect("poisoned mutex").remove(name) |
| 115 } | 221 } |
| 116 } | 222 } |
| 117 | 223 |
| 118 lazy_static::lazy_static! { | 224 static SYSTEMD_SOCKETS: once_cell::sync::OnceCell<Result<SystemdSockets, Mutex<InitError>>> = once_cell::sync::OnceCell::new(); |
| 119 // We don't panic in order to let the application handle the error later | |
| 120 static ref SYSTEMD_SOCKETS: Result<SystemdSockets, Mutex<LibSystemdError>> = SystemdSockets::new().map_err(Mutex::new); | |
| 121 } | |
| 122 } | 225 } |
| 123 | 226 |
| 124 /// Socket address that can be an ordinary address or a systemd socket | 227 /// Socket address that can be an ordinary address or a systemd socket |
| 125 /// | 228 /// |
| 126 /// This is the core type of this crate that abstracts possible addresses. | 229 /// This is the core type of this crate that abstracts possible addresses. |
| 337 // This approach makes the rest of the code much simpler as it doesn't require sprinkling it | 440 // This approach makes the rest of the code much simpler as it doesn't require sprinkling it |
| 338 // with #[cfg(all(target_os = "linux", feature = "enable_systemd"))] yet still statically guarantees it won't execute. | 441 // with #[cfg(all(target_os = "linux", feature = "enable_systemd"))] yet still statically guarantees it won't execute. |
| 339 #[cfg(not(all(target_os = "linux", feature = "enable_systemd")))] | 442 #[cfg(not(all(target_os = "linux", feature = "enable_systemd")))] |
| 340 fn get_systemd(socket_name: Never, _prefixed: bool) -> Result<(std::net::TcpListener, SocketAddrInner), BindError> { | 443 fn get_systemd(socket_name: Never, _prefixed: bool) -> Result<(std::net::TcpListener, SocketAddrInner), BindError> { |
| 341 match socket_name {} | 444 match socket_name {} |
| 445 } | |
| 446 } | |
| 447 | |
| 448 /// Initializes the library while there's only a single thread. | |
| 449 /// | |
| 450 /// Unfortunately, this library has to be initialized and, for soundness, this initialization must | |
| 451 /// happen when no other threads are running. This is attempted automatically when trying to bind a | |
| 452 /// systemd socket but at that time there may be other threads running and error reporting also | |
| 453 /// faces some restrictions. This function provides better control over the initialization point | |
| 454 /// and returns a more idiomatic error type. | |
| 455 /// | |
| 456 /// You should generally call this at around the top of `main`, where no threads were created yet. | |
| 457 #[inline] | |
| 458 pub fn init() -> Result<(), error::InitError> { | |
| 459 #[cfg(all(target_os = "linux", feature = "enable_systemd"))] | |
| 460 { | |
| 461 // Calling with true is always sound | |
| 462 unsafe { systemd_sockets::init(true) }.map_err(error::InitError) | |
| 463 } | |
| 464 #[cfg(not(all(target_os = "linux", feature = "enable_systemd")))] | |
| 465 { | |
| 466 Ok(()) | |
| 467 } | |
| 468 } | |
| 469 | |
| 470 /// Initializes the library without protection against double close. | |
| 471 /// | |
| 472 /// Unfortunately, this library has to be initialized and, because double closing file descriptors | |
| 473 /// is unsound, the library has some protections against double close. However these protections | |
| 474 /// come with the limitation that the library must be initailized with a single thread. | |
| 475 /// | |
| 476 /// If for any reason you're unable to call `init` in a single thread at around the top of `main` | |
| 477 /// (and this should be almost never) you may call this method if you've ensured that no other part | |
| 478 /// of your codebase is operating on systemd-provided file descriptors stored in the environment | |
| 479 /// variables. | |
| 480 pub unsafe fn init_unprotected() -> Result<(), error::InitError> { | |
| 481 #[cfg(all(target_os = "linux", feature = "enable_systemd"))] | |
| 482 { | |
| 483 systemd_sockets::init(false).map_err(error::InitError) | |
| 484 } | |
| 485 #[cfg(not(all(target_os = "linux", feature = "enable_systemd")))] | |
| 486 { | |
| 487 Ok(()) | |
| 342 } | 488 } |
| 343 } | 489 } |
| 344 | 490 |
| 345 /// Displays the address in format that can be parsed again. | 491 /// Displays the address in format that can be parsed again. |
| 346 /// | 492 /// |
