From e70c5b33a84d8e56beef35449e4f31621503f76e Mon Sep 17 00:00:00 2001 From: Karolin Varner Date: Sat, 3 Aug 2024 13:07:00 +0200 Subject: [PATCH 01/21] chore: Ignore vscode directory --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d240f9d6..4270d4ea 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,7 @@ _markdown_* **/result-* .direnv -/output \ No newline at end of file +# Visual studio code +.vscode + +/output From 6bbe85a57b2257b5411f32d737f1341bc8b42a2e Mon Sep 17 00:00:00 2001 From: Karolin Varner Date: Sat, 3 Aug 2024 13:45:04 +0200 Subject: [PATCH 02/21] chore: Remove unnecessary imports --- cipher-traits/src/kem.rs | 2 -- secret-memory/src/secret.rs | 1 - util/src/file.rs | 1 - util/src/result.rs | 1 - wireguard-broker/src/api/msgs.rs | 1 - wireguard-broker/src/api/server.rs | 1 - wireguard-broker/src/bin/priviledged.rs | 1 - wireguard-broker/src/lib.rs | 2 +- 8 files changed, 1 insertion(+), 9 deletions(-) diff --git a/cipher-traits/src/kem.rs b/cipher-traits/src/kem.rs index f03f5f7a..5470736b 100644 --- a/cipher-traits/src/kem.rs +++ b/cipher-traits/src/kem.rs @@ -10,8 +10,6 @@ //! The [KEM] Trait describes the basic API offered by a Key Encapsulation //! Mechanism. Two implementations for it are provided, [StaticKEM] and [EphemeralKEM]. -use std::result::Result; - /// Key Encapsulation Mechanism /// /// The KEM interface defines three operations: Key generation, key encapsulation and key diff --git a/secret-memory/src/secret.rs b/secret-memory/src/secret.rs index 6173de38..3715ea07 100644 --- a/secret-memory/src/secret.rs +++ b/secret-memory/src/secret.rs @@ -1,6 +1,5 @@ use std::cell::RefCell; use std::collections::HashMap; -use std::convert::TryInto; use std::fmt; use std::ops::{Deref, DerefMut}; use std::path::Path; diff --git a/util/src/file.rs b/util/src/file.rs index af10ae04..dfba76c0 100644 --- a/util/src/file.rs +++ b/util/src/file.rs @@ -2,7 +2,6 @@ use anyhow::ensure; use std::fs::File; use std::io::Read; use std::os::unix::fs::OpenOptionsExt; -use std::result::Result; use std::{fs::OpenOptions, path::Path}; pub enum Visibility { diff --git a/util/src/result.rs b/util/src/result.rs index 8f0f258f..986ac6bd 100644 --- a/util/src/result.rs +++ b/util/src/result.rs @@ -1,5 +1,4 @@ use std::convert::Infallible; -use std::result::Result; /// Try block basically…returns a result and allows the use of the question mark operator inside #[macro_export] diff --git a/wireguard-broker/src/api/msgs.rs b/wireguard-broker/src/api/msgs.rs index 89c6a6d0..83787dfa 100644 --- a/wireguard-broker/src/api/msgs.rs +++ b/wireguard-broker/src/api/msgs.rs @@ -1,4 +1,3 @@ -use std::result::Result; use std::str::{from_utf8, Utf8Error}; use zerocopy::{AsBytes, FromBytes, FromZeroes}; diff --git a/wireguard-broker/src/api/server.rs b/wireguard-broker/src/api/server.rs index bd3f6a30..d996d59a 100644 --- a/wireguard-broker/src/api/server.rs +++ b/wireguard-broker/src/api/server.rs @@ -1,5 +1,4 @@ use std::borrow::BorrowMut; -use std::result::Result; use rosenpass_secret_memory::{Public, Secret}; diff --git a/wireguard-broker/src/bin/priviledged.rs b/wireguard-broker/src/bin/priviledged.rs index 131b9ad5..9c446bd1 100644 --- a/wireguard-broker/src/bin/priviledged.rs +++ b/wireguard-broker/src/bin/priviledged.rs @@ -9,7 +9,6 @@ fn main() { #[cfg(target_os = "linux")] pub mod linux { use std::io::{stdin, stdout, Read, Write}; - use std::result::Result; use rosenpass_wireguard_broker::api::msgs; use rosenpass_wireguard_broker::api::server::BrokerServer; diff --git a/wireguard-broker/src/lib.rs b/wireguard-broker/src/lib.rs index ee46b674..e9ba4431 100644 --- a/wireguard-broker/src/lib.rs +++ b/wireguard-broker/src/lib.rs @@ -1,5 +1,5 @@ use rosenpass_secret_memory::{Public, Secret}; -use std::{fmt::Debug, result::Result}; +use std::fmt::Debug; pub const WG_KEY_LEN: usize = 32; pub const WG_PEER_LEN: usize = 32; From deafc1c1af63fe7390ed6d3407cb4dca304fe6ef Mon Sep 17 00:00:00 2001 From: Karolin Varner Date: Sat, 3 Aug 2024 13:50:50 +0200 Subject: [PATCH 03/21] =?UTF-8?q?chore:=20Style=20adjustments=20=E2=80=93?= =?UTF-8?q?=20Cargo.toml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 44a6e325..ead707c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,13 +51,13 @@ arbitrary = { version = "1.3.2", features = ["derive"] } anyhow = { version = "1.0.86", features = ["backtrace", "std"] } mio = { version = "1.0.1", features = ["net", "os-poll"] } oqs-sys = { version = "0.9.1", default-features = false, features = [ - 'classic_mceliece', - 'kyber', + 'classic_mceliece', + 'kyber', ] } blake2 = "0.10.6" chacha20poly1305 = { version = "0.10.1", default-features = false, features = [ - "std", - "heapless", + "std", + "heapless", ] } zerocopy = { version = "0.7.35", features = ["derive"] } home = "0.5.9" From 37f7b3e4e924dde478cbfa6f927d44922d8699a9 Mon Sep 17 00:00:00 2001 From: Karolin Varner Date: Sat, 3 Aug 2024 13:56:25 +0200 Subject: [PATCH 04/21] fix: Consistently use feature flag `experiment_libcrux` Before this, some parts of the code used an incorrect feature flag name, preventing libcrux from being used. --- ciphers/src/lib.rs | 8 +++++--- ciphers/src/subtle/mod.rs | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/ciphers/src/lib.rs b/ciphers/src/lib.rs index 25c9cb2a..58240b15 100644 --- a/ciphers/src/lib.rs +++ b/ciphers/src/lib.rs @@ -9,10 +9,12 @@ const_assert!(KEY_LEN == hash_domain::KEY_LEN); /// Authenticated encryption with associated data pub mod aead { - #[cfg(not(feature = "libcrux"))] - pub use crate::subtle::chacha20poly1305_ietf::{decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN}; - #[cfg(feature = "libcrux")] + #[cfg(not(feature = "experiment_libcrux"))] pub use crate::subtle::chacha20poly1305_ietf::{decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN}; + #[cfg(feature = "experiment_libcrux")] + pub use crate::subtle::chacha20poly1305_ietf_libcrux::{ + decrypt, encrypt, KEY_LEN, NONCE_LEN, TAG_LEN, + }; } /// Authenticated encryption with associated data with a constant nonce diff --git a/ciphers/src/subtle/mod.rs b/ciphers/src/subtle/mod.rs index fc82773f..b7161647 100644 --- a/ciphers/src/subtle/mod.rs +++ b/ciphers/src/subtle/mod.rs @@ -1,7 +1,7 @@ pub mod blake2b; -#[cfg(not(feature = "libcrux"))] +#[cfg(not(feature = "experiment_libcrux"))] pub mod chacha20poly1305_ietf; -#[cfg(feature = "libcrux")] +#[cfg(feature = "experiment_libcrux")] pub mod chacha20poly1305_ietf_libcrux; pub mod incorrect_hmac_blake2b; pub mod xchacha20poly1305_ietf; From a4b8fc222637509fc39ad25a35b9d229e45a6874 Mon Sep 17 00:00:00 2001 From: Karolin Varner Date: Sat, 3 Aug 2024 14:05:22 +0200 Subject: [PATCH 05/21] chore: Move memcmp test API doc to test memcmp test module --- constant-time/src/memcmp.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/constant-time/src/memcmp.rs b/constant-time/src/memcmp.rs index 9872028b..8039cc8f 100644 --- a/constant-time/src/memcmp.rs +++ b/constant-time/src/memcmp.rs @@ -7,18 +7,16 @@ /// /// The execution time of the function grows approx. linear with the length of the input. This is /// considered safe. -/// -/// ## Tests -/// [`tests::memcmp_runs_in_constant_time`] runs a stasticial test that the equality of the two -/// input parameters does not correlate with the run time. -/// -/// For discussion on how to (further) ensure the constant-time execution of this function, -/// see #[inline] pub fn memcmp(a: &[u8], b: &[u8]) -> bool { a.len() == b.len() && unsafe { memsec::memeq(a.as_ptr(), b.as_ptr(), a.len()) } } +/// [tests::memcmp_runs_in_constant_time] runs a stasticial test that the equality of the two +/// input parameters does not correlate with the run time. +/// +/// For discussion on how to (further) ensure the constant-time execution of this function, +/// see #[cfg(all(test, feature = "constant_time_tests"))] mod tests { use super::*; From 40c5bbd167b86ec9a3b58368b4f18f7a1705c40f Mon Sep 17 00:00:00 2001 From: Karolin Varner Date: Sat, 3 Aug 2024 14:06:19 +0200 Subject: [PATCH 06/21] chore: Ensure that rustAnalyzer is installed in dev environment --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 950dcbf0..4a546042 100644 --- a/flake.nix +++ b/flake.nix @@ -411,12 +411,12 @@ inherit (packages.proof-proverif) CRYPTOVERIF_LIB; inputsFrom = [ packages.default ]; nativeBuildInputs = with pkgs; [ + inputs.fenix.packages.${system}.complete.toolchain cmake # override the fakecmake from the main step above cargo-release clippy nodePackages.prettier nushell # for the .ci/gen-workflow-files.nu script - rustfmt packages.proverif-patched ]; }; From 54ac5eecdbc55c589636fd878ac30417cdb7793c Mon Sep 17 00:00:00 2001 From: Karolin Varner Date: Sat, 3 Aug 2024 14:06:58 +0200 Subject: [PATCH 07/21] chore: Warnings & clippy hints --- fuzz/fuzz_targets/aead_enc_into.rs | 3 +-- rosenpass/tests/integration_test.rs | 3 ++- secret-memory/src/public.rs | 3 ++- secret-memory/src/secret.rs | 2 +- util/src/typenum.rs | 7 +++++++ 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/fuzz/fuzz_targets/aead_enc_into.rs b/fuzz/fuzz_targets/aead_enc_into.rs index 19f0bc46..8f529f7a 100644 --- a/fuzz/fuzz_targets/aead_enc_into.rs +++ b/fuzz/fuzz_targets/aead_enc_into.rs @@ -15,8 +15,7 @@ pub struct Input { } fuzz_target!(|input: Input| { - let mut ciphertext: Vec = Vec::with_capacity(input.plaintext.len() + 16); - ciphertext.resize(input.plaintext.len() + 16, 0); + let mut ciphertext = vec![0u8; input.plaintext.len() + 16]; aead::encrypt( ciphertext.as_mut_slice(), diff --git a/rosenpass/tests/integration_test.rs b/rosenpass/tests/integration_test.rs index 66925d67..97c86ca8 100644 --- a/rosenpass/tests/integration_test.rs +++ b/rosenpass/tests/integration_test.rs @@ -283,6 +283,7 @@ struct MockBrokerInner { interface: Option, } +#[allow(dead_code)] #[derive(Debug, Default)] struct MockBroker { inner: Arc>, @@ -321,7 +322,7 @@ impl rosenpass_wireguard_broker::WireGuardBroker for MockBroker { if let Ok(ref mut mutex) = lock { **mutex = MockBrokerInner { psk: Some(config.psk.clone()), - peer_id: Some(config.peer_id.clone()), + peer_id: Some(*config.peer_id), interface: Some(std::str::from_utf8(config.interface).unwrap().to_string()), }; break Ok(()); diff --git a/secret-memory/src/public.rs b/secret-memory/src/public.rs index 81fb6487..88bb785a 100644 --- a/secret-memory/src/public.rs +++ b/secret-memory/src/public.rs @@ -317,6 +317,7 @@ impl StoreValueB64Writer for PublicBox { mod tests { #[cfg(test)] + #[allow(clippy::module_inception)] mod tests { use crate::{Public, PublicBox}; use rosenpass_util::{ @@ -346,7 +347,7 @@ mod tests { // Store the original bytes to an example file in the temporary directory let example_file = temp_dir.path().join("example_file"); - std::fs::write(example_file.clone(), &original_bytes).unwrap(); + std::fs::write(&example_file, original_bytes).unwrap(); // Load the value from the example file into our generic type let loaded_public = T::load(&example_file).unwrap(); diff --git a/secret-memory/src/secret.rs b/secret-memory/src/secret.rs index 3715ea07..9f4e50da 100644 --- a/secret-memory/src/secret.rs +++ b/secret-memory/src/secret.rs @@ -386,7 +386,7 @@ mod test { // Store the original secret to an example file in the temporary directory let example_file = temp_dir.path().join("example_file"); - std::fs::write(example_file.clone(), &original_bytes).unwrap(); + std::fs::write(&example_file, original_bytes).unwrap(); // Load the secret from the example file let loaded_secret = Secret::load(&example_file).unwrap(); diff --git a/util/src/typenum.rs b/util/src/typenum.rs index 94fe0fd0..2f1d32fd 100644 --- a/util/src/typenum.rs +++ b/util/src/typenum.rs @@ -19,15 +19,22 @@ pub trait IntoConst { const VALUE: T; } +#[allow(dead_code)] struct ConstApplyNegSign::Type>>( *const T, *const Param, ); + +#[allow(dead_code)] struct ConstApplyPosSign::Type>>( *const T, *const Param, ); + +#[allow(dead_code)] struct ConstLshift, const SHIFT: i32>(*const T, *const Param); // impl IntoConst + +#[allow(dead_code)] struct ConstAdd, Rhs: IntoConst>(*const T, *const Lhs, *const Rhs); // impl IntoConst /// Assigns an unsigned type to a signed type From 648a94ead845aa3429a2917d1c89b57747083a21 Mon Sep 17 00:00:00 2001 From: Karolin Varner Date: Sat, 3 Aug 2024 15:02:49 +0200 Subject: [PATCH 08/21] chore: Clippy fixes on wireguard-broker --- wireguard-broker/src/api/client.rs | 4 ++-- wireguard-broker/src/api/config.rs | 12 +++++----- wireguard-broker/src/api/msgs.rs | 2 +- wireguard-broker/src/api/server.rs | 2 +- wireguard-broker/src/bin/priviledged.rs | 2 +- wireguard-broker/src/bin/socket_handler.rs | 4 ++-- wireguard-broker/src/brokers/mio_client.rs | 26 +++++++++------------- wireguard-broker/src/brokers/netlink.rs | 9 ++++---- wireguard-broker/tests/integration.rs | 1 + 9 files changed, 28 insertions(+), 34 deletions(-) diff --git a/wireguard-broker/src/api/client.rs b/wireguard-broker/src/api/client.rs index f27c3355..73dbb066 100644 --- a/wireguard-broker/src/api/client.rs +++ b/wireguard-broker/src/api/client.rs @@ -88,7 +88,7 @@ where None => return Ok(None), }; - let typ = res.get(0).ok_or(invalid_msg_poller())?; + let typ = res.first().ok_or(invalid_msg_poller())?; let typ = msgs::MsgType::try_from(*typ)?; let msgs::MsgType::SetPsk = typ; // Assert type @@ -113,7 +113,7 @@ where fn set_psk(&mut self, config: SerializedBrokerConfig) -> Result<(), Self::Error> { let config: Result = config.try_into(); - let config = config.map_err(|e| BrokerClientSetPskError::BrokerError(e))?; + let config = config.map_err(BrokerClientSetPskError::BrokerError)?; use BrokerClientSetPskError::*; const BUF_SIZE: usize = REQUEST_MSG_BUFFER_SIZE; diff --git a/wireguard-broker/src/api/config.rs b/wireguard-broker/src/api/config.rs index 097742fe..c487d7fa 100644 --- a/wireguard-broker/src/api/config.rs +++ b/wireguard-broker/src/api/config.rs @@ -11,12 +11,12 @@ pub struct NetworkBrokerConfig<'a> { pub psk: &'a Secret, } -impl<'a> Into> for NetworkBrokerConfig<'a> { - fn into(self) -> SerializedBrokerConfig<'a> { - SerializedBrokerConfig { - interface: self.iface.as_bytes(), - peer_id: self.peer_id, - psk: self.psk, +impl<'a> From> for SerializedBrokerConfig<'a> { + fn from(src: NetworkBrokerConfig<'a>) -> SerializedBrokerConfig<'a> { + Self { + interface: src.iface.as_bytes(), + peer_id: src.peer_id, + psk: src.psk, additional_params: &[], } } diff --git a/wireguard-broker/src/api/msgs.rs b/wireguard-broker/src/api/msgs.rs index 83787dfa..4021c5b3 100644 --- a/wireguard-broker/src/api/msgs.rs +++ b/wireguard-broker/src/api/msgs.rs @@ -42,7 +42,7 @@ impl SetPskRequest { self.iface_size = iface.len() as u8; self.iface_buf = [0; 255]; - (&mut self.iface_buf[..iface.len()]).copy_from_slice(iface); + self.iface_buf[..iface.len()].copy_from_slice(iface); Some(()) } diff --git a/wireguard-broker/src/api/server.rs b/wireguard-broker/src/api/server.rs index d996d59a..1eaf2556 100644 --- a/wireguard-broker/src/api/server.rs +++ b/wireguard-broker/src/api/server.rs @@ -48,7 +48,7 @@ where ) -> Result { use BrokerServerError::*; - let typ = req.get(0).ok_or(InvalidMessage)?; + let typ = req.first().ok_or(InvalidMessage)?; let typ = msgs::MsgType::try_from(*typ)?; let msgs::MsgType::SetPsk = typ; // Assert type diff --git a/wireguard-broker/src/bin/priviledged.rs b/wireguard-broker/src/bin/priviledged.rs index 9c446bd1..2086cbe0 100644 --- a/wireguard-broker/src/bin/priviledged.rs +++ b/wireguard-broker/src/bin/priviledged.rs @@ -59,7 +59,7 @@ pub mod linux { // Write the response stdout.write_all(&(res.len() as u64).to_le_bytes())?; - stdout.write_all(&res)?; + stdout.write_all(res)?; stdout.flush()?; } } diff --git a/wireguard-broker/src/bin/socket_handler.rs b/wireguard-broker/src/bin/socket_handler.rs index e1693c58..9638ba59 100644 --- a/wireguard-broker/src/bin/socket_handler.rs +++ b/wireguard-broker/src/bin/socket_handler.rs @@ -121,7 +121,7 @@ async fn direct_broker_process( // Read the message itself let mut res_buf = request; // Avoid allocating memory if we don't have to - res_buf.resize(len as usize, 0); + res_buf.resize(len, 0); stdout.read_exact(&mut res_buf[..len]).await?; // Return to the unix socket connection worker @@ -163,7 +163,7 @@ async fn on_accept(queue: mpsc::Sender, mut stream: UnixStream) - ); // Read the message itself - req_buf.resize(len as usize, 0); + req_buf.resize(len, 0); stream.read_exact(&mut req_buf[..len]).await?; // Handle the message diff --git a/wireguard-broker/src/brokers/mio_client.rs b/wireguard-broker/src/brokers/mio_client.rs index 666a0558..8898613d 100644 --- a/wireguard-broker/src/brokers/mio_client.rs +++ b/wireguard-broker/src/brokers/mio_client.rs @@ -52,23 +52,17 @@ impl MioBrokerClient { // This sucks match self.inner.poll_response() { - Ok(res) => { - return Ok(res); - } - Err(BrokerClientPollResponseError::IoError(e)) => { - return Err(e); - } - Err(BrokerClientPollResponseError::InvalidMessage) => { - bail!("Invalid message"); - } - }; + Ok(res) => Ok(res), + Err(BrokerClientPollResponseError::IoError(e)) => Err(e), + Err(BrokerClientPollResponseError::InvalidMessage) => bail!("Invalid message"), + } } } impl WireGuardBroker for MioBrokerClient { type Error = anyhow::Error; - fn set_psk<'a>(&mut self, config: SerializedBrokerConfig<'a>) -> anyhow::Result<()> { + fn set_psk(&mut self, config: SerializedBrokerConfig<'_>) -> anyhow::Result<()> { use BrokerClientSetPskError::*; let e = self.inner.set_psk(config); match e { @@ -115,7 +109,7 @@ impl BrokerClientIo for MioBrokerClientIo { fn send_msg(&mut self, buf: &[u8]) -> Result<(), Self::SendError> { self.flush()?; self.send_or_buffer(&(buf.len() as u64).to_le_bytes())?; - self.send_or_buffer(&buf)?; + self.send_or_buffer(buf)?; self.flush()?; Ok(()) @@ -192,7 +186,7 @@ impl MioBrokerClientIo { self.send_buf.drain(..written); - (&self.socket).try_io(|| (&self.socket).flush())?; + self.socket.try_io(|| (&self.socket).flush())?; res } @@ -204,7 +198,7 @@ impl MioBrokerClientIo { off += raw_send(&self.socket, buf)?; } - self.send_buf.extend((&buf[off..]).iter()); + self.send_buf.extend(buf[off..].iter()); Ok(()) } @@ -231,7 +225,7 @@ fn raw_send(mut socket: &mio::net::UnixStream, data: &[u8]) -> anyhow::Result anyhow::Result { @@ -255,5 +249,5 @@ fn raw_recv(mut socket: &mio::net::UnixStream, out: &mut [u8]) -> anyhow::Result } })?; - return Ok(off); + Ok(off) } diff --git a/wireguard-broker/src/brokers/netlink.rs b/wireguard-broker/src/brokers/netlink.rs index 4c8ffda8..556bd3cb 100644 --- a/wireguard-broker/src/brokers/netlink.rs +++ b/wireguard-broker/src/brokers/netlink.rs @@ -87,21 +87,20 @@ impl WireGuardBroker for NetlinkWireGuardBroker { .sock .get_device(wg::DeviceInterface::from_name(config.iface))?; - if state + if !state .peers .iter() - .find(|p| &p.public_key == &config.peer_id.value) - .is_none() + .any(|p| p.public_key == config.peer_id.value) { return Err(SetPskError::NoSuchPeer); } // Peer update description - let mut set_peer = wireguard_uapi::set::Peer::from_public_key(&config.peer_id); + let mut set_peer = wireguard_uapi::set::Peer::from_public_key(config.peer_id); set_peer .flags .push(wireguard_uapi::linux::set::WgPeerF::UpdateOnly); - set_peer.preshared_key = Some(&config.psk.secret()); + set_peer.preshared_key = Some(config.psk.secret()); // Device update description let mut set_dev = wireguard_uapi::set::Device::from_ifname(config.iface); diff --git a/wireguard-broker/tests/integration.rs b/wireguard-broker/tests/integration.rs index 2166aab9..b25885f6 100644 --- a/wireguard-broker/tests/integration.rs +++ b/wireguard-broker/tests/integration.rs @@ -36,6 +36,7 @@ mod integration_tests { impl WireGuardBroker for MockServerBroker { type Error = SetPskError; + #[allow(clippy::clone_on_copy)] fn set_psk(&mut self, config: SerializedBrokerConfig) -> Result<(), Self::Error> { loop { let mut lock = self.inner.try_lock(); From 8d3c8790fe3ee15079c3af563cc919408629ff25 Mon Sep 17 00:00:00 2001 From: Karolin Varner Date: Sat, 3 Aug 2024 15:06:58 +0200 Subject: [PATCH 09/21] chore: Reorganize memfd secret policy - Policy is now set in main.rs, not cli.rs. - Feature is called experiment_memfd_secret, not enable_memfd_alloc This also fixes the last remaining warnings. --- rosenpass/Cargo.toml | 2 +- rosenpass/src/cli.rs | 10 ---------- rosenpass/src/main.rs | 8 ++++++++ rosenpass/tests/integration_test.rs | 12 ++++++++++++ rp/Cargo.toml | 2 +- rp/src/main.rs | 4 ++-- 6 files changed, 24 insertions(+), 14 deletions(-) diff --git a/rosenpass/Cargo.toml b/rosenpass/Cargo.toml index a90c458e..e574ad5a 100644 --- a/rosenpass/Cargo.toml +++ b/rosenpass/Cargo.toml @@ -53,5 +53,5 @@ procspawn = {workspace = true} [features] enable_broker_api = ["rosenpass-wireguard-broker/enable_broker_api"] -enable_memfd_alloc = [] +experiment_memfd_secret = [] experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux"] diff --git a/rosenpass/src/cli.rs b/rosenpass/src/cli.rs index d69d4046..47a33d85 100644 --- a/rosenpass/src/cli.rs +++ b/rosenpass/src/cli.rs @@ -3,9 +3,6 @@ use clap::{Parser, Subcommand}; use rosenpass_cipher_traits::Kem; use rosenpass_ciphers::kem::StaticKem; use rosenpass_secret_memory::file::StoreSecret; -use rosenpass_secret_memory::{ - secret_policy_try_use_memfd_secrets, secret_policy_use_only_malloc_secrets, -}; use rosenpass_util::file::{LoadValue, LoadValueB64, StoreValue}; use rosenpass_wireguard_broker::brokers::native_unix::{ NativeUnixBroker, NativeUnixBrokerConfigBaseBuilder, NativeUnixBrokerConfigBaseBuilderError, @@ -158,13 +155,6 @@ impl CliCommand { /// ## TODO /// - This method consumes the [`CliCommand`] value. It might be wise to use a reference... pub fn run(self, test_helpers: Option) -> anyhow::Result<()> { - //Specify secret policy - - #[cfg(feature = "enable_memfd_alloc")] - secret_policy_try_use_memfd_secrets(); - #[cfg(not(feature = "enable_memfd_alloc"))] - secret_policy_use_only_malloc_secrets(); - use CliCommand::*; match self { Man => { diff --git a/rosenpass/src/main.rs b/rosenpass/src/main.rs index acf28507..58f65cb5 100644 --- a/rosenpass/src/main.rs +++ b/rosenpass/src/main.rs @@ -8,6 +8,14 @@ pub fn main() { // parse CLI arguments let args = CliArgs::parse(); + { + use rosenpass_secret_memory as SM; + #[cfg(feature = "experiment_memfd_secret")] + SM::secret_policy_try_use_memfd_secrets(); + #[cfg(not(feature = "experiment_memfd_secret"))] + SM::secret_policy_use_only_malloc_secrets(); + } + // init logging { let mut log_builder = env_logger::Builder::from_default_env(); // sets log level filter from environment (or defaults) diff --git a/rosenpass/tests/integration_test.rs b/rosenpass/tests/integration_test.rs index 97c86ca8..628e8c1a 100644 --- a/rosenpass/tests/integration_test.rs +++ b/rosenpass/tests/integration_test.rs @@ -15,9 +15,19 @@ use std::io::Write; const BIN: &str = "rosenpass"; +fn setup_tests() { + use rosenpass_secret_memory as SM; + #[cfg(feature = "experiment_memfd_secret")] + SM::secret_policy_try_use_memfd_secrets(); + #[cfg(not(feature = "experiment_memfd_secret"))] + SM::secret_policy_use_only_malloc_secrets(); +} + // check that we can generate keys #[test] fn generate_keys() { + setup_tests(); + let tmpdir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("keygen"); fs::create_dir_all(&tmpdir).unwrap(); @@ -134,6 +144,7 @@ fn run_server_client_exchange( #[test] #[serial] fn check_exchange_under_normal() { + setup_tests(); setup_logging(); let tmpdir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join("exchange"); @@ -206,6 +217,7 @@ fn check_exchange_under_normal() { #[test] #[serial] fn check_exchange_under_dos() { + setup_tests(); setup_logging(); //Generate binary with responder with feature integration_test diff --git a/rp/Cargo.toml b/rp/Cargo.toml index 95e3babb..9166bdc9 100644 --- a/rp/Cargo.toml +++ b/rp/Cargo.toml @@ -39,5 +39,5 @@ tempfile = {workspace = true} stacker = {workspace = true} [features] -enable_memfd_alloc = [] +experiment_memfd_secret = [] experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux"] diff --git a/rp/src/main.rs b/rp/src/main.rs index 014b0e77..7a2ab996 100644 --- a/rp/src/main.rs +++ b/rp/src/main.rs @@ -11,9 +11,9 @@ mod key; #[tokio::main] async fn main() { - #[cfg(feature = "enable_memfd_alloc")] + #[cfg(feature = "experiment_memfd_secret")] policy::secret_policy_try_use_memfd_secrets(); - #[cfg(not(feature = "enable_memfd_alloc"))] + #[cfg(not(feature = "experiment_memfd_secret"))] policy::secret_policy_use_only_malloc_secrets(); let cli = match Cli::parse(std::env::args().peekable()) { From c9c266fe7ce63f0272a8e84869cf7d33c24630a2 Mon Sep 17 00:00:00 2001 From: Karolin Varner Date: Sat, 3 Aug 2024 15:24:35 +0200 Subject: [PATCH 10/21] fix: Flush stdout after printing key update notification Otherwise, the notification might not be delivered due to buffering. --- rosenpass/src/app_server.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/rosenpass/src/app_server.rs b/rosenpass/src/app_server.rs index b6d34c42..55ceeeab 100644 --- a/rosenpass/src/app_server.rs +++ b/rosenpass/src/app_server.rs @@ -16,7 +16,9 @@ use std::cell::Cell; use std::collections::HashMap; use std::fmt::Debug; +use std::io::stdout; use std::io::ErrorKind; +use std::io::Write; use std::net::Ipv4Addr; use std::net::Ipv6Addr; use std::net::SocketAddr; @@ -870,10 +872,14 @@ impl AppServer { // this is intentionally writing to stdout instead of stderr, because // it is meant to allow external detection of a successful key-exchange - println!( + let stdout = stdout(); + let mut stdout = stdout.lock(); + writeln!( + stdout, "output-key peer {} key-file {of:?} {why}", peerid.fmt_b64::() - ); + )?; + stdout.flush()?; } peer.set_psk(self, key)?; From 1ab457ed37bae69c64370f84a74c37704d96fc48 Mon Sep 17 00:00:00 2001 From: Karolin Varner Date: Sat, 3 Aug 2024 15:28:59 +0200 Subject: [PATCH 11/21] fix: Print stack trace to errors propagated to main function --- rosenpass/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rosenpass/src/main.rs b/rosenpass/src/main.rs index 58f65cb5..bb6680ee 100644 --- a/rosenpass/src/main.rs +++ b/rosenpass/src/main.rs @@ -37,7 +37,7 @@ pub fn main() { match args.command.run(None) { Ok(_) => {} Err(e) => { - error!("{e}"); + error!("{e:?}"); exit(1); } } From 3cc3b6009f9cb973a90ecdf34747e051ea1a2d3f Mon Sep 17 00:00:00 2001 From: Karolin Varner Date: Sat, 3 Aug 2024 15:32:15 +0200 Subject: [PATCH 12/21] chore: Move CliCommand::run -> CliArgs::run; do not mutate the configuration This way CliArgs::run has access to all command line parameters. Avoided mutating the CliArgs (or rather CliCommand) structure here, because doing so is simply bad style. There is no good reasoning for why this function should mutate CliCommand, except for a bit of convenience. --- rosenpass/src/cli.rs | 26 ++++++++++++++------------ rosenpass/src/main.rs | 2 +- rosenpass/tests/integration_test.rs | 6 ++---- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/rosenpass/src/cli.rs b/rosenpass/src/cli.rs index 47a33d85..473b30f2 100644 --- a/rosenpass/src/cli.rs +++ b/rosenpass/src/cli.rs @@ -149,14 +149,14 @@ pub enum CliCommand { Man, } -impl CliCommand { +impl CliArgs { /// runs the command specified via CLI /// /// ## TODO /// - This method consumes the [`CliCommand`] value. It might be wise to use a reference... pub fn run(self, test_helpers: Option) -> anyhow::Result<()> { use CliCommand::*; - match self { + match &self.command { Man => { let man_cmd = std::process::Command::new("man") .args(["1", "rosenpass"]) @@ -168,7 +168,7 @@ impl CliCommand { } GenConfig { config_file, force } => { ensure!( - force || !config_file.exists(), + *force || !config_file.exists(), "config file {config_file:?} already exists" ); @@ -183,9 +183,9 @@ impl CliCommand { let mut secret_key: Option = None; // Manual arg parsing, since clap wants to prefix flags with "--" - let mut args = args.into_iter(); + let mut args = args.iter(); loop { - match (args.next().as_deref(), args.next()) { + match (args.next().map(|x| x.as_str()), args.next()) { (Some("private-key"), Some(opt)) | (Some("secret-key"), Some(opt)) => { secret_key = Some(opt.into()); } @@ -227,7 +227,7 @@ impl CliCommand { (config.public_key, config.secret_key) } - (_, Some(pkf), Some(skf)) => (pkf, skf), + (_, Some(pkf), Some(skf)) => (pkf.clone(), skf.clone()), _ => { bail!("either a config-file or both public-key and secret-key file are required") } @@ -266,16 +266,17 @@ impl CliCommand { Exchange { first_arg, - mut rest_of_args, + rest_of_args, config_file, } => { - rest_of_args.insert(0, first_arg); + let mut rest_of_args = rest_of_args.clone(); + rest_of_args.insert(0, first_arg.clone()); let args = rest_of_args; let mut config = config::Rosenpass::parse_args(args)?; if let Some(p) = config_file { - config.store(&p)?; - config.config_file_path = p; + config.store(p)?; + config.config_file_path.clone_from(p); } config.validate()?; Self::event_loop(config, test_helpers)?; @@ -283,7 +284,7 @@ impl CliCommand { Validate { config_files } => { for file in config_files { - match config::Rosenpass::load(&file) { + match config::Rosenpass::load(file) { Ok(config) => { eprintln!("{file:?} is valid TOML and conforms to the expected schema"); match config.validate() { @@ -305,6 +306,7 @@ impl CliCommand { test_helpers: Option, ) -> anyhow::Result<()> { const MAX_PSK_SIZE: usize = 1000; + // load own keys let sk = SSk::load(&config.secret_key)?; let pk = SPk::load(&config.public_key)?; @@ -313,7 +315,7 @@ impl CliCommand { let mut srv = std::boxed::Box::::new(AppServer::new( sk, pk, - config.listen, + config.listen.clone(), config.verbosity, test_helpers, )?); diff --git a/rosenpass/src/main.rs b/rosenpass/src/main.rs index bb6680ee..b769c48b 100644 --- a/rosenpass/src/main.rs +++ b/rosenpass/src/main.rs @@ -34,7 +34,7 @@ pub fn main() { // error!("error dummy"); } - match args.command.run(None) { + match args.run(None) { Ok(_) => {} Err(e) => { error!("{e:?}"); diff --git a/rosenpass/tests/integration_test.rs b/rosenpass/tests/integration_test.rs index 628e8c1a..5569a944 100644 --- a/rosenpass/tests/integration_test.rs +++ b/rosenpass/tests/integration_test.rs @@ -104,8 +104,7 @@ fn run_server_client_exchange( .unwrap(); std::thread::spawn(move || { - cli.command - .run(Some( + cli.run(Some( server_test_builder .termination_handler(Some(server_terminate_rx)) .build() @@ -122,8 +121,7 @@ fn run_server_client_exchange( .unwrap(); std::thread::spawn(move || { - cli.command - .run(Some( + cli.run(Some( client_test_builder .termination_handler(Some(client_terminate_rx)) .build() From 2dde0a2b4710cad6c13aa68df85cfa1cb81c2a79 Mon Sep 17 00:00:00 2001 From: Karolin Varner Date: Sat, 3 Aug 2024 16:31:19 +0200 Subject: [PATCH 13/21] chore: Refactor integration_tests (purely cosmetic) --- rosenpass/tests/integration_test.rs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/rosenpass/tests/integration_test.rs b/rosenpass/tests/integration_test.rs index 5569a944..f572d68c 100644 --- a/rosenpass/tests/integration_test.rs +++ b/rosenpass/tests/integration_test.rs @@ -104,13 +104,11 @@ fn run_server_client_exchange( .unwrap(); std::thread::spawn(move || { - cli.run(Some( - server_test_builder - .termination_handler(Some(server_terminate_rx)) - .build() - .unwrap(), - )) + let test_helpers = server_test_builder + .termination_handler(Some(server_terminate_rx)) + .build() .unwrap(); + cli.run(Some(test_helpers)).unwrap(); }); let cli = CliArgs::try_parse_from( @@ -121,13 +119,11 @@ fn run_server_client_exchange( .unwrap(); std::thread::spawn(move || { - cli.run(Some( - client_test_builder - .termination_handler(Some(client_terminate_rx)) - .build() - .unwrap(), - )) + let test_helpers = client_test_builder + .termination_handler(Some(client_terminate_rx)) + .build() .unwrap(); + cli.run(Some(test_helpers)).unwrap(); }); // give them some time to do the key exchange under load From 138e6b65533d90cad3255999ecedf0b93d701f09 Mon Sep 17 00:00:00 2001 From: Karolin Varner Date: Sat, 3 Aug 2024 16:32:02 +0200 Subject: [PATCH 14/21] chore: to crate documentation indendation (purely cosmetic) --- to/src/to/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/to/src/to/mod.rs b/to/src/to/mod.rs index 33f54d97..dfe4e522 100644 --- a/to/src/to/mod.rs +++ b/to/src/to/mod.rs @@ -6,8 +6,8 @@ //! - `Dst: ?Sized`; (e.g. [u8]) – The target to write to //! - `Out: Sized = &mut Dst`; (e.g. &mut [u8]) – A reference to the target to write to //! - `Coercable: ?Sized + DstCoercion`; (e.g. `[u8]`, `[u8; 16]`) – Some value that -//! destination coercion can be applied to. Usually either `Dst` itself (e.g. `[u8]` or some sized variant of -//! `Dst` (e.g. `[u8; 64]`). +//! destination coercion can be applied to. Usually either `Dst` itself (e.g. `[u8]` or some sized variant of +//! `Dst` (e.g. `[u8; 64]`). //! - `Ret: Sized`; (anything) – must be `CondenseBeside<_>` if condensing is to be applied. The ordinary return value of a function with an output //! - `Val: Sized + BorrowMut`; (e.g. [u8; 16]) – Some owned storage that can be borrowed as `Dst` //! - `Condensed: Sized = CondenseBeside::Condensed`; (e.g. [u8; 16], Result<[u8; 16]>) – The combiation of Val and Ret after condensing was applied (`Beside::condense()`/`Ret::condense(v)` for all `v : Val`). From 1bf0eed90a8ed2984a6d6dcf016713fe63cc40d3 Mon Sep 17 00:00:00 2001 From: Karolin Varner Date: Sat, 3 Aug 2024 16:46:48 +0200 Subject: [PATCH 15/21] feat: Convenience function to just call a function --- util/src/functional.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/util/src/functional.rs b/util/src/functional.rs index c138a2ef..10c5f945 100644 --- a/util/src/functional.rs +++ b/util/src/functional.rs @@ -13,3 +13,7 @@ where f(&v); v } + +pub fn run R>(f: F) -> R { + f() +} From 3063d3e4c2dfa6b3d1eb68f6b176a776e4ff95a7 Mon Sep 17 00:00:00 2001 From: Karolin Varner Date: Sat, 3 Aug 2024 16:48:25 +0200 Subject: [PATCH 16/21] feat: Convenience traits to get the ErrorKind of an io error for match clauses --- util/src/io.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++++++ util/src/lib.rs | 1 + 2 files changed, 52 insertions(+) create mode 100644 util/src/io.rs diff --git a/util/src/io.rs b/util/src/io.rs new file mode 100644 index 00000000..7dde4b4e --- /dev/null +++ b/util/src/io.rs @@ -0,0 +1,51 @@ +use std::{borrow::Borrow, io}; + +pub trait IoErrorKind { + fn io_error_kind(&self) -> io::ErrorKind; +} + +impl> IoErrorKind for T { + fn io_error_kind(&self) -> io::ErrorKind { + self.borrow().kind() + } +} + +pub trait TryIoErrorKind { + fn try_io_error_kind(&self) -> Option; +} + +impl TryIoErrorKind for T { + fn try_io_error_kind(&self) -> Option { + Some(self.io_error_kind()) + } +} + +pub trait IoResultKindHintExt: Sized { + type Error; + fn io_err_kind_hint(self) -> Result; +} + +impl IoResultKindHintExt for Result { + type Error = E; + fn io_err_kind_hint(self) -> Result { + self.map_err(|e| { + let kind = e.borrow().io_error_kind(); + (e, kind) + }) + } +} + +pub trait TryIoResultKindHintExt: Sized { + type Error; + fn try_io_err_kind_hint(self) -> Result)>; +} + +impl TryIoResultKindHintExt for Result { + type Error = E; + fn try_io_err_kind_hint(self) -> Result)> { + self.map_err(|e| { + let opt_kind = e.borrow().try_io_error_kind(); + (e, opt_kind) + }) + } +} diff --git a/util/src/lib.rs b/util/src/lib.rs index 637de5d1..f16e98fa 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -4,6 +4,7 @@ pub mod b64; pub mod fd; pub mod file; pub mod functional; +pub mod io; pub mod mem; pub mod ord; pub mod result; From ea071f536376bc790197ff7f598f676b8ab9dc82 Mon Sep 17 00:00:00 2001 From: Karolin Varner Date: Sat, 3 Aug 2024 16:49:02 +0200 Subject: [PATCH 17/21] feat: Convenience functions and traits to automatically handle ErrorKind::{Interrupt, WouldBlock} --- util/src/io.rs | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/util/src/io.rs b/util/src/io.rs index 7dde4b4e..111959f1 100644 --- a/util/src/io.rs +++ b/util/src/io.rs @@ -49,3 +49,62 @@ impl TryIoResultKindHintExt for Result { }) } } + +/// Automatically handles `std::io::ErrorKind::Interrupted`. +/// +/// - If there is no error (i.e. on `Ok(r)`), the function will return `Ok(Some(r))` +/// - `Interrupted` is handled internally, by retrying the IO operation +/// - Other errors are returned as is +pub fn handle_interrupted(mut iofn: F) -> Result, E> +where + E: TryIoErrorKind, + F: FnMut() -> Result, +{ + use io::ErrorKind as E; + loop { + match iofn().try_io_err_kind_hint() { + Ok(v) => return Ok(Some(v)), + Err((_, Some(E::Interrupted))) => continue, // try again + Err((e, _)) => return Err(e), + }; + } +} + +/// Automatically handles `std::io::ErrorKind::{WouldBlock, Interrupted}`. +/// +/// - If there is no error (i.e. on `Ok(r)`), the function will return `Ok(Some(r))` +/// - `Interrupted` is handled internally, by retrying the IO operation +/// - `WouldBlock` is handled by returning `Ok(None)`, +/// - Other errors are returned as is +pub fn nonblocking_handle_io_errors(mut iofn: F) -> Result, E> +where + E: TryIoErrorKind, + F: FnMut() -> Result, +{ + use io::ErrorKind as E; + loop { + match iofn().try_io_err_kind_hint() { + Ok(v) => return Ok(Some(v)), + Err((_, Some(E::WouldBlock))) => return Ok(None), // no data to read + Err((_, Some(E::Interrupted))) => continue, // try again + Err((e, _)) => return Err(e), + }; + } +} + +pub trait ReadNonblockingWithBoringErrorsHandledExt { + /// Convenience wrapper using [nonblocking_handle_io_errors] with [std::io::Read] + fn read_nonblocking_with_boring_errors_handled( + &mut self, + buf: &mut [u8], + ) -> io::Result>; +} + +impl ReadNonblockingWithBoringErrorsHandledExt for T { + fn read_nonblocking_with_boring_errors_handled( + &mut self, + buf: &mut [u8], + ) -> io::Result> { + nonblocking_handle_io_errors(|| self.read(buf)) + } +} From 730a03957ade3b1bc05daf6959e6997586289d78 Mon Sep 17 00:00:00 2001 From: Karolin Varner Date: Sat, 3 Aug 2024 16:50:21 +0200 Subject: [PATCH 18/21] feat: A variety of utilities in preparation for implementing the API --- Cargo.lock | 3 + util/Cargo.toml | 3 + util/src/length_prefix_encoding/decoder.rs | 359 +++++++++++++++++++ util/src/length_prefix_encoding/encoder.rs | 381 +++++++++++++++++++++ util/src/length_prefix_encoding/mod.rs | 2 + util/src/lib.rs | 4 + util/src/mio.rs | 39 +++ util/src/result.rs | 11 + util/src/zerocopy/mod.rs | 7 + util/src/zerocopy/ref_maker.rs | 106 ++++++ util/src/zerocopy/zerocopy_ref_ext.rs | 27 ++ util/src/zerocopy/zerocopy_slice_ext.rs | 39 +++ util/src/zeroize/mod.rs | 2 + util/src/zeroize/zeroized_ext.rs | 10 + 14 files changed, 993 insertions(+) create mode 100644 util/src/length_prefix_encoding/decoder.rs create mode 100644 util/src/length_prefix_encoding/encoder.rs create mode 100644 util/src/length_prefix_encoding/mod.rs create mode 100644 util/src/mio.rs create mode 100644 util/src/zerocopy/mod.rs create mode 100644 util/src/zerocopy/ref_maker.rs create mode 100644 util/src/zerocopy/zerocopy_ref_ext.rs create mode 100644 util/src/zerocopy/zerocopy_slice_ext.rs create mode 100644 util/src/zeroize/mod.rs create mode 100644 util/src/zeroize/zeroized_ext.rs diff --git a/Cargo.lock b/Cargo.lock index 858723b7..7f318b6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2039,9 +2039,12 @@ version = "0.1.0" dependencies = [ "anyhow", "base64ct", + "mio 1.0.1", "rustix", "static_assertions", + "thiserror", "typenum", + "zerocopy", "zeroize", ] diff --git a/util/Cargo.toml b/util/Cargo.toml index 7e2ef08b..e80ba0f5 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -18,3 +18,6 @@ typenum = { workspace = true } static_assertions = { workspace = true } rustix = {workspace = true} zeroize = {workspace = true} +zerocopy = { workspace = true } +thiserror = { workspace = true } +mio = { workspace = true } diff --git a/util/src/length_prefix_encoding/decoder.rs b/util/src/length_prefix_encoding/decoder.rs new file mode 100644 index 00000000..26a7130c --- /dev/null +++ b/util/src/length_prefix_encoding/decoder.rs @@ -0,0 +1,359 @@ +use std::{borrow::BorrowMut, cmp::min, io}; + +use thiserror::Error; +use zeroize::Zeroize; + +use crate::{ + io::{TryIoErrorKind, TryIoResultKindHintExt}, + result::ensure_or, +}; + +pub const HEADER_SIZE: usize = std::mem::size_of::(); + +#[derive(Error, Debug)] +pub enum SanityError { + #[error("Offset is out of read buffer bounds")] + OutOfBufferBounds, + #[error("Offset is out of message buffer bounds")] + OutOfMessageBounds, +} + +#[derive(Error, Debug)] +#[error("Message too lage ({msg_size} bytes) for buffer ({buf_size} bytes)")] +pub struct MessageTooLargeError { + msg_size: usize, + buf_size: usize, +} + +impl MessageTooLargeError { + pub fn new(msg_size: usize, buf_size: usize) -> Self { + Self { msg_size, buf_size } + } + + pub fn ensure(msg_size: usize, buf_size: usize) -> Result<(), Self> { + let err = MessageTooLargeError { msg_size, buf_size }; + ensure_or(msg_size <= buf_size, err) + } +} + +#[derive(Debug)] +pub struct ReadFromIoReturn<'a> { + pub bytes_read: usize, + pub message: Option<&'a mut [u8]>, +} + +impl<'a> ReadFromIoReturn<'a> { + pub fn new(bytes_read: usize, message: Option<&'a mut [u8]>) -> Self { + Self { + bytes_read, + message, + } + } +} + +#[derive(Debug, Error)] +pub enum ReadFromIoError { + #[error("Error reading from the underlying stream")] + IoError(#[from] io::Error), + #[error("Message size out of buffer bounds")] + MessageTooLargeError(#[from] MessageTooLargeError), +} + +impl TryIoErrorKind for ReadFromIoError { + fn try_io_error_kind(&self) -> Option { + match self { + ReadFromIoError::IoError(ioe) => Some(ioe.kind()), + _ => None, + } + } +} + +#[derive(Debug, Default, Clone)] +pub struct LengthPrefixDecoder> { + header: [u8; HEADER_SIZE], + buf: Buf, + off: usize, +} + +impl> LengthPrefixDecoder { + pub fn new(buf: Buf) -> Self { + let header = Default::default(); + let off = 0; + Self { header, buf, off } + } + + pub fn clear(&mut self) { + self.zeroize() + } + + pub fn from_parts(header: [u8; HEADER_SIZE], buf: Buf, off: usize) -> Self { + Self { header, buf, off } + } + + pub fn into_parts(self) -> ([u8; HEADER_SIZE], Buf, usize) { + let Self { header, buf, off } = self; + (header, buf, off) + } + + pub fn read_all_from_stdio( + &mut self, + mut r: R, + ) -> Result<&mut [u8], ReadFromIoError> { + use io::ErrorKind as K; + loop { + match self.read_from_stdio(&mut r).try_io_err_kind_hint() { + // Success (appeasing the borrow checker by calling message_mut()) + Ok(ReadFromIoReturn { + message: Some(_), .. + }) => break Ok(self.message_mut().unwrap().unwrap()), + + // Unexpected EOF + Ok(ReadFromIoReturn { bytes_read: 0, .. }) => { + break Err(ReadFromIoError::IoError(io::Error::new( + K::UnexpectedEof, + "", + ))) + } + + // Retry + Ok(ReadFromIoReturn { message: None, .. }) => continue, + Err((_, Some(K::Interrupted))) => continue, + + // Other error + Err((e, _)) => break Err(e), + } + } + } + + pub fn read_from_stdio( + &mut self, + mut r: R, + ) -> Result { + Ok(match self.next_slice_to_write_to()? { + // Read some bytes; any MessageTooLargeError in the call to self.message_mut() is + // ignored to ensure this function changes no state upon errors; the user should rerun + // the function and colect the MessageTooLargeError on the following invocation + Some(buf) => { + let bytes_read = r.read(buf)?; + self.advance(bytes_read).unwrap(); + let message = self.message_mut().ok().flatten(); + ReadFromIoReturn { + bytes_read, + message, + } + } + // Message is already fully read; full delegation to self.message_mut() + None => ReadFromIoReturn { + bytes_read: 0, + message: self.message_mut()?, + }, + }) + } + + pub fn next_slice_to_write_to(&mut self) -> Result, MessageTooLargeError> { + fn some_if_nonempty(buf: &mut [u8]) -> Option<&mut [u8]> { + match buf.is_empty() { + true => None, + false => Some(buf), + } + } + + macro_rules! return_if_nonempty_some { + ($opt:expr) => {{ + // Deliberate double expansion of $opt to appease the borrow checker *sigh* + if $opt.and_then(some_if_nonempty).is_some() { + return Ok($opt); + } + }}; + } + + return_if_nonempty_some!(Some(self.header_buffer_left_mut())); + return_if_nonempty_some!(self.message_fragment_left_mut()?); + Ok(None) + } + + pub fn advance(&mut self, count: usize) -> Result<(), SanityError> { + let off = self.off + count; + let msg_off = off.saturating_sub(HEADER_SIZE); + + use SanityError as E; + let alloc = self.message_buffer().len(); + let msgsz = self.message_size(); + ensure_or(msg_off <= alloc, E::OutOfBufferBounds)?; + ensure_or( + msgsz.map(|s| msg_off <= s).unwrap_or(true), + E::OutOfMessageBounds, + )?; + + self.off = off; + Ok(()) + } + + pub fn ensure_sufficient_msg_buffer(&self) -> Result<(), MessageTooLargeError> { + let buf_size = self.message_buffer().len(); + let msg_size = match self.get_header() { + None => return Ok(()), + Some(v) => v, + }; + MessageTooLargeError::ensure(msg_size, buf_size) + } + + pub fn header_buffer(&self) -> &[u8] { + &self.header[..] + } + + pub fn header_buffer_mut(&mut self) -> &mut [u8] { + &mut self.header[..] + } + + pub fn message_buffer(&self) -> &[u8] { + self.buf.borrow() + } + + pub fn message_buffer_mut(&mut self) -> &mut [u8] { + self.buf.borrow_mut() + } + + pub fn bytes_read(&self) -> &usize { + &self.off + } + + pub fn into_message_buffer(self) -> Buf { + let Self { buf, .. } = self; + buf + } + + pub fn header_buffer_offset(&self) -> usize { + min(self.off, HEADER_SIZE) + } + + pub fn message_buffer_offset(&self) -> usize { + self.off.saturating_sub(HEADER_SIZE) + } + + pub fn has_header(&self) -> bool { + self.header_buffer_offset() == HEADER_SIZE + } + + pub fn has_message(&self) -> Result { + self.ensure_sufficient_msg_buffer()?; + let msg_size = match self.get_header() { + None => return Ok(false), + Some(v) => v, + }; + Ok(self.message_buffer_avail().len() == msg_size) + } + + pub fn header_buffer_avail(&self) -> &[u8] { + let off = self.header_buffer_offset(); + &self.header_buffer()[..off] + } + + pub fn header_buffer_avail_mut(&mut self) -> &mut [u8] { + let off = self.header_buffer_offset(); + &mut self.header_buffer_mut()[..off] + } + + pub fn header_buffer_left(&self) -> &[u8] { + let off = self.header_buffer_offset(); + &self.header_buffer()[off..] + } + + pub fn header_buffer_left_mut(&mut self) -> &mut [u8] { + let off = self.header_buffer_offset(); + &mut self.header_buffer_mut()[off..] + } + + pub fn message_buffer_avail(&self) -> &[u8] { + let off = self.message_buffer_offset(); + &self.message_buffer()[..off] + } + + pub fn message_buffer_avail_mut(&mut self) -> &mut [u8] { + let off = self.message_buffer_offset(); + &mut self.message_buffer_mut()[..off] + } + + pub fn message_buffer_left(&self) -> &[u8] { + let off = self.message_buffer_offset(); + &self.message_buffer()[off..] + } + + pub fn message_buffer_left_mut(&mut self) -> &mut [u8] { + let off = self.message_buffer_offset(); + &mut self.message_buffer_mut()[off..] + } + + pub fn get_header(&self) -> Option { + match self.header_buffer_offset() == HEADER_SIZE { + false => None, + true => Some(u64::from_le_bytes(self.header) as usize), + } + } + + pub fn message_size(&self) -> Option { + self.get_header() + } + + pub fn encoded_message_bytes(&self) -> Option { + self.message_size().map(|sz| sz + HEADER_SIZE) + } + + pub fn message_fragment(&self) -> Result, MessageTooLargeError> { + self.ensure_sufficient_msg_buffer()?; + Ok(self.message_size().map(|sz| &self.message_buffer()[..sz])) + } + + pub fn message_fragment_mut(&mut self) -> Result, MessageTooLargeError> { + self.ensure_sufficient_msg_buffer()?; + Ok(self + .message_size() + .map(|sz| &mut self.message_buffer_mut()[..sz])) + } + + pub fn message_fragment_avail(&self) -> Result, MessageTooLargeError> { + let off = self.message_buffer_avail().len(); + self.message_fragment() + .map(|frag| frag.map(|frag| &frag[..off])) + } + + pub fn message_fragment_avail_mut( + &mut self, + ) -> Result, MessageTooLargeError> { + let off = self.message_buffer_avail().len(); + self.message_fragment_mut() + .map(|frag| frag.map(|frag| &mut frag[..off])) + } + + pub fn message_fragment_left(&self) -> Result, MessageTooLargeError> { + let off = self.message_buffer_avail().len(); + self.message_fragment() + .map(|frag| frag.map(|frag| &frag[off..])) + } + + pub fn message_fragment_left_mut(&mut self) -> Result, MessageTooLargeError> { + let off = self.message_buffer_avail().len(); + self.message_fragment_mut() + .map(|frag| frag.map(|frag| &mut frag[off..])) + } + + pub fn message(&self) -> Result, MessageTooLargeError> { + let sz = self.message_size(); + self.message_fragment_avail() + .map(|frag_opt| frag_opt.and_then(|frag| (frag.len() == sz?).then_some(frag))) + } + + pub fn message_mut(&mut self) -> Result, MessageTooLargeError> { + let sz = self.message_size(); + self.message_fragment_avail_mut() + .map(|frag_opt| frag_opt.and_then(|frag| (frag.len() == sz?).then_some(frag))) + } +} + +impl> Zeroize for LengthPrefixDecoder { + fn zeroize(&mut self) { + self.header.zeroize(); + self.message_buffer_mut().zeroize(); + self.off.zeroize(); + } +} diff --git a/util/src/length_prefix_encoding/encoder.rs b/util/src/length_prefix_encoding/encoder.rs new file mode 100644 index 00000000..a7f86b57 --- /dev/null +++ b/util/src/length_prefix_encoding/encoder.rs @@ -0,0 +1,381 @@ +use std::{ + borrow::{Borrow, BorrowMut}, + cmp::min, + io, +}; + +use thiserror::Error; +use zeroize::Zeroize; + +use crate::{io::IoResultKindHintExt, result::ensure_or}; + +pub const HEADER_SIZE: usize = std::mem::size_of::(); + +#[derive(Error, Debug, Clone, Copy)] +#[error("Write position is out of buffer bounds")] +pub struct PositionOutOfBufferBounds; + +#[derive(Error, Debug, Clone, Copy)] +#[error("Write position is out of message bounds")] +pub struct PositionOutOfMessageBounds; + +#[derive(Error, Debug, Clone, Copy)] +#[error("Write position is out of header bounds")] +pub struct PositionOutOfHeaderBounds; + +#[derive(Error, Debug, Clone, Copy)] +#[error("Message length is bigger than buffer length")] +pub struct MessageTooLarge; + +#[derive(Error, Debug, Clone, Copy)] +pub enum MessageLenSanityError { + #[error("{0:?}")] + PositionOutOfMessageBounds(#[from] PositionOutOfMessageBounds), + #[error("{0:?}")] + MessageTooLarge(#[from] MessageTooLarge), +} + +#[derive(Error, Debug, Clone, Copy)] +pub enum PositionSanityError { + #[error("{0:?}")] + PositionOutOfMessageBounds(#[from] PositionOutOfMessageBounds), + #[error("{0:?}")] + PositionOutOfBufferBounds(#[from] PositionOutOfBufferBounds), +} + +#[derive(Error, Debug, Clone, Copy)] +pub enum SanityError { + #[error("{0:?}")] + PositionOutOfMessageBounds(#[from] PositionOutOfMessageBounds), + #[error("{0:?}")] + PositionOutOfBufferBounds(#[from] PositionOutOfBufferBounds), + #[error("{0:?}")] + MessageTooLarge(#[from] MessageTooLarge), +} + +impl TryFrom for MessageLenSanityError { + type Error = PositionOutOfBufferBounds; + + fn try_from(value: SanityError) -> Result { + use {MessageLenSanityError as T, SanityError as F}; + match value { + F::PositionOutOfMessageBounds(e) => Ok(T::PositionOutOfMessageBounds(e)), + F::MessageTooLarge(e) => Ok(T::MessageTooLarge(e)), + F::PositionOutOfBufferBounds(e) => Err(e), + } + } +} + +impl From for SanityError { + fn from(value: MessageLenSanityError) -> Self { + use {MessageLenSanityError as F, SanityError as T}; + match value { + F::PositionOutOfMessageBounds(e) => T::PositionOutOfMessageBounds(e), + F::MessageTooLarge(e) => T::MessageTooLarge(e), + } + } +} + +impl From for SanityError { + fn from(value: PositionSanityError) -> Self { + use {PositionSanityError as F, SanityError as T}; + match value { + F::PositionOutOfBufferBounds(e) => T::PositionOutOfBufferBounds(e), + F::PositionOutOfMessageBounds(e) => T::PositionOutOfMessageBounds(e), + } + } +} + +pub struct WriteToIoReturn { + pub bytes_written: usize, + pub done: bool, +} + +#[derive(Clone, Copy, Debug)] +pub struct LengthPrefixEncoder> { + buf: Buf, + header: [u8; HEADER_SIZE], + pos: usize, +} + +impl> LengthPrefixEncoder { + pub fn from_buffer(buf: Buf) -> Self { + let (header, pos) = ([0u8; HEADER_SIZE], 0); + let mut r = Self { buf, header, pos }; + r.clear(); + r + } + + pub fn from_message(msg: Buf) -> Self { + let mut r = Self::from_buffer(msg); + r.restart_write_with_new_message(r.buffer_bytes().len()) + .unwrap(); + r + } + + pub fn from_short_message(msg: Buf, len: usize) -> Result { + let mut r = Self::from_message(msg); + r.set_message_len(len)?; + Ok(r) + } + + pub fn from_parts(buf: Buf, len: usize, pos: usize) -> Result { + let mut r = Self::from_buffer(buf); + r.set_msg_len_and_position(len, pos)?; + Ok(r) + } + + pub fn into_buffer(self) -> Buf { + let Self { buf, .. } = self; + buf + } + + pub fn into_parts(self) -> (Buf, usize, usize) { + let len = self.message_len(); + let pos = self.writing_position(); + let buf = self.into_buffer(); + (buf, len, pos) + } + + pub fn clear(&mut self) { + self.set_msg_len_and_position(0, 0).unwrap(); + self.set_message_offset(0).unwrap(); + } + + pub fn write_all_to_stdio(&mut self, mut w: W) -> io::Result<()> { + use io::ErrorKind as K; + loop { + match self.write_to_stdio(&mut w).io_err_kind_hint() { + // Done + Ok(WriteToIoReturn { done: true, .. }) => break Ok(()), + + // Retry + Ok(WriteToIoReturn { done: false, .. }) => continue, + Err((_, K::Interrupted)) => continue, + + Err((e, _)) => break Err(e), + } + } + } + + pub fn write_to_stdio(&mut self, mut w: W) -> io::Result { + if self.exhausted() { + return Ok(WriteToIoReturn { + bytes_written: 0, + done: true, + }); + } + + let buf = self.next_slice_to_write(); + let bytes_written = w.write(buf)?; + self.advance(bytes_written).unwrap(); + + let done = self.exhausted(); + Ok(WriteToIoReturn { + bytes_written, + done, + }) + } + + pub fn restart_write(&mut self) { + self.set_writing_position(0).unwrap() + } + + pub fn restart_write_with_new_message( + &mut self, + len: usize, + ) -> Result<(), MessageLenSanityError> { + self.set_msg_len_and_position(len, 0) + .map_err(|e| e.try_into().unwrap()) + } + + pub fn next_slice_to_write(&self) -> &[u8] { + let s = self.header_left(); + if !s.is_empty() { + return s; + } + + let s = self.message_left(); + if !s.is_empty() { + return s; + } + + &[] + } + + pub fn exhausted(&self) -> bool { + self.next_slice_to_write().is_empty() + } + + pub fn message(&self) -> &[u8] { + &self.buffer_bytes()[..self.message_len()] + } + + pub fn header_written(&self) -> &[u8] { + &self.header()[..self.header_offset()] + } + + pub fn header_left(&self) -> &[u8] { + &self.header()[self.header_offset()..] + } + + pub fn message_written(&self) -> &[u8] { + &self.message()[..self.message_offset()] + } + + pub fn message_left(&self) -> &[u8] { + &self.message()[self.message_offset()..] + } + + pub fn buf(&self) -> &Buf { + &self.buf + } + + pub fn buffer_bytes(&self) -> &[u8] { + self.buf().borrow() + } + + pub fn decode_header(&self) -> u64 { + u64::from_le_bytes(self.header) + } + + pub fn header(&self) -> &[u8; HEADER_SIZE] { + &self.header + } + + pub fn message_len(&self) -> usize { + self.decode_header() as usize + } + + pub fn encoded_message_bytes(&self) -> usize { + self.message_len() + HEADER_SIZE + } + + pub fn writing_position(&self) -> usize { + self.pos + } + + pub fn header_offset(&self) -> usize { + min(self.writing_position(), HEADER_SIZE) + } + + pub fn message_offset(&self) -> usize { + self.writing_position().saturating_sub(HEADER_SIZE) + } + + pub fn set_header(&mut self, header: [u8; HEADER_SIZE]) -> Result<(), MessageLenSanityError> { + self.offset_transaction(|t| { + t.header = header; + t.ensure_msg_in_buf_bounds()?; + t.ensure_pos_in_msg_bounds()?; + Ok(()) + }) + } + + pub fn encode_and_set_header(&mut self, header: u64) -> Result<(), MessageLenSanityError> { + self.set_header(header.to_le_bytes()) + } + + pub fn set_message_len(&mut self, len: usize) -> Result<(), MessageLenSanityError> { + self.encode_and_set_header(len as u64) + } + + pub fn set_writing_position(&mut self, pos: usize) -> Result<(), PositionSanityError> { + self.offset_transaction(|t| { + t.pos = pos; + t.ensure_pos_in_buf_bounds()?; + t.ensure_pos_in_msg_bounds()?; + Ok(()) + }) + } + + pub fn set_header_offset(&mut self, off: usize) -> Result<(), PositionOutOfHeaderBounds> { + ensure_or(off <= HEADER_SIZE, PositionOutOfHeaderBounds)?; + self.set_writing_position(off).unwrap(); + Ok(()) + } + + pub fn set_message_offset(&mut self, off: usize) -> Result<(), PositionSanityError> { + self.set_writing_position(off + HEADER_SIZE) + } + + pub fn advance(&mut self, off: usize) -> Result<(), PositionSanityError> { + self.set_writing_position(self.writing_position() + off) + } + + pub fn set_msg_len_and_position(&mut self, len: usize, pos: usize) -> Result<(), SanityError> { + self.pos = 0; + self.set_message_len(len)?; + self.set_writing_position(pos)?; + Ok(()) + } + + fn offset_transaction(&mut self, f: F) -> Result<(), E> + where + F: FnOnce(&mut LengthPrefixEncoder<&[u8]>) -> Result<(), E>, + { + let (header, pos) = { + let (buf, header, pos) = (self.buffer_bytes(), self.header, self.pos); + let mut tmp = LengthPrefixEncoder { buf, header, pos }; + f(&mut tmp)?; + Ok((tmp.header, tmp.pos)) + }?; + (self.header, self.pos) = (header, pos); + Ok(()) + } + + fn ensure_pos_in_buf_bounds(&self) -> Result<(), PositionOutOfBufferBounds> { + ensure_or( + self.message_offset() <= self.buffer_bytes().len(), + PositionOutOfBufferBounds, + ) + } + + fn ensure_pos_in_msg_bounds(&self) -> Result<(), PositionOutOfMessageBounds> { + ensure_or( + self.message_offset() <= self.message_len(), + PositionOutOfMessageBounds, + ) + } + + fn ensure_msg_in_buf_bounds(&self) -> Result<(), MessageTooLarge> { + ensure_or( + self.message_len() <= self.buffer_bytes().len(), + MessageTooLarge, + ) + } +} + +impl> LengthPrefixEncoder { + pub fn buf_mut(&mut self) -> &mut Buf { + &mut self.buf + } + + pub fn buffer_bytes_mut(&mut self) -> &mut [u8] { + self.buf.borrow_mut() + } + + pub fn message_mut(&mut self) -> &mut [u8] { + let off = self.message_len(); + &mut self.buffer_bytes_mut()[..off] + } + + pub fn message_written_mut(&mut self) -> &mut [u8] { + let off = self.message_offset(); + &mut self.message_mut()[..off] + } + + pub fn message_left_mut(&mut self) -> &mut [u8] { + let off = self.message_offset(); + &mut self.message_mut()[off..] + } +} + +impl> Zeroize for LengthPrefixEncoder { + fn zeroize(&mut self) { + self.buffer_bytes_mut().zeroize(); + self.header.zeroize(); + self.pos.zeroize(); + self.clear(); + } +} diff --git a/util/src/length_prefix_encoding/mod.rs b/util/src/length_prefix_encoding/mod.rs new file mode 100644 index 00000000..a4d5bf51 --- /dev/null +++ b/util/src/length_prefix_encoding/mod.rs @@ -0,0 +1,2 @@ +pub mod decoder; +pub mod encoder; diff --git a/util/src/lib.rs b/util/src/lib.rs index f16e98fa..c4d3f313 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -5,8 +5,12 @@ pub mod fd; pub mod file; pub mod functional; pub mod io; +pub mod length_prefix_encoding; pub mod mem; +pub mod mio; pub mod ord; pub mod result; pub mod time; pub mod typenum; +pub mod zerocopy; +pub mod zeroize; diff --git a/util/src/mio.rs b/util/src/mio.rs new file mode 100644 index 00000000..10757eec --- /dev/null +++ b/util/src/mio.rs @@ -0,0 +1,39 @@ +use mio::net::{UnixListener, UnixStream}; +use rustix::fd::RawFd; + +use crate::fd::claim_fd; + +pub mod interest { + use mio::Interest; + pub const R: Interest = Interest::READABLE; + pub const W: Interest = Interest::WRITABLE; + pub const RW: Interest = R.add(W); +} + +pub trait UnixListenerExt: Sized { + fn claim_fd(fd: RawFd) -> anyhow::Result; +} + +impl UnixListenerExt for UnixListener { + fn claim_fd(fd: RawFd) -> anyhow::Result { + use std::os::unix::net::UnixListener as StdUnixListener; + + let sock = StdUnixListener::from(claim_fd(fd)?); + sock.set_nonblocking(true)?; + Ok(UnixListener::from_std(sock)) + } +} + +pub trait UnixStreamExt: Sized { + fn claim_fd(fd: RawFd) -> anyhow::Result; +} + +impl UnixStreamExt for UnixStream { + fn claim_fd(fd: RawFd) -> anyhow::Result { + use std::os::unix::net::UnixStream as StdUnixStream; + + let sock = StdUnixStream::from(claim_fd(fd)?); + sock.set_nonblocking(true)?; + Ok(UnixStream::from_std(sock)) + } +} diff --git a/util/src/result.rs b/util/src/result.rs index 986ac6bd..76b60629 100644 --- a/util/src/result.rs +++ b/util/src/result.rs @@ -96,3 +96,14 @@ impl GuaranteedValue for Guaranteed { self.unwrap() } } + +pub fn ensure_or(b: bool, err: E) -> Result<(), E> { + match b { + true => Ok(()), + false => Err(err), + } +} + +pub fn bail_if(b: bool, err: E) -> Result<(), E> { + ensure_or(!b, err) +} diff --git a/util/src/zerocopy/mod.rs b/util/src/zerocopy/mod.rs new file mode 100644 index 00000000..6856bea3 --- /dev/null +++ b/util/src/zerocopy/mod.rs @@ -0,0 +1,7 @@ +mod ref_maker; +mod zerocopy_ref_ext; +mod zerocopy_slice_ext; + +pub use ref_maker::*; +pub use zerocopy_ref_ext::*; +pub use zerocopy_slice_ext::*; diff --git a/util/src/zerocopy/ref_maker.rs b/util/src/zerocopy/ref_maker.rs new file mode 100644 index 00000000..13e6fe96 --- /dev/null +++ b/util/src/zerocopy/ref_maker.rs @@ -0,0 +1,106 @@ +use std::marker::PhantomData; + +use anyhow::{ensure, Context}; +use zerocopy::{ByteSlice, ByteSliceMut, Ref}; +use zeroize::Zeroize; + +use crate::zeroize::ZeroizedExt; + +#[derive(Clone, Copy, Debug)] +pub struct RefMaker { + buf: B, + _phantom_t: PhantomData, +} + +impl RefMaker { + pub fn new(buf: B) -> Self { + let _phantom_t = PhantomData; + Self { buf, _phantom_t } + } + + pub const fn target_size() -> usize { + std::mem::size_of::() + } + + pub fn into_buf(self) -> B { + self.buf + } + + pub fn buf(&self) -> &B { + &self.buf + } + + pub fn buf_mut(&mut self) -> &mut B { + &mut self.buf + } +} + +impl RefMaker { + pub fn parse(self) -> anyhow::Result> { + self.ensure_fit()?; + Ref::::new(self.buf).context("Parser error!") + } + + pub fn from_prefix_with_tail(self) -> anyhow::Result<(Self, B)> { + self.ensure_fit()?; + let (head, tail) = self.buf.split_at(Self::target_size()); + Ok((Self::new(head), tail)) + } + + pub fn split_prefix(self) -> anyhow::Result<(Self, Self)> { + self.ensure_fit()?; + let (head, tail) = self.buf.split_at(Self::target_size()); + Ok((Self::new(head), Self::new(tail))) + } + + pub fn from_prefix(self) -> anyhow::Result { + Ok(Self::from_prefix_with_tail(self)?.0) + } + + pub fn from_suffix_with_tail(self) -> anyhow::Result<(Self, B)> { + self.ensure_fit()?; + let point = self.bytes().len() - Self::target_size(); + let (head, tail) = self.buf.split_at(point); + Ok((Self::new(head), tail)) + } + + pub fn split_suffix(self) -> anyhow::Result<(Self, Self)> { + self.ensure_fit()?; + let (head, tail) = self.buf.split_at(Self::target_size()); + Ok((Self::new(head), Self::new(tail))) + } + + pub fn from_suffix(self) -> anyhow::Result { + Ok(Self::from_suffix_with_tail(self)?.0) + } + + pub fn bytes(&self) -> &[u8] { + self.buf().deref() + } + + pub fn ensure_fit(&self) -> anyhow::Result<()> { + let have = self.bytes().len(); + let need = Self::target_size(); + ensure!( + need <= have, + "Buffer is undersized at {have} bytes (need {need} bytes)!" + ); + Ok(()) + } +} + +impl RefMaker { + pub fn make_zeroized(self) -> anyhow::Result> { + self.zeroized().parse() + } + + pub fn bytes_mut(&mut self) -> &mut [u8] { + self.buf_mut().deref_mut() + } +} + +impl Zeroize for RefMaker { + fn zeroize(&mut self) { + self.bytes_mut().zeroize() + } +} diff --git a/util/src/zerocopy/zerocopy_ref_ext.rs b/util/src/zerocopy/zerocopy_ref_ext.rs new file mode 100644 index 00000000..1acc52af --- /dev/null +++ b/util/src/zerocopy/zerocopy_ref_ext.rs @@ -0,0 +1,27 @@ +use zerocopy::{ByteSlice, ByteSliceMut, Ref}; + +pub trait ZerocopyEmancipateExt { + fn emancipate(&self) -> Ref<&[u8], T>; +} + +pub trait ZerocopyEmancipateMutExt { + fn emancipate_mut(&mut self) -> Ref<&mut [u8], T>; +} + +impl ZerocopyEmancipateExt for Ref +where + B: ByteSlice, +{ + fn emancipate(&self) -> Ref<&[u8], T> { + Ref::new(self.bytes()).unwrap() + } +} + +impl ZerocopyEmancipateMutExt for Ref +where + B: ByteSliceMut, +{ + fn emancipate_mut(&mut self) -> Ref<&mut [u8], T> { + Ref::new(self.bytes_mut()).unwrap() + } +} diff --git a/util/src/zerocopy/zerocopy_slice_ext.rs b/util/src/zerocopy/zerocopy_slice_ext.rs new file mode 100644 index 00000000..eb0000af --- /dev/null +++ b/util/src/zerocopy/zerocopy_slice_ext.rs @@ -0,0 +1,39 @@ +use zerocopy::{ByteSlice, ByteSliceMut, Ref}; + +use super::RefMaker; + +pub trait ZerocopySliceExt: Sized + ByteSlice { + fn zk_ref_maker(self) -> RefMaker { + RefMaker::::new(self) + } + + fn zk_parse(self) -> anyhow::Result> { + self.zk_ref_maker().parse() + } + + fn zk_parse_prefix(self) -> anyhow::Result> { + self.zk_ref_maker().from_prefix()?.parse() + } + + fn zk_parse_suffix(self) -> anyhow::Result> { + self.zk_ref_maker().from_prefix()?.parse() + } +} + +impl ZerocopySliceExt for B {} + +pub trait ZerocopyMutSliceExt: ZerocopySliceExt + Sized + ByteSliceMut { + fn zk_zeroized(self) -> anyhow::Result> { + self.zk_ref_maker().make_zeroized() + } + + fn zk_zeroized_from_prefix(self) -> anyhow::Result> { + self.zk_ref_maker().from_prefix()?.make_zeroized() + } + + fn zk_zeroized_from_suffic(self) -> anyhow::Result> { + self.zk_ref_maker().from_prefix()?.make_zeroized() + } +} + +impl ZerocopyMutSliceExt for B {} diff --git a/util/src/zeroize/mod.rs b/util/src/zeroize/mod.rs new file mode 100644 index 00000000..f1d37be1 --- /dev/null +++ b/util/src/zeroize/mod.rs @@ -0,0 +1,2 @@ +mod zeroized_ext; +pub use zeroized_ext::*; diff --git a/util/src/zeroize/zeroized_ext.rs b/util/src/zeroize/zeroized_ext.rs new file mode 100644 index 00000000..c4f87d5f --- /dev/null +++ b/util/src/zeroize/zeroized_ext.rs @@ -0,0 +1,10 @@ +use zeroize::Zeroize; + +pub trait ZeroizedExt: Zeroize + Sized { + fn zeroized(mut self) -> Self { + self.zeroize(); + self + } +} + +impl ZeroizedExt for T {} From 4bcd38a4ea033ec35a91faf2d07499fc4130cd59 Mon Sep 17 00:00:00 2001 From: Karolin Varner Date: Sat, 3 Aug 2024 16:51:18 +0200 Subject: [PATCH 19/21] feat: Infrastructure for the Rosenpass API --- Cargo.lock | 11 ++ Cargo.toml | 3 + rosenpass/Cargo.toml | 17 ++ .../src/api/boilerplate/byte_slice_ext.rs | 116 +++++++++++ .../src/api/boilerplate/message_trait.rs | 29 +++ rosenpass/src/api/boilerplate/message_type.rs | 116 +++++++++++ rosenpass/src/api/boilerplate/mod.rs | 17 ++ rosenpass/src/api/boilerplate/payload.rs | 96 ++++++++++ rosenpass/src/api/boilerplate/request_ref.rs | 107 +++++++++++ .../src/api/boilerplate/request_response.rs | 103 ++++++++++ rosenpass/src/api/boilerplate/response_ref.rs | 108 +++++++++++ rosenpass/src/api/boilerplate/server.rs | 40 ++++ rosenpass/src/api/cli.rs | 40 ++++ rosenpass/src/api/config.rs | 41 ++++ .../src/api/crypto_server_api_handler.rs | 44 +++++ rosenpass/src/api/mio/connection.rs | 167 ++++++++++++++++ rosenpass/src/api/mio/manager.rs | 91 +++++++++ rosenpass/src/api/mio/mod.rs | 5 + rosenpass/src/api/mod.rs | 9 + rosenpass/src/app_server.rs | 75 ++++++-- rosenpass/src/bin/gen-ipc-msg-types.rs | 86 +++++++++ rosenpass/src/cli.rs | 30 ++- rosenpass/src/config.rs | 16 +- rosenpass/src/lib.rs | 6 + rosenpass/tests/api-integration-tests.rs | 180 ++++++++++++++++++ 25 files changed, 1537 insertions(+), 16 deletions(-) create mode 100644 rosenpass/src/api/boilerplate/byte_slice_ext.rs create mode 100644 rosenpass/src/api/boilerplate/message_trait.rs create mode 100644 rosenpass/src/api/boilerplate/message_type.rs create mode 100644 rosenpass/src/api/boilerplate/mod.rs create mode 100644 rosenpass/src/api/boilerplate/payload.rs create mode 100644 rosenpass/src/api/boilerplate/request_ref.rs create mode 100644 rosenpass/src/api/boilerplate/request_response.rs create mode 100644 rosenpass/src/api/boilerplate/response_ref.rs create mode 100644 rosenpass/src/api/boilerplate/server.rs create mode 100644 rosenpass/src/api/cli.rs create mode 100644 rosenpass/src/api/config.rs create mode 100644 rosenpass/src/api/crypto_server_api_handler.rs create mode 100644 rosenpass/src/api/mio/connection.rs create mode 100644 rosenpass/src/api/mio/manager.rs create mode 100644 rosenpass/src/api/mio/mod.rs create mode 100644 rosenpass/src/api/mod.rs create mode 100644 rosenpass/src/bin/gen-ipc-msg-types.rs create mode 100644 rosenpass/tests/api-integration-tests.rs diff --git a/Cargo.lock b/Cargo.lock index 7f318b6e..bc92f880 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1074,6 +1074,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + [[package]] name = "home" version = "0.5.9" @@ -1930,6 +1936,9 @@ dependencies = [ "criterion", "derive_builder 0.20.0", "env_logger", + "heck", + "hex", + "hex-literal", "home", "log", "memoffset 0.9.1", @@ -1948,10 +1957,12 @@ dependencies = [ "serial_test", "stacker", "static_assertions", + "tempfile", "test_bin", "thiserror", "toml", "zerocopy", + "zeroize", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ead707c3..ab829ffd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,9 @@ derive_builder = "0.20.0" tokio = { version = "1.39", features = ["macros", "rt-multi-thread"] } postcard= {version = "1.0.8", features = ["alloc"]} libcrux = { version = "0.0.2-pre.2" } +hex-literal = { version = "0.4.1" } +hex = { version = "0.4.3" } +heck = { version = "0.5.0" } #Dev dependencies serial_test = "3.1.1" diff --git a/rosenpass/Cargo.toml b/rosenpass/Cargo.toml index e574ad5a..11cf7ecc 100644 --- a/rosenpass/Cargo.toml +++ b/rosenpass/Cargo.toml @@ -13,6 +13,15 @@ readme = "readme.md" name = "rosenpass" path = "src/main.rs" +[[bin]] +name = "rosenpass-gen-ipc-msg-types" +path = "src/bin/gen-ipc-msg-types.rs" +required-features = ["experiment_api", "internal_bin_gen_ipc_msg_types"] + +[[test]] +name = "api-integration-tests" +required-features = ["experiment_api", "internal_testing"] + [[bench]] name = "handshake" harness = false @@ -40,6 +49,10 @@ zerocopy = { workspace = true } home = { workspace = true } derive_builder = {workspace = true} rosenpass-wireguard-broker = {workspace = true} +zeroize = { workspace = true } +hex-literal = { workspace = true, optional = true } +hex = { workspace = true, optional = true } +heck = { workspace = true, optional = true } [build-dependencies] anyhow = { workspace = true } @@ -50,8 +63,12 @@ test_bin = { workspace = true } stacker = { workspace = true } serial_test = {workspace = true} procspawn = {workspace = true} +tempfile = { workspace = true } [features] enable_broker_api = ["rosenpass-wireguard-broker/enable_broker_api"] experiment_memfd_secret = [] experiment_libcrux = ["rosenpass-ciphers/experiment_libcrux"] +experiment_api = ["hex-literal"] +internal_testing = [] +internal_bin_gen_ipc_msg_types = ["hex", "heck"] diff --git a/rosenpass/src/api/boilerplate/byte_slice_ext.rs b/rosenpass/src/api/boilerplate/byte_slice_ext.rs new file mode 100644 index 00000000..dd20b49b --- /dev/null +++ b/rosenpass/src/api/boilerplate/byte_slice_ext.rs @@ -0,0 +1,116 @@ +use zerocopy::{ByteSlice, Ref}; + +use rosenpass_util::zerocopy::{RefMaker, ZerocopySliceExt}; + +use super::{ + PingRequest, PingResponse, RawMsgType, RefMakerRawMsgTypeExt, RequestMsgType, RequestRef, + ResponseMsgType, ResponseRef, +}; + +pub trait ByteSliceRefExt: ByteSlice { + fn msg_type_maker(self) -> RefMaker { + self.zk_ref_maker() + } + + fn msg_type(self) -> anyhow::Result> { + self.zk_parse() + } + + fn msg_type_from_prefix(self) -> anyhow::Result> { + self.zk_parse_prefix() + } + + fn msg_type_from_suffix(self) -> anyhow::Result> { + self.zk_parse_suffix() + } + + fn request_msg_type(self) -> anyhow::Result { + self.msg_type_maker().parse_request_msg_type() + } + + fn request_msg_type_from_prefix(self) -> anyhow::Result { + self.msg_type_maker() + .from_prefix()? + .parse_request_msg_type() + } + + fn request_msg_type_from_suffix(self) -> anyhow::Result { + self.msg_type_maker() + .from_suffix()? + .parse_request_msg_type() + } + + fn response_msg_type(self) -> anyhow::Result { + self.msg_type_maker().parse_response_msg_type() + } + + fn response_msg_type_from_prefix(self) -> anyhow::Result { + self.msg_type_maker() + .from_prefix()? + .parse_response_msg_type() + } + + fn response_msg_type_from_suffix(self) -> anyhow::Result { + self.msg_type_maker() + .from_suffix()? + .parse_response_msg_type() + } + + fn parse_request(self) -> anyhow::Result> { + RequestRef::parse(self) + } + + fn parse_request_from_prefix(self) -> anyhow::Result> { + RequestRef::parse_from_prefix(self) + } + + fn parse_request_from_suffix(self) -> anyhow::Result> { + RequestRef::parse_from_suffix(self) + } + + fn parse_response(self) -> anyhow::Result> { + ResponseRef::parse(self) + } + + fn parse_response_from_prefix(self) -> anyhow::Result> { + ResponseRef::parse_from_prefix(self) + } + + fn parse_response_from_suffix(self) -> anyhow::Result> { + ResponseRef::parse_from_suffix(self) + } + + fn ping_request_maker(self) -> RefMaker { + self.zk_ref_maker() + } + + fn ping_request(self) -> anyhow::Result> { + self.zk_parse() + } + + fn ping_request_from_prefix(self) -> anyhow::Result> { + self.zk_parse_prefix() + } + + fn ping_request_from_suffix(self) -> anyhow::Result> { + self.zk_parse_suffix() + } + + fn ping_response_maker(self) -> RefMaker { + self.zk_ref_maker() + } + + fn ping_response(self) -> anyhow::Result> { + self.zk_parse() + } + + fn ping_response_from_prefix(self) -> anyhow::Result> { + self.zk_parse_prefix() + } + + fn ping_response_from_suffix(self) -> anyhow::Result> { + self.zk_parse_suffix() + } +} + +impl ByteSliceRefExt for B {} diff --git a/rosenpass/src/api/boilerplate/message_trait.rs b/rosenpass/src/api/boilerplate/message_trait.rs new file mode 100644 index 00000000..de4105b7 --- /dev/null +++ b/rosenpass/src/api/boilerplate/message_trait.rs @@ -0,0 +1,29 @@ +use zerocopy::{ByteSliceMut, Ref}; + +use rosenpass_util::zerocopy::RefMaker; + +use super::RawMsgType; + +pub trait Message { + type Payload; + type MessageClass: Into; + const MESSAGE_TYPE: Self::MessageClass; + + fn from_payload(payload: Self::Payload) -> Self; + fn init(&mut self); + fn setup(buf: B) -> anyhow::Result>; +} + +pub trait ZerocopyResponseMakerSetupMessageExt { + fn setup_msg(self) -> anyhow::Result>; +} + +impl ZerocopyResponseMakerSetupMessageExt for RefMaker +where + B: ByteSliceMut, + T: Message, +{ + fn setup_msg(self) -> anyhow::Result> { + T::setup(self.into_buf()) + } +} diff --git a/rosenpass/src/api/boilerplate/message_type.rs b/rosenpass/src/api/boilerplate/message_type.rs new file mode 100644 index 00000000..8fca7922 --- /dev/null +++ b/rosenpass/src/api/boilerplate/message_type.rs @@ -0,0 +1,116 @@ +use hex_literal::hex; +use rosenpass_util::zerocopy::RefMaker; +use zerocopy::ByteSlice; + +use crate::RosenpassError::{self, InvalidApiMessageType}; + +pub type RawMsgType = u128; + +// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Ping Request +pub const PING_REQUEST: RawMsgType = + RawMsgType::from_le_bytes(hex!("2397 3ecc c441 704d 0b02 ea31 45d3 4999")); +// hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Ping Response +pub const PING_RESPONSE: RawMsgType = + RawMsgType::from_le_bytes(hex!("4ec7 f6f0 2bbc ba64 48f1 da14 c7cf 0260")); + +pub trait MessageAttributes { + fn message_size(&self) -> usize; +} + +#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)] +pub enum RequestMsgType { + Ping, +} + +#[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)] +pub enum ResponseMsgType { + Ping, +} + +impl MessageAttributes for RequestMsgType { + fn message_size(&self) -> usize { + match self { + Self::Ping => std::mem::size_of::(), + } + } +} + +impl MessageAttributes for ResponseMsgType { + fn message_size(&self) -> usize { + match self { + Self::Ping => std::mem::size_of::(), + } + } +} + +impl TryFrom for RequestMsgType { + type Error = RosenpassError; + + fn try_from(value: u128) -> Result { + use RequestMsgType as E; + Ok(match value { + self::PING_REQUEST => E::Ping, + _ => return Err(InvalidApiMessageType(value)), + }) + } +} + +impl From for RawMsgType { + fn from(val: RequestMsgType) -> Self { + use RequestMsgType as E; + match val { + E::Ping => self::PING_REQUEST, + } + } +} + +impl TryFrom for ResponseMsgType { + type Error = RosenpassError; + + fn try_from(value: u128) -> Result { + use ResponseMsgType as E; + Ok(match value { + self::PING_RESPONSE => E::Ping, + _ => return Err(InvalidApiMessageType(value)), + }) + } +} + +impl From for RawMsgType { + fn from(val: ResponseMsgType) -> Self { + use ResponseMsgType as E; + match val { + E::Ping => self::PING_RESPONSE, + } + } +} + +pub trait RawMsgTypeExt { + fn into_request_msg_type(self) -> Result; + fn into_response_msg_type(self) -> Result; +} + +impl RawMsgTypeExt for RawMsgType { + fn into_request_msg_type(self) -> Result { + self.try_into() + } + + fn into_response_msg_type(self) -> Result { + self.try_into() + } +} + +pub trait RefMakerRawMsgTypeExt { + fn parse_request_msg_type(self) -> anyhow::Result; + fn parse_response_msg_type(self) -> anyhow::Result; +} + +impl RefMakerRawMsgTypeExt for RefMaker { + fn parse_request_msg_type(self) -> anyhow::Result { + Ok(self.parse()?.read().try_into()?) + } + + fn parse_response_msg_type(self) -> anyhow::Result { + Ok(self.parse()?.read().try_into()?) + } +} diff --git a/rosenpass/src/api/boilerplate/mod.rs b/rosenpass/src/api/boilerplate/mod.rs new file mode 100644 index 00000000..d60f0757 --- /dev/null +++ b/rosenpass/src/api/boilerplate/mod.rs @@ -0,0 +1,17 @@ +mod byte_slice_ext; +mod message_trait; +mod message_type; +mod payload; +mod request_ref; +mod request_response; +mod response_ref; +mod server; + +pub use byte_slice_ext::*; +pub use message_trait::*; +pub use message_type::*; +pub use payload::*; +pub use request_ref::*; +pub use request_response::*; +pub use response_ref::*; +pub use server::*; diff --git a/rosenpass/src/api/boilerplate/payload.rs b/rosenpass/src/api/boilerplate/payload.rs new file mode 100644 index 00000000..7537ee5b --- /dev/null +++ b/rosenpass/src/api/boilerplate/payload.rs @@ -0,0 +1,96 @@ +use rosenpass_util::zerocopy::ZerocopyMutSliceExt; +use zerocopy::{AsBytes, ByteSliceMut, FromBytes, FromZeroes, Ref}; + +use super::{Message, RawMsgType, RequestMsgType, ResponseMsgType}; + +/// Size required to fit any message in binary form +pub const MAX_REQUEST_LEN: usize = 2500; // TODO fix this +pub const MAX_RESPONSE_LEN: usize = 2500; // TODO fix this + +#[repr(packed)] +#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)] +pub struct Envelope { + /// Which message this is + pub msg_type: RawMsgType, + /// The actual Paylod + pub payload: M, +} + +pub type RequestEnvelope = Envelope; +pub type ResponseEnvelope = Envelope; + +#[repr(packed)] +#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)] +pub struct PingRequestPayload { + /// Randomly generated connection id + pub echo: [u8; 256], +} + +pub type PingRequest = RequestEnvelope; + +impl PingRequest { + pub fn new(echo: [u8; 256]) -> Self { + Self::from_payload(PingRequestPayload { echo }) + } +} + +impl Message for PingRequest { + type Payload = PingRequestPayload; + type MessageClass = RequestMsgType; + const MESSAGE_TYPE: Self::MessageClass = RequestMsgType::Ping; + + fn from_payload(payload: Self::Payload) -> Self { + Self { + msg_type: Self::MESSAGE_TYPE.into(), + payload, + } + } + + fn setup(buf: B) -> anyhow::Result> { + let mut r: Ref = buf.zk_zeroized()?; + r.init(); + Ok(r) + } + + fn init(&mut self) { + self.msg_type = Self::MESSAGE_TYPE.into(); + } +} + +#[repr(packed)] +#[derive(Debug, Copy, Clone, Hash, AsBytes, FromBytes, FromZeroes, PartialEq, Eq)] +pub struct PingResponsePayload { + /// Randomly generated connection id + pub echo: [u8; 256], +} + +pub type PingResponse = ResponseEnvelope; + +impl PingResponse { + pub fn new(echo: [u8; 256]) -> Self { + Self::from_payload(PingResponsePayload { echo }) + } +} + +impl Message for PingResponse { + type Payload = PingResponsePayload; + type MessageClass = ResponseMsgType; + const MESSAGE_TYPE: Self::MessageClass = ResponseMsgType::Ping; + + fn from_payload(payload: Self::Payload) -> Self { + Self { + msg_type: Self::MESSAGE_TYPE.into(), + payload, + } + } + + fn setup(buf: B) -> anyhow::Result> { + let mut r: Ref = buf.zk_zeroized()?; + r.init(); + Ok(r) + } + + fn init(&mut self) { + self.msg_type = Self::MESSAGE_TYPE.into(); + } +} diff --git a/rosenpass/src/api/boilerplate/request_ref.rs b/rosenpass/src/api/boilerplate/request_ref.rs new file mode 100644 index 00000000..22f6bd1a --- /dev/null +++ b/rosenpass/src/api/boilerplate/request_ref.rs @@ -0,0 +1,107 @@ +use anyhow::ensure; + +use zerocopy::{ByteSlice, ByteSliceMut, Ref}; + +use super::{ByteSliceRefExt, MessageAttributes, PingRequest, RequestMsgType}; + +struct RequestRefMaker { + buf: B, + msg_type: RequestMsgType, +} + +impl RequestRef { + pub fn parse(buf: B) -> anyhow::Result { + RequestRefMaker::new(buf)?.parse() + } + + pub fn parse_from_prefix(buf: B) -> anyhow::Result { + RequestRefMaker::new(buf)?.from_prefix()?.parse() + } + + pub fn parse_from_suffix(buf: B) -> anyhow::Result { + RequestRefMaker::new(buf)?.from_suffix()?.parse() + } + + pub fn message_type(&self) -> RequestMsgType { + match self { + Self::Ping(_) => RequestMsgType::Ping, + } + } +} + +impl From> for RequestRef { + fn from(v: Ref) -> Self { + Self::Ping(v) + } +} + +impl RequestRefMaker { + fn new(buf: B) -> anyhow::Result { + let msg_type = buf.deref().request_msg_type_from_prefix()?; + Ok(Self { buf, msg_type }) + } + + fn target_size(&self) -> usize { + self.msg_type.message_size() + } + + fn parse(self) -> anyhow::Result> { + Ok(match self.msg_type { + RequestMsgType::Ping => RequestRef::Ping(self.buf.ping_request()?), + }) + } + + #[allow(clippy::wrong_self_convention)] + fn from_prefix(self) -> anyhow::Result { + self.ensure_fit()?; + let point = self.target_size(); + let Self { buf, msg_type } = self; + let (buf, _) = buf.split_at(point); + Ok(Self { buf, msg_type }) + } + + #[allow(clippy::wrong_self_convention)] + fn from_suffix(self) -> anyhow::Result { + self.ensure_fit()?; + let point = self.buf.len() - self.target_size(); + let Self { buf, msg_type } = self; + let (buf, _) = buf.split_at(point); + Ok(Self { buf, msg_type }) + } + + pub fn ensure_fit(&self) -> anyhow::Result<()> { + let have = self.buf.len(); + let need = self.target_size(); + ensure!( + need <= have, + "Buffer is undersized at {have} bytes (need {need} bytes)!" + ); + Ok(()) + } +} + +pub enum RequestRef { + Ping(Ref), +} + +impl RequestRef +where + B: ByteSlice, +{ + pub fn bytes(&self) -> &[u8] { + match self { + Self::Ping(r) => r.bytes(), + } + } +} + +impl RequestRef +where + B: ByteSliceMut, +{ + pub fn bytes_mut(&mut self) -> &[u8] { + match self { + Self::Ping(r) => r.bytes_mut(), + } + } +} diff --git a/rosenpass/src/api/boilerplate/request_response.rs b/rosenpass/src/api/boilerplate/request_response.rs new file mode 100644 index 00000000..5af7a205 --- /dev/null +++ b/rosenpass/src/api/boilerplate/request_response.rs @@ -0,0 +1,103 @@ +use rosenpass_util::zerocopy::{ + RefMaker, ZerocopyEmancipateExt, ZerocopyEmancipateMutExt, ZerocopySliceExt, +}; +use zerocopy::{ByteSlice, ByteSliceMut, Ref}; + +use super::{Message, PingRequest, PingResponse}; +use super::{RequestRef, ResponseRef, ZerocopyResponseMakerSetupMessageExt}; + +pub trait RequestMsg: Sized + Message { + type ResponseMsg: ResponseMsg; + + fn zk_response_maker(buf: B) -> RefMaker { + buf.zk_ref_maker() + } + + fn setup_response(buf: B) -> anyhow::Result> { + Self::zk_response_maker(buf).setup_msg() + } + + fn setup_response_from_prefix( + buf: B, + ) -> anyhow::Result> { + Self::zk_response_maker(buf).from_prefix()?.setup_msg() + } + + fn setup_response_from_suffix( + buf: B, + ) -> anyhow::Result> { + Self::zk_response_maker(buf).from_prefix()?.setup_msg() + } +} + +pub trait ResponseMsg: Message { + type RequestMsg: RequestMsg; +} + +impl RequestMsg for PingRequest { + type ResponseMsg = PingResponse; +} + +impl ResponseMsg for PingResponse { + type RequestMsg = PingRequest; +} + +pub type PingPair = (Ref, Ref); + +pub enum RequestResponsePair { + Ping(PingPair), +} + +impl From> for RequestResponsePair { + fn from(v: PingPair) -> Self { + RequestResponsePair::Ping(v) + } +} + +impl RequestResponsePair +where + B1: ByteSlice, + B2: ByteSlice, +{ + pub fn both(&self) -> (RequestRef<&[u8]>, ResponseRef<&[u8]>) { + match self { + Self::Ping((req, res)) => { + let req = RequestRef::Ping(req.emancipate()); + let res = ResponseRef::Ping(res.emancipate()); + (req, res) + } + } + } + + pub fn request(&self) -> RequestRef<&[u8]> { + self.both().0 + } + + pub fn response(&self) -> ResponseRef<&[u8]> { + self.both().1 + } +} + +impl RequestResponsePair +where + B1: ByteSliceMut, + B2: ByteSliceMut, +{ + pub fn both_mut(&mut self) -> (RequestRef<&mut [u8]>, ResponseRef<&mut [u8]>) { + match self { + Self::Ping((req, res)) => { + let req = RequestRef::Ping(req.emancipate_mut()); + let res = ResponseRef::Ping(res.emancipate_mut()); + (req, res) + } + } + } + + pub fn request_mut(&mut self) -> RequestRef<&mut [u8]> { + self.both_mut().0 + } + + pub fn response_mut(&mut self) -> ResponseRef<&mut [u8]> { + self.both_mut().1 + } +} diff --git a/rosenpass/src/api/boilerplate/response_ref.rs b/rosenpass/src/api/boilerplate/response_ref.rs new file mode 100644 index 00000000..38b1e751 --- /dev/null +++ b/rosenpass/src/api/boilerplate/response_ref.rs @@ -0,0 +1,108 @@ +// TODO: This is copied verbatim from ResponseRef…not pretty +use anyhow::ensure; + +use zerocopy::{ByteSlice, ByteSliceMut, Ref}; + +use super::{ByteSliceRefExt, MessageAttributes, PingResponse, ResponseMsgType}; + +struct ResponseRefMaker { + buf: B, + msg_type: ResponseMsgType, +} + +impl ResponseRef { + pub fn parse(buf: B) -> anyhow::Result { + ResponseRefMaker::new(buf)?.parse() + } + + pub fn parse_from_prefix(buf: B) -> anyhow::Result { + ResponseRefMaker::new(buf)?.from_prefix()?.parse() + } + + pub fn parse_from_suffix(buf: B) -> anyhow::Result { + ResponseRefMaker::new(buf)?.from_suffix()?.parse() + } + + pub fn message_type(&self) -> ResponseMsgType { + match self { + Self::Ping(_) => ResponseMsgType::Ping, + } + } +} + +impl From> for ResponseRef { + fn from(v: Ref) -> Self { + Self::Ping(v) + } +} + +impl ResponseRefMaker { + fn new(buf: B) -> anyhow::Result { + let msg_type = buf.deref().response_msg_type_from_prefix()?; + Ok(Self { buf, msg_type }) + } + + fn target_size(&self) -> usize { + self.msg_type.message_size() + } + + fn parse(self) -> anyhow::Result> { + Ok(match self.msg_type { + ResponseMsgType::Ping => ResponseRef::Ping(self.buf.ping_response()?), + }) + } + + #[allow(clippy::wrong_self_convention)] + fn from_prefix(self) -> anyhow::Result { + self.ensure_fit()?; + let point = self.target_size(); + let Self { buf, msg_type } = self; + let (buf, _) = buf.split_at(point); + Ok(Self { buf, msg_type }) + } + + #[allow(clippy::wrong_self_convention)] + fn from_suffix(self) -> anyhow::Result { + self.ensure_fit()?; + let point = self.buf.len() - self.target_size(); + let Self { buf, msg_type } = self; + let (buf, _) = buf.split_at(point); + Ok(Self { buf, msg_type }) + } + + pub fn ensure_fit(&self) -> anyhow::Result<()> { + let have = self.buf.len(); + let need = self.target_size(); + ensure!( + need <= have, + "Buffer is undersized at {have} bytes (need {need} bytes)!" + ); + Ok(()) + } +} + +pub enum ResponseRef { + Ping(Ref), +} + +impl ResponseRef +where + B: ByteSlice, +{ + pub fn bytes(&self) -> &[u8] { + match self { + Self::Ping(r) => r.bytes(), + } + } +} + +impl ResponseRef +where + B: ByteSliceMut, +{ + pub fn bytes_mut(&mut self) -> &[u8] { + match self { + Self::Ping(r) => r.bytes_mut(), + } + } +} diff --git a/rosenpass/src/api/boilerplate/server.rs b/rosenpass/src/api/boilerplate/server.rs new file mode 100644 index 00000000..d4e42db9 --- /dev/null +++ b/rosenpass/src/api/boilerplate/server.rs @@ -0,0 +1,40 @@ +use zerocopy::{ByteSlice, ByteSliceMut}; + +use super::{ByteSliceRefExt, Message, PingRequest, PingResponse, RequestRef, RequestResponsePair}; + +pub trait Server { + fn ping(&mut self, req: &PingRequest, res: &mut PingResponse) -> anyhow::Result<()>; + + fn dispatch( + &mut self, + p: &mut RequestResponsePair, + ) -> anyhow::Result<()> + where + ReqBuf: ByteSlice, + ResBuf: ByteSliceMut, + { + match p { + RequestResponsePair::Ping((req, res)) => self.ping(req, res), + } + } + + fn handle_message(&mut self, req: ReqBuf, res: ResBuf) -> anyhow::Result + where + ReqBuf: ByteSlice, + ResBuf: ByteSliceMut, + { + let req = req.parse_request_from_prefix()?; + // TODO: This is not pretty; This match should be moved into RequestRef + let mut pair = match req { + RequestRef::Ping(req) => { + let mut res = res.ping_response_from_prefix()?; + res.init(); + RequestResponsePair::Ping((req, res)) + } + }; + self.dispatch(&mut pair)?; + + let res_len = pair.request().bytes().len(); + Ok(res_len) + } +} diff --git a/rosenpass/src/api/cli.rs b/rosenpass/src/api/cli.rs new file mode 100644 index 00000000..b160a5c5 --- /dev/null +++ b/rosenpass/src/api/cli.rs @@ -0,0 +1,40 @@ +use std::path::PathBuf; + +use clap::Args; + +use crate::config::Rosenpass as RosenpassConfig; + +use super::config::ApiConfig; + +#[cfg(feature = "experiment_api")] +#[derive(Args, Debug)] +pub struct ApiCli { + /// Where in the file-system to create the unix socket the rosenpass API will be listening for + /// connections on + #[arg(long)] + api_listen_path: Vec, + + /// When rosenpass is called from another process, the other process can open and bind the + /// unix socket for the Rosenpass API to use themselves, passing it to this process. In Rust this can be achieved + /// using the [command-fds](https://docs.rs/command-fds/latest/command_fds/) crate. + #[arg(long)] + api_listen_fd: Vec, + + /// When rosenpass is called from another process, the other process can connect the unix socket for the API + /// themselves, for instance using the `socketpair(2)` system call. + #[arg(long)] + api_stream_fd: Vec, +} + +impl ApiCli { + pub fn apply_to_config(&self, cfg: &mut RosenpassConfig) -> anyhow::Result<()> { + self.apply_to_api_config(&mut cfg.api) + } + + pub fn apply_to_api_config(&self, cfg: &mut ApiConfig) -> anyhow::Result<()> { + cfg.listen_path.extend_from_slice(&self.api_listen_path); + cfg.listen_fd.extend_from_slice(&self.api_listen_fd); + cfg.stream_fd.extend_from_slice(&self.api_stream_fd); + Ok(()) + } +} diff --git a/rosenpass/src/api/config.rs b/rosenpass/src/api/config.rs new file mode 100644 index 00000000..0a410a4d --- /dev/null +++ b/rosenpass/src/api/config.rs @@ -0,0 +1,41 @@ +use std::path::PathBuf; + +use mio::net::UnixListener; +use rosenpass_util::mio::{UnixListenerExt, UnixStreamExt}; +use serde::{Deserialize, Serialize}; + +use crate::app_server::AppServer; + +#[derive(Debug, Serialize, Deserialize, Default, Clone)] +pub struct ApiConfig { + /// Where in the file-system to create the unix socket the rosenpass API will be listening for + /// connections on + pub listen_path: Vec, + + /// When rosenpass is called from another process, the other process can open and bind the + /// unix socket for the Rosenpass API to use themselves, passing it to this process. In Rust this can be achieved + /// using the [command-fds](https://docs.rs/command-fds/latest/command_fds/) crate. + pub listen_fd: Vec, + + /// When rosenpass is called from another process, the other process can connect the unix socket for the API + /// themselves, for instance using the `socketpair(2)` system call. + pub stream_fd: Vec, +} + +impl ApiConfig { + pub fn apply_to_app_server(&self, srv: &mut AppServer) -> anyhow::Result<()> { + for path in self.listen_path.iter() { + srv.add_api_listener(UnixListener::bind(path)?)?; + } + + for fd in self.listen_fd.iter() { + srv.add_api_listener(UnixListenerExt::claim_fd(*fd)?)?; + } + + for fd in self.stream_fd.iter() { + srv.add_api_connection(UnixStreamExt::claim_fd(*fd)?)?; + } + + Ok(()) + } +} diff --git a/rosenpass/src/api/crypto_server_api_handler.rs b/rosenpass/src/api/crypto_server_api_handler.rs new file mode 100644 index 00000000..003f1d86 --- /dev/null +++ b/rosenpass/src/api/crypto_server_api_handler.rs @@ -0,0 +1,44 @@ +use rosenpass_to::{ops::copy_slice, To}; + +use crate::protocol::CryptoServer; + +use super::Server as ApiServer; + +#[derive(Debug)] +pub struct CryptoServerApiState { + _dummy: (), +} + +impl CryptoServerApiState { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Self { _dummy: () } + } + + pub fn acquire_backend<'a>( + &'a mut self, + crypto: &'a mut Option, + ) -> CryptoServerApiHandler<'a> { + let state = self; + CryptoServerApiHandler { state, crypto } + } +} + +pub struct CryptoServerApiHandler<'a> { + #[allow(unused)] // TODO: Remove + crypto: &'a mut Option, + #[allow(unused)] // TODO: Remove + state: &'a mut CryptoServerApiState, +} + +impl<'a> ApiServer for CryptoServerApiHandler<'a> { + fn ping( + &mut self, + req: &super::PingRequest, + res: &mut super::PingResponse, + ) -> anyhow::Result<()> { + let (req, res) = (&req.payload, &mut res.payload); + copy_slice(&req.echo).to(&mut res.echo); + Ok(()) + } +} diff --git a/rosenpass/src/api/mio/connection.rs b/rosenpass/src/api/mio/connection.rs new file mode 100644 index 00000000..e2d196a4 --- /dev/null +++ b/rosenpass/src/api/mio/connection.rs @@ -0,0 +1,167 @@ +use mio::{net::UnixStream, Interest}; +use rosenpass_util::{ + io::{IoResultKindHintExt, TryIoResultKindHintExt}, + length_prefix_encoding::{ + decoder::{self as lpe_decoder, LengthPrefixDecoder}, + encoder::{self as lpe_encoder, LengthPrefixEncoder}, + }, +}; +use zeroize::Zeroize; + +use crate::{api::Server, app_server::MioTokenDispenser, protocol::CryptoServer}; + +use super::super::{CryptoServerApiState, MAX_REQUEST_LEN, MAX_RESPONSE_LEN}; + +#[derive(Debug)] +pub struct MioConnection { + io: UnixStream, + invalid_read: bool, + read_buffer: LengthPrefixDecoder<[u8; MAX_REQUEST_LEN]>, + write_buffer: LengthPrefixEncoder<[u8; MAX_RESPONSE_LEN]>, + api_state: CryptoServerApiState, +} + +impl MioConnection { + pub fn new( + mut io: UnixStream, + registry: &mio::Registry, + token_dispenser: &mut MioTokenDispenser, // TODO: We should actually start using tokens… + ) -> std::io::Result { + registry.register( + &mut io, + token_dispenser.dispense(), + Interest::READABLE | Interest::WRITABLE, + )?; + + let invalid_read = false; + let read_buffer = LengthPrefixDecoder::new([0u8; MAX_REQUEST_LEN]); + let write_buffer = LengthPrefixEncoder::from_buffer([0u8; MAX_RESPONSE_LEN]); + let api_state = CryptoServerApiState::new(); + Ok(Self { + io, + invalid_read, + read_buffer, + write_buffer, + api_state, + }) + } + + pub fn poll(&mut self, crypto: &mut Option) -> anyhow::Result<()> { + self.flush_write_buffer()?; + if self.write_buffer.exhausted() { + self.recv(crypto)?; + } + Ok(()) + } + + // This is *exclusively* called by recv if the read_buffer holds a message + fn handle_incoming_message(&mut self, crypto: &mut Option) -> anyhow::Result<()> { + // Unwrap is allowed because recv() confirms before the call that a message was + // received + let req = self.read_buffer.message().unwrap().unwrap(); + + // TODO: The API should not return anyhow::Result + let response_len = self + .api_state + .acquire_backend(crypto) + .handle_message(req, self.write_buffer.buffer_bytes_mut())?; + self.read_buffer.zeroize(); // clear for new message to read + self.write_buffer + .restart_write_with_new_message(response_len)?; + + self.flush_write_buffer()?; + Ok(()) + } + + fn flush_write_buffer(&mut self) -> anyhow::Result<()> { + if self.write_buffer.exhausted() { + return Ok(()); + } + + loop { + use lpe_encoder::WriteToIoReturn as Ret; + use std::io::ErrorKind as K; + + match self + .write_buffer + .write_to_stdio(&self.io) + .io_err_kind_hint() + { + // Done + Ok(Ret { done: true, .. }) => { + self.write_buffer.zeroize(); // clear for new message to write + break; + } + + // Would block + Ok(Ret { + bytes_written: 0, .. + }) => break, + Err((_e, K::WouldBlock)) => break, + + // Just continue + Ok(_) => continue, /* Ret { bytes_written > 0, done = false } acc. to previous cases*/ + Err((_e, K::Interrupted)) => continue, + + // Other errors + Err((e, _ek)) => Err(e)?, + } + } + + Ok(()) + } + + fn recv(&mut self, crypto: &mut Option) -> anyhow::Result<()> { + if !self.write_buffer.exhausted() || self.invalid_read { + return Ok(()); + } + + loop { + use lpe_decoder::{ReadFromIoError as E, ReadFromIoReturn as Ret}; + use std::io::ErrorKind as K; + + match self + .read_buffer + .read_from_stdio(&self.io) + .try_io_err_kind_hint() + { + // We actually received a proper message + // (Impl below match to appease borrow checker) + Ok(Ret { + message: Some(_msg), + .. + }) => {} + + // Message does not fit in buffer + Err((e @ E::MessageTooLargeError { .. }, _)) => { + log::warn!("Received message on API that was too big to fit in our buffers; \ + looks like the client is broken. Stopping to process messages of the client.\n\ + Error: {e:?}"); + // TODO: We should properly close down the socket in this case, but to do that, + // we need to have the facilities in the Rosenpass IO handling system to close + // open connections. + // Just leaving the API connections dangling for now. + // This should be fixed for non-experimental use of the API. + self.invalid_read = true; + break; + } + + // Would block + Ok(Ret { bytes_read: 0, .. }) => break, + Err((_, Some(K::WouldBlock))) => break, + + // Just keep going + Ok(Ret { bytes_read: _, .. }) => continue, + Err((_, Some(K::Interrupted))) => continue, + + // Other IO Error (just pass on to the caller) + Err((E::IoError(e), _)) => Err(e)?, + }; + + self.handle_incoming_message(crypto)?; + break; // Handle just one message, leave some room for other IO handlers + } + + Ok(()) + } +} diff --git a/rosenpass/src/api/mio/manager.rs b/rosenpass/src/api/mio/manager.rs new file mode 100644 index 00000000..172addf3 --- /dev/null +++ b/rosenpass/src/api/mio/manager.rs @@ -0,0 +1,91 @@ +use std::io; + +use mio::net::{UnixListener, UnixStream}; + +use rosenpass_util::{io::nonblocking_handle_io_errors, mio::interest::RW as MIO_RW}; + +use crate::{app_server::MioTokenDispenser, protocol::CryptoServer}; + +use super::MioConnection; + +#[derive(Default, Debug)] +pub struct MioManager { + listeners: Vec, + connections: Vec, +} + +impl MioManager { + pub fn new() -> Self { + Self::default() + } + + pub fn add_listener( + &mut self, + mut listener: UnixListener, + registry: &mio::Registry, + token_dispenser: &mut MioTokenDispenser, + ) -> io::Result<()> { + registry.register(&mut listener, token_dispenser.dispense(), MIO_RW)?; + self.listeners.push(listener); + Ok(()) + } + + pub fn add_connection( + &mut self, + connection: UnixStream, + registry: &mio::Registry, + token_dispenser: &mut MioTokenDispenser, + ) -> io::Result<()> { + let connection = MioConnection::new(connection, registry, token_dispenser)?; + self.connections.push(connection); + Ok(()) + } + + pub fn poll( + &mut self, + crypto: &mut Option, + registry: &mio::Registry, + token_dispenser: &mut MioTokenDispenser, + ) -> anyhow::Result<()> { + self.accept_connections(registry, token_dispenser)?; + self.poll_connections(crypto)?; + Ok(()) + } + + fn accept_connections( + &mut self, + registry: &mio::Registry, + token_dispenser: &mut MioTokenDispenser, + ) -> io::Result<()> { + for idx in 0..self.listeners.len() { + self.accept_from(idx, registry, token_dispenser)?; + } + Ok(()) + } + + fn accept_from( + &mut self, + idx: usize, + registry: &mio::Registry, + token_dispenser: &mut MioTokenDispenser, + ) -> io::Result<()> { + // Accept connection until the socket would block or returns another error + loop { + match nonblocking_handle_io_errors(|| self.listeners[idx].accept())? { + None => break, + Some((conn, _addr)) => { + self.add_connection(conn, registry, token_dispenser)?; + } + }; + } + + Ok(()) + } + + fn poll_connections(&mut self, crypto: &mut Option) -> anyhow::Result<()> { + for conn in self.connections.iter_mut() { + conn.poll(crypto)? + } + Ok(()) + } +} diff --git a/rosenpass/src/api/mio/mod.rs b/rosenpass/src/api/mio/mod.rs new file mode 100644 index 00000000..c284c8b3 --- /dev/null +++ b/rosenpass/src/api/mio/mod.rs @@ -0,0 +1,5 @@ +mod connection; +mod manager; + +pub use connection::*; +pub use manager::*; diff --git a/rosenpass/src/api/mod.rs b/rosenpass/src/api/mod.rs new file mode 100644 index 00000000..9c9781dc --- /dev/null +++ b/rosenpass/src/api/mod.rs @@ -0,0 +1,9 @@ +mod boilerplate; +mod crypto_server_api_handler; + +pub use boilerplate::*; +pub use crypto_server_api_handler::*; + +pub mod cli; +pub mod config; +pub mod mio; diff --git a/rosenpass/src/app_server.rs b/rosenpass/src/app_server.rs index 55ceeeab..15353bc7 100644 --- a/rosenpass/src/app_server.rs +++ b/rosenpass/src/app_server.rs @@ -1,5 +1,6 @@ use anyhow::bail; +use anyhow::Context; use anyhow::Result; use derive_builder::Builder; use log::{error, info, warn}; @@ -65,7 +66,7 @@ pub struct MioTokenDispenser { } impl MioTokenDispenser { - fn dispense(&mut self) -> Token { + pub fn dispense(&mut self) -> Token { let r = self.counter; self.counter += 1; Token(r) @@ -146,7 +147,7 @@ pub struct AppServerTest { // TODO add user control via unix domain socket and stdin/stdout #[derive(Debug)] pub struct AppServer { - pub crypt: CryptoServer, + pub crypt: Option, pub sockets: Vec, pub events: mio::Events, pub mio_poll: mio::Poll, @@ -161,6 +162,8 @@ pub struct AppServer { pub unpolled_count: usize, pub last_update_time: Instant, pub test_helpers: Option, + #[cfg(feature = "experiment_api")] + pub api_manager: crate::api::mio::MioManager, } /// A socket pointer is an index assigned to a socket; @@ -603,7 +606,7 @@ impl AppServer { // TODO use mio::net::UnixStream together with std::os::unix::net::UnixStream for Linux Ok(Self { - crypt: CryptoServer::new(sk, pk), + crypt: Some(CryptoServer::new(sk, pk)), peers: Vec::new(), verbosity, sockets, @@ -618,9 +621,23 @@ impl AppServer { unpolled_count: 0, last_update_time: Instant::now(), test_helpers, + #[cfg(feature = "experiment_api")] + api_manager: crate::api::mio::MioManager::default(), }) } + pub fn crypto_server(&self) -> anyhow::Result<&CryptoServer> { + self.crypt + .as_ref() + .context("Cryptography handler not initialized") + } + + pub fn crypto_server_mut(&mut self) -> anyhow::Result<&mut CryptoServer> { + self.crypt + .as_mut() + .context("Cryptography handler not initialized") + } + pub fn verbose(&self) -> bool { matches!(self.verbosity, Verbosity::Verbose) } @@ -671,7 +688,7 @@ impl AppServer { broker_peer: Option, hostname: Option, ) -> anyhow::Result { - let PeerPtr(pn) = self.crypt.add_peer(psk, pk)?; + let PeerPtr(pn) = self.crypto_server_mut()?.add_peer(psk, pk)?; assert!(pn == self.peers.len()); let initial_endpoint = hostname .map(Endpoint::discovery_from_hostname) @@ -714,7 +731,7 @@ impl AppServer { ); if tries_left > 0 { error!("re-initializing networking in {sleep}! {tries_left} tries left."); - std::thread::sleep(self.crypt.timebase.dur(sleep)); + std::thread::sleep(self.crypto_server_mut()?.timebase.dur(sleep)); continue; } @@ -760,11 +777,11 @@ impl AppServer { match self.poll(&mut *rx)? { #[allow(clippy::redundant_closure_call)] SendInitiation(peer) => tx_maybe_with!(peer, || self - .crypt + .crypto_server_mut()? .initiate_handshake(peer.lower(), &mut *tx))?, #[allow(clippy::redundant_closure_call)] SendRetransmission(peer) => tx_maybe_with!(peer, || self - .crypt + .crypto_server_mut()? .retransmit_handshake(peer.lower(), &mut *tx))?, DeleteKey(peer) => { self.output_key(peer, Stale, &SymKey::random())?; @@ -785,7 +802,9 @@ impl AppServer { DoSOperation::UnderLoad => { self.handle_msg_under_load(&endpoint, &rx[..len], &mut *tx) } - DoSOperation::Normal => self.crypt.handle_msg(&rx[..len], &mut *tx), + DoSOperation::Normal => { + self.crypto_server_mut()?.handle_msg(&rx[..len], &mut *tx) + } }; match msg_result { Err(ref e) => { @@ -813,7 +832,8 @@ impl AppServer { ap.get_app_mut(self).current_endpoint = Some(endpoint); // TODO: Maybe we should rather call the key "rosenpass output"? - self.output_key(ap, Exchanged, &self.crypt.osk(p)?)?; + let osk = &self.crypto_server_mut()?.osk(p)?; + self.output_key(ap, Exchanged, osk)?; } } } @@ -829,9 +849,9 @@ impl AppServer { tx: &mut [u8], ) -> Result { match endpoint { - Endpoint::SocketBoundAddress(socket) => { - self.crypt.handle_msg_under_load(rx, &mut *tx, socket) - } + Endpoint::SocketBoundAddress(socket) => self + .crypto_server_mut()? + .handle_msg_under_load(rx, &mut *tx, socket), Endpoint::Discovery(_) => { anyhow::bail!("Host-path discovery is not supported under load") } @@ -844,7 +864,7 @@ impl AppServer { why: KeyOutputReason, key: &SymKey, ) -> anyhow::Result<()> { - let peerid = peer.lower().get(&self.crypt).pidt()?; + let peerid = peer.lower().get(self.crypto_server()?).pidt()?; if self.verbose() { let msg = match why { @@ -891,7 +911,7 @@ impl AppServer { use crate::protocol::PollResult as C; use AppPollResult as A; loop { - return Ok(match self.crypt.poll()? { + return Ok(match self.crypto_server_mut()?.poll()? { C::DeleteKey(PeerPtr(no)) => A::DeleteKey(AppPeerPtr(no)), C::SendInitiation(PeerPtr(no)) => A::SendInitiation(AppPeerPtr(no)), C::SendRetransmission(PeerPtr(no)) => A::SendRetransmission(AppPeerPtr(no)), @@ -1019,6 +1039,33 @@ impl AppServer { broker.process_poll()?; } + // API poll + + #[cfg(feature = "experiment_api")] + self.api_manager.poll( + &mut self.crypt, + self.mio_poll.registry(), + &mut self.mio_token_dispenser, + )?; + Ok(None) } + + #[cfg(feature = "experiment_api")] + pub fn add_api_connection(&mut self, connection: mio::net::UnixStream) -> std::io::Result<()> { + self.api_manager.add_connection( + connection, + self.mio_poll.registry(), + &mut self.mio_token_dispenser, + ) + } + + #[cfg(feature = "experiment_api")] + pub fn add_api_listener(&mut self, listener: mio::net::UnixListener) -> std::io::Result<()> { + self.api_manager.add_listener( + listener, + self.mio_poll.registry(), + &mut self.mio_token_dispenser, + ) + } } diff --git a/rosenpass/src/bin/gen-ipc-msg-types.rs b/rosenpass/src/bin/gen-ipc-msg-types.rs new file mode 100644 index 00000000..2e128de6 --- /dev/null +++ b/rosenpass/src/bin/gen-ipc-msg-types.rs @@ -0,0 +1,86 @@ +use anyhow::{Context, Result}; +use heck::ToShoutySnakeCase; + +use rosenpass_ciphers::{hash_domain::HashDomain, KEY_LEN}; + +fn calculate_hash_value(hd: HashDomain, values: &[&str]) -> Result<[u8; KEY_LEN]> { + match values.split_first() { + Some((head, tail)) => calculate_hash_value(hd.mix(head.as_bytes())?, tail), + None => Ok(hd.into_value()), + } +} + +fn print_literal(path: &[&str]) -> Result<()> { + let val = calculate_hash_value(HashDomain::zero(), path)?; + let (last, prefix) = path.split_last().context("developer error!")?; + let var_name = last.to_shouty_snake_case(); + + print!("// hash domain hash of: "); + for n in prefix.iter() { + print!("{n} -> "); + } + println!("{last}"); + + let c = hex::encode(val) + .chars() + .collect::>() + .chunks_exact(4) + .map(|chunk| chunk.iter().collect::()) + .collect::>(); + println!("const {var_name} : RawMsgType = RawMsgType::from_le_bytes(hex!(\"{} {} {} {} {} {} {} {}\"));", + c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7]); + + Ok(()) +} + +#[derive(Debug, Clone)] +enum Tree { + Branch(String, Vec), + Leaf(String), +} + +impl Tree { + fn name(&self) -> &str { + match self { + Self::Branch(name, _) => name, + Self::Leaf(name) => name, + } + } + + fn gen_code_inner(&self, prefix: &[&str]) -> Result<()> { + let mut path = prefix.to_owned(); + path.push(self.name()); + + match self { + Self::Branch(_, ref children) => { + for c in children.iter() { + c.gen_code_inner(&path)? + } + } + Self::Leaf(_) => print_literal(&path)?, + }; + + Ok(()) + } + + fn gen_code(&self) -> Result<()> { + self.gen_code_inner(&[]) + } +} + +fn main() -> Result<()> { + let tree = Tree::Branch( + "Rosenpass IPC API".to_owned(), + vec![Tree::Branch( + "Rosenpass Protocol Server".to_owned(), + vec![ + Tree::Leaf("Ping Request".to_owned()), + Tree::Leaf("Ping Response".to_owned()), + ], + )], + ); + + println!("type RawMsgType = u128;"); + println!(); + tree.gen_code() +} diff --git a/rosenpass/src/cli.rs b/rosenpass/src/cli.rs index 473b30f2..fae2f88b 100644 --- a/rosenpass/src/cli.rs +++ b/rosenpass/src/cli.rs @@ -32,11 +32,21 @@ pub struct CliArgs { #[arg(short, long, group = "log-level")] quiet: bool, + #[command(flatten)] + #[cfg(feature = "experiment_api")] + api: crate::api::cli::ApiCli, + #[command(subcommand)] pub command: CliCommand, } impl CliArgs { + pub fn apply_to_config(&self, _cfg: &mut config::Rosenpass) -> anyhow::Result<()> { + #[cfg(feature = "experiment_api")] + self.api.apply_to_config(_cfg)?; + Ok(()) + } + /// returns the log level filter set by CLI args /// returns `None` if the user did not specify any log level filter via CLI /// @@ -259,8 +269,10 @@ impl CliArgs { "config file '{config_file:?}' does not exist" ); - let config = config::Rosenpass::load(config_file)?; + let mut config = config::Rosenpass::load(config_file)?; config.validate()?; + self.apply_to_config(&mut config)?; + Self::event_loop(config, test_helpers)?; } @@ -279,6 +291,8 @@ impl CliArgs { config.config_file_path.clone_from(p); } config.validate()?; + self.apply_to_config(&mut config)?; + Self::event_loop(config, test_helpers)?; } @@ -320,6 +334,8 @@ impl CliArgs { test_helpers, )?); + config.apply_to_app_server(&mut srv)?; + let broker_store_ptr = srv.register_broker(Box::new(NativeUnixBroker::new()))?; fn cfg_err_map(e: NativeUnixBrokerConfigBaseBuilderError) -> anyhow::Error { @@ -367,3 +383,15 @@ fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow ssk.store_secret(secret_key)?; spk.store(public_key) } + +#[cfg(feature = "internal_testing")] +pub mod testing { + use super::*; + + pub fn generate_and_save_keypair( + secret_key: PathBuf, + public_key: PathBuf, + ) -> anyhow::Result<()> { + super::generate_and_save_keypair(secret_key, public_key) + } +} diff --git a/rosenpass/src/config.rs b/rosenpass/src/config.rs index ab320eef..e7538d35 100644 --- a/rosenpass/src/config.rs +++ b/rosenpass/src/config.rs @@ -19,6 +19,8 @@ use anyhow::{bail, ensure}; use rosenpass_util::file::{fopen_w, Visibility}; use serde::{Deserialize, Serialize}; +use crate::app_server::AppServer; + #[derive(Debug, Serialize, Deserialize)] pub struct Rosenpass { /// path to the public key file @@ -27,6 +29,10 @@ pub struct Rosenpass { /// path to the secret key file pub secret_key: PathBuf, + /// Location of the API listen sockets + #[cfg(feature = "experiment_api")] + pub api: crate::api::config::ApiConfig, + /// list of [`SocketAddr`] to listen on /// /// Examples: @@ -54,7 +60,7 @@ pub struct Rosenpass { /// ## TODO /// - replace this type with [`log::LevelFilter`], also see -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Copy, Clone)] pub enum Verbosity { Quiet, Verbose, @@ -157,6 +163,12 @@ impl Rosenpass { self.store(&self.config_file_path) } + pub fn apply_to_app_server(&self, _srv: &mut AppServer) -> anyhow::Result<()> { + #[cfg(feature = "experiment_api")] + self.api.apply_to_app_server(_srv)?; + Ok(()) + } + /// Validate a configuration /// /// ## TODO @@ -206,6 +218,8 @@ impl Rosenpass { public_key: PathBuf::from(public_key.as_ref()), secret_key: PathBuf::from(secret_key.as_ref()), listen: vec![], + #[cfg(feature = "experiment_api")] + api: crate::api::config::ApiConfig::default(), verbosity: Verbosity::Quiet, peers: vec![], config_file_path: PathBuf::new(), diff --git a/rosenpass/src/lib.rs b/rosenpass/src/lib.rs index a561dc04..b186bd21 100644 --- a/rosenpass/src/lib.rs +++ b/rosenpass/src/lib.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "experiment_api")] +pub mod api; pub mod app_server; pub mod cli; pub mod config; @@ -11,4 +13,8 @@ pub enum RosenpassError { BufferSizeMismatch, #[error("invalid message type")] InvalidMessageType(u8), + #[error("invalid API message type")] + InvalidApiMessageType(u128), + #[error("could not parse API message")] + InvalidApiMessage, } diff --git a/rosenpass/tests/api-integration-tests.rs b/rosenpass/tests/api-integration-tests.rs new file mode 100644 index 00000000..da9c84af --- /dev/null +++ b/rosenpass/tests/api-integration-tests.rs @@ -0,0 +1,180 @@ +use std::{ + io::{BufRead, BufReader}, + net::ToSocketAddrs, + os::unix::net::UnixStream, + process::Stdio, +}; + +use anyhow::{bail, Context}; +use rosenpass::api; +use rosenpass_to::{ops::copy_slice_least_src, To}; +use rosenpass_util::zerocopy::ZerocopySliceExt; +use rosenpass_util::{ + file::LoadValueB64, + length_prefix_encoding::{decoder::LengthPrefixDecoder, encoder::LengthPrefixEncoder}, +}; +use tempfile::TempDir; +use zerocopy::AsBytes; + +use rosenpass::protocol::SymKey; + +#[test] +fn api_integration_test() -> anyhow::Result<()> { + rosenpass_secret_memory::policy::secret_policy_use_only_malloc_secrets(); + + let dir = TempDir::with_prefix("rosenpass-api-integration-test")?; + + macro_rules! tempfile { + ($($lst:expr),+) => {{ + let mut buf = dir.path().to_path_buf(); + $(buf.push($lst);)* + buf + }} + } + + let peer_a_endpoint = "[::1]:61423"; + let peer_a_osk = tempfile!("a.osk"); + let peer_b_osk = tempfile!("b.osk"); + + use rosenpass::config; + let peer_a = config::Rosenpass { + config_file_path: tempfile!("a.config"), + secret_key: tempfile!("a.sk"), + public_key: tempfile!("a.pk"), + listen: peer_a_endpoint.to_socket_addrs()?.collect(), // TODO: This could collide by accident + verbosity: config::Verbosity::Verbose, + api: api::config::ApiConfig { + listen_path: vec![tempfile!("a.sock")], + listen_fd: vec![], + stream_fd: vec![], + }, + peers: vec![config::RosenpassPeer { + public_key: tempfile!("b.pk"), + key_out: Some(peer_a_osk.clone()), + endpoint: None, + pre_shared_key: None, + wg: None, + }], + }; + + let peer_b = config::Rosenpass { + config_file_path: tempfile!("b.config"), + secret_key: tempfile!("b.sk"), + public_key: tempfile!("b.pk"), + listen: vec![], + verbosity: config::Verbosity::Verbose, + api: api::config::ApiConfig { + listen_path: vec![tempfile!("b.sock")], + listen_fd: vec![], + stream_fd: vec![], + }, + peers: vec![config::RosenpassPeer { + public_key: tempfile!("a.pk"), + key_out: Some(peer_b_osk.clone()), + endpoint: Some(peer_a_endpoint.to_owned()), + pre_shared_key: None, + wg: None, + }], + }; + + // Generate the keys + rosenpass::cli::testing::generate_and_save_keypair( + peer_a.secret_key.clone(), + peer_a.public_key.clone(), + )?; + rosenpass::cli::testing::generate_and_save_keypair( + peer_b.secret_key.clone(), + peer_b.public_key.clone(), + )?; + + // Write the configuration files + peer_a.commit()?; + peer_b.commit()?; + + // Start peer a + let proc_a = std::process::Command::new(env!("CARGO_BIN_EXE_rosenpass")) + .args([ + "exchange-config", + peer_a.config_file_path.to_str().context("")?, + ]) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .spawn()?; + + // Start peer b + let proc_b = std::process::Command::new(env!("CARGO_BIN_EXE_rosenpass")) + .args([ + "exchange-config", + peer_b.config_file_path.to_str().context("")?, + ]) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .spawn()?; + + // Acquire stdout + let mut out_a = BufReader::new(proc_a.stdout.context("")?).lines(); + let mut out_b = BufReader::new(proc_b.stdout.context("")?).lines(); + + // Wait for the keys to successfully exchange a key + let mut attempt = 0; + loop { + let line_a = out_a.next().context("")??; + let line_b = out_b.next().context("")??; + + let words_a = line_a.split(' ').collect::>(); + let words_b = line_b.split(' ').collect::>(); + + // FIXED FIXED PEER-ID FIXED FILENAME STATUS + // output-key peer KZqXTZ4l2aNnkJtLPhs4D8JxHTGmRSL9w3Qr+X8JxFk= key-file "client-A-osk" exchanged + let peer_a_id = words_b + .get(2) + .with_context(|| format!("Bad rosenpass output: `{line_b}`"))?; + let peer_b_id = words_a + .get(2) + .with_context(|| format!("Bad rosenpass output: `{line_a}`"))?; + assert_eq!( + line_a, + format!( + "output-key peer {peer_b_id} key-file \"{}\" exchanged", + peer_a_osk.to_str().context("")? + ) + ); + assert_eq!( + line_b, + format!( + "output-key peer {peer_a_id} key-file \"{}\" exchanged", + peer_b_osk.to_str().context("")? + ) + ); + + // Read OSKs + let osk_a = SymKey::load_b64::<64, _>(peer_a_osk.clone())?; + let osk_b = SymKey::load_b64::<64, _>(peer_b_osk.clone())?; + match osk_a.secret() == osk_b.secret() { + true => break, + false if attempt > 10 => bail!("Peers did not produce a matching key even after ten attempts. Something is wrong with the key exchange!"), + false => {}, + }; + + attempt += 1; + } + + // Now connect to the peers + let api_a = UnixStream::connect(&peer_a.api.listen_path[0])?; + let api_b = UnixStream::connect(&peer_b.api.listen_path[0])?; + + for conn in ([api_a, api_b]).iter() { + let mut echo = [0u8; 256]; + copy_slice_least_src("Hello World".as_bytes()).to(&mut echo); + + let req = api::PingRequest::new(echo); + LengthPrefixEncoder::from_message(req.as_bytes()).write_all_to_stdio(conn)?; + + let mut decoder = LengthPrefixDecoder::new([0u8; api::MAX_RESPONSE_LEN]); + let res = decoder.read_all_from_stdio(conn)?; + let res = res.zk_parse::()?; + assert_eq!(*res, api::PingResponse::new(echo)); + } + + Ok(()) +} From 6d47169a5cee6319ebf419e375635e1fac2d48fc Mon Sep 17 00:00:00 2001 From: Karolin Varner Date: Sun, 4 Aug 2024 21:16:09 +0200 Subject: [PATCH 20/21] feat: Set CLOEXEC flag on claimed fds and mask them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Masking the file descriptors (by replaying them with a file descriptor pointing towards /dev/null) mitigates use after free (on file descriptor) attacks. In case some piece of code still holds a reference to the file descriptor, that file descriptor now merely holds a reference to /dev/null. Otherwise, the file descriptor might be reused and the reference could now mistakenly point to all sorts of – potentially more harmful – files, such as memfd_secret file descriptors, storing our secret keys. --- Cargo.toml | 2 +- util/src/fd.rs | 54 +++++++++++++++++++++++++++++++++++-------- util/src/mem.rs | 61 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ab829ffd..555af420 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,4 +83,4 @@ procspawn = {version = "1.0.0", features= ["test-support"]} #Broker dependencies (might need cleanup or changes) wireguard-uapi = { version = "3.0.0", features = ["xplatform"] } command-fds = "0.2.3" -rustix = { version = "0.38.27", features = ["net"] } +rustix = { version = "0.38.27", features = ["net", "fs"] } diff --git a/util/src/fd.rs b/util/src/fd.rs index cf5eaf95..0268fa35 100644 --- a/util/src/fd.rs +++ b/util/src/fd.rs @@ -1,12 +1,48 @@ -use std::os::fd::{OwnedFd, RawFd}; +use rustix::{ + fd::{AsFd, BorrowedFd, FromRawFd, OwnedFd, RawFd}, + io::{fcntl_dupfd_cloexec, DupFlags}, +}; -/// Clone some file descriptor +use crate::mem::Forgetting; + +/// Prepare a file descriptor for use in Rust code. /// -/// If the file descriptor is invalid, an error will be raised. -pub fn claim_fd(fd: RawFd) -> anyhow::Result { - use rustix::{fd::BorrowedFd, io::dup}; - - // This is safe since [dup] will simply raise - let fd = unsafe { dup(BorrowedFd::borrow_raw(fd))? }; - Ok(fd) +/// Checks if the file descriptor is valid and duplicates it to a new file descriptor. +/// The old file descriptor is masked to avoid potential use after free (on file descriptor) +/// in case the given file descriptor is still used somewhere +pub fn claim_fd(fd: RawFd) -> rustix::io::Result { + let new = clone_fd_cloexec(unsafe { BorrowedFd::borrow_raw(fd) })?; + mask_fd(fd)?; + Ok(new) +} + +pub fn mask_fd(fd: RawFd) -> rustix::io::Result<()> { + let mut owned = Forgetting::new(unsafe { OwnedFd::from_raw_fd(fd) }); + clone_fd_to_cloexec(open_nullfd()?, &mut owned) +} + +pub fn clone_fd_cloexec(fd: Fd) -> rustix::io::Result { + const MINFD: RawFd = 3; // Avoid stdin, stdout, and stderr + fcntl_dupfd_cloexec(fd, MINFD) +} + +#[cfg(target_os = "linux")] +pub fn clone_fd_to_cloexec(fd: Fd, new: &mut OwnedFd) -> rustix::io::Result<()> { + use rustix::io::dup3; + dup3(fd, new, DupFlags::CLOEXEC) +} + +#[cfg(not(target_os = "linux"))] +pub fn clone_fd_to_cloexec(fd: Fd, new: &mut OwnedFd) -> rustix::io::Result<()> { + use rustix::io::{dup2, fcntl_setfd, FdFlags}; + dup2(&fd, new)?; + fcntl_setfd(&new, FdFlags::CLOEXEC) +} + +/// Open a "blocked" file descriptor. I.e. a file descriptor that is neither meant for reading nor +/// writing +pub fn open_nullfd() -> rustix::io::Result { + use rustix::fs::{open, Mode, OFlags}; + // TODO: Add tests showing that this will throw errors on use + open("/dev/null", OFlags::CLOEXEC, Mode::empty()) } diff --git a/util/src/mem.rs b/util/src/mem.rs index be3d0e81..620e3d89 100644 --- a/util/src/mem.rs +++ b/util/src/mem.rs @@ -1,5 +1,7 @@ use std::borrow::{Borrow, BorrowMut}; use std::cmp::min; +use std::mem::{forget, swap}; +use std::ops::{Deref, DerefMut}; /// Concatenate two byte arrays // TODO: Zeroize result? @@ -31,3 +33,62 @@ pub fn cpy_min + ?Sized, F: Borrow<[u8]> + ?Sized>(src: &F, d let len = min(src.len(), dst.len()); dst[..len].copy_from_slice(&src[..len]); } + +/// Wrapper type to inhibit calling [std::mem::Drop] when the underlying variable is freed +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Default)] +pub struct Forgetting { + value: Option, +} + +impl Forgetting { + pub fn new(value: T) -> Self { + let value = Some(value); + Self { value } + } + + pub fn extract(mut self) -> T { + let mut value = None; + swap(&mut value, &mut self.value); + value.unwrap() + } +} + +impl From for Forgetting { + fn from(value: T) -> Self { + Self::new(value) + } +} + +impl Deref for Forgetting { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.value.as_ref().unwrap() + } +} + +impl DerefMut for Forgetting { + fn deref_mut(&mut self) -> &mut Self::Target { + self.value.as_mut().unwrap() + } +} + +impl Borrow for Forgetting { + fn borrow(&self) -> &T { + self.deref() + } +} + +impl BorrowMut for Forgetting { + fn borrow_mut(&mut self) -> &mut T { + self.deref_mut() + } +} + +impl Drop for Forgetting { + fn drop(&mut self) { + let mut value = None; + swap(&mut self.value, &mut value); + forget(value) + } +} From 9fd3df67eda3b3fbff5a710fa3bf0b5c143869cb Mon Sep 17 00:00:00 2001 From: Katherine Watson Date: Wed, 7 Aug 2024 23:11:13 -0700 Subject: [PATCH 21/21] chore: Fix typos and add various comments --- rosenpass/src/api/boilerplate/message_type.rs | 5 +++-- rosenpass/src/api/cli.rs | 2 +- rosenpass/src/api/mio/manager.rs | 2 ++ util/src/fd.rs | 2 ++ util/src/length_prefix_encoding/decoder.rs | 4 ++-- util/src/zerocopy/ref_maker.rs | 9 +++++---- util/src/zerocopy/zerocopy_slice_ext.rs | 6 +++--- 7 files changed, 18 insertions(+), 12 deletions(-) diff --git a/rosenpass/src/api/boilerplate/message_type.rs b/rosenpass/src/api/boilerplate/message_type.rs index 8fca7922..3a69564e 100644 --- a/rosenpass/src/api/boilerplate/message_type.rs +++ b/rosenpass/src/api/boilerplate/message_type.rs @@ -6,6 +6,7 @@ use crate::RosenpassError::{self, InvalidApiMessageType}; pub type RawMsgType = u128; +// constants generated by gen-ipc-msg-types: // hash domain hash of: Rosenpass IPC API -> Rosenpass Protocol Server -> Ping Request pub const PING_REQUEST: RawMsgType = RawMsgType::from_le_bytes(hex!("2397 3ecc c441 704d 0b02 ea31 45d3 4999")); @@ -46,7 +47,7 @@ impl MessageAttributes for ResponseMsgType { impl TryFrom for RequestMsgType { type Error = RosenpassError; - fn try_from(value: u128) -> Result { + fn try_from(value: RawMsgType) -> Result { use RequestMsgType as E; Ok(match value { self::PING_REQUEST => E::Ping, @@ -67,7 +68,7 @@ impl From for RawMsgType { impl TryFrom for ResponseMsgType { type Error = RosenpassError; - fn try_from(value: u128) -> Result { + fn try_from(value: RawMsgType) -> Result { use ResponseMsgType as E; Ok(match value { self::PING_RESPONSE => E::Ping, diff --git a/rosenpass/src/api/cli.rs b/rosenpass/src/api/cli.rs index b160a5c5..6c45c896 100644 --- a/rosenpass/src/api/cli.rs +++ b/rosenpass/src/api/cli.rs @@ -10,7 +10,7 @@ use super::config::ApiConfig; #[derive(Args, Debug)] pub struct ApiCli { /// Where in the file-system to create the unix socket the rosenpass API will be listening for - /// connections on + /// connections on. #[arg(long)] api_listen_path: Vec, diff --git a/rosenpass/src/api/mio/manager.rs b/rosenpass/src/api/mio/manager.rs index 172addf3..c1ed019d 100644 --- a/rosenpass/src/api/mio/manager.rs +++ b/rosenpass/src/api/mio/manager.rs @@ -70,6 +70,8 @@ impl MioManager { token_dispenser: &mut MioTokenDispenser, ) -> io::Result<()> { // Accept connection until the socket would block or returns another error + // TODO: This currently only adds connections--we eventually need the ability to remove + // them as well, see the note in connection.rs loop { match nonblocking_handle_io_errors(|| self.listeners[idx].accept())? { None => break, diff --git a/util/src/fd.rs b/util/src/fd.rs index 0268fa35..95ff08e3 100644 --- a/util/src/fd.rs +++ b/util/src/fd.rs @@ -17,6 +17,8 @@ pub fn claim_fd(fd: RawFd) -> rustix::io::Result { } pub fn mask_fd(fd: RawFd) -> rustix::io::Result<()> { + // Safety: because the OwnedFd resulting from OwnedFd::from_raw_fd is wrapped in a Forgetting, + // it never gets dropped, meaning that fd is never closed and thus outlives the OwnedFd let mut owned = Forgetting::new(unsafe { OwnedFd::from_raw_fd(fd) }); clone_fd_to_cloexec(open_nullfd()?, &mut owned) } diff --git a/util/src/length_prefix_encoding/decoder.rs b/util/src/length_prefix_encoding/decoder.rs index 26a7130c..1769b704 100644 --- a/util/src/length_prefix_encoding/decoder.rs +++ b/util/src/length_prefix_encoding/decoder.rs @@ -19,7 +19,7 @@ pub enum SanityError { } #[derive(Error, Debug)] -#[error("Message too lage ({msg_size} bytes) for buffer ({buf_size} bytes)")] +#[error("Message too large ({msg_size} bytes) for buffer ({buf_size} bytes)")] pub struct MessageTooLargeError { msg_size: usize, buf_size: usize, @@ -132,7 +132,7 @@ impl> LengthPrefixDecoder { Ok(match self.next_slice_to_write_to()? { // Read some bytes; any MessageTooLargeError in the call to self.message_mut() is // ignored to ensure this function changes no state upon errors; the user should rerun - // the function and colect the MessageTooLargeError on the following invocation + // the function and collect the MessageTooLargeError on the following invocation Some(buf) => { let bytes_read = r.read(buf)?; self.advance(bytes_read).unwrap(); diff --git a/util/src/zerocopy/ref_maker.rs b/util/src/zerocopy/ref_maker.rs index 13e6fe96..0b18702a 100644 --- a/util/src/zerocopy/ref_maker.rs +++ b/util/src/zerocopy/ref_maker.rs @@ -57,21 +57,22 @@ impl RefMaker { Ok(Self::from_prefix_with_tail(self)?.0) } - pub fn from_suffix_with_tail(self) -> anyhow::Result<(Self, B)> { + pub fn from_suffix_with_head(self) -> anyhow::Result<(Self, B)> { self.ensure_fit()?; let point = self.bytes().len() - Self::target_size(); let (head, tail) = self.buf.split_at(point); - Ok((Self::new(head), tail)) + Ok((Self::new(tail), head)) } pub fn split_suffix(self) -> anyhow::Result<(Self, Self)> { self.ensure_fit()?; - let (head, tail) = self.buf.split_at(Self::target_size()); + let point = self.bytes().len() - Self::target_size(); + let (head, tail) = self.buf.split_at(point); Ok((Self::new(head), Self::new(tail))) } pub fn from_suffix(self) -> anyhow::Result { - Ok(Self::from_suffix_with_tail(self)?.0) + Ok(Self::from_suffix_with_head(self)?.0) } pub fn bytes(&self) -> &[u8] { diff --git a/util/src/zerocopy/zerocopy_slice_ext.rs b/util/src/zerocopy/zerocopy_slice_ext.rs index eb0000af..13992539 100644 --- a/util/src/zerocopy/zerocopy_slice_ext.rs +++ b/util/src/zerocopy/zerocopy_slice_ext.rs @@ -16,7 +16,7 @@ pub trait ZerocopySliceExt: Sized + ByteSlice { } fn zk_parse_suffix(self) -> anyhow::Result> { - self.zk_ref_maker().from_prefix()?.parse() + self.zk_ref_maker().from_suffix()?.parse() } } @@ -31,8 +31,8 @@ pub trait ZerocopyMutSliceExt: ZerocopySliceExt + Sized + ByteSliceMut { self.zk_ref_maker().from_prefix()?.make_zeroized() } - fn zk_zeroized_from_suffic(self) -> anyhow::Result> { - self.zk_ref_maker().from_prefix()?.make_zeroized() + fn zk_zeroized_from_suffix(self) -> anyhow::Result> { + self.zk_ref_maker().from_suffix()?.make_zeroized() } }