#![forbid(unsafe_code)]
use std::os::unix::net::UnixDatagram;
use std::path::PathBuf;
use std::time::Instant;
#[cfg(any(feature = "udp", feature = "secure-udp"))]
use crate::transport::bind_ephemeral;
#[cfg(all(feature = "panic-handler", feature = "secure-udp"))]
use varta_vlp::crypto::Key;
use varta_vlp::{Frame, Status, NONCE_TERMINAL};
#[cfg(all(feature = "panic-handler", feature = "secure-udp"))]
#[derive(Debug)]
pub enum PanicInstallError {
EntropyUnavailable(std::io::Error),
SocketBind(std::io::Error),
}
#[cfg(all(feature = "panic-handler", feature = "secure-udp"))]
impl core::fmt::Display for PanicInstallError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
PanicInstallError::EntropyUnavailable(e) => {
write!(
f,
"varta: panic-hook install failed — entropy unavailable: {e}"
)
}
PanicInstallError::SocketBind(e) => {
write!(
f,
"varta: panic-hook install failed — socket bind/connect: {e}"
)
}
}
}
}
#[cfg(all(feature = "panic-handler", feature = "secure-udp"))]
impl std::error::Error for PanicInstallError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
PanicInstallError::EntropyUnavailable(e) => Some(e),
PanicInstallError::SocketBind(e) => Some(e),
}
}
}
pub fn install(socket_path: impl Into<PathBuf>) -> std::io::Result<()> {
let path: PathBuf = socket_path.into();
let start = Instant::now();
let sock = UnixDatagram::unbound()?;
sock.connect(&path)?;
sock.set_nonblocking(true)?;
let prev = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
let timestamp = start.elapsed().as_nanos().min(u64::MAX as u128) as u64;
let frame = Frame::new(
Status::Critical,
std::process::id(),
timestamp,
NONCE_TERMINAL,
0,
);
let mut buf = [0u8; 32];
frame.encode(&mut buf);
let _ = sock.send(&buf);
prev(info);
}));
Ok(())
}
#[cfg(feature = "udp")]
pub fn install_panic_handler_udp(addr: std::net::SocketAddr) -> std::io::Result<()> {
let start = Instant::now();
let sock = bind_ephemeral(&addr)?;
sock.connect(addr)?;
sock.set_nonblocking(true)?;
let prev = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
let timestamp = start.elapsed().as_nanos().min(u64::MAX as u128) as u64;
let frame = Frame::new(
Status::Critical,
std::process::id(),
timestamp,
NONCE_TERMINAL,
0,
);
let mut buf = [0u8; 32];
frame.encode(&mut buf);
let _ = sock.send(&buf);
prev(info);
}));
Ok(())
}
#[cfg(all(feature = "panic-handler", feature = "secure-udp"))]
pub(crate) fn install_with_entropy_provider<F, G>(
addr: std::net::SocketAddr,
key: Key,
provider: F,
refresh: G,
) -> Result<(), PanicInstallError>
where
F: FnOnce() -> std::io::Result<[u8; 8]>,
G: Fn() -> Option<[u8; 8]> + Send + Sync + 'static,
{
use varta_vlp::crypto::{self, NONCE_BYTES};
let start = Instant::now();
let iv_random: [u8; 8] = provider().map_err(PanicInstallError::EntropyUnavailable)?;
let install_pid = std::process::id();
let sock = bind_ephemeral(&addr).map_err(PanicInstallError::SocketBind)?;
sock.connect(addr).map_err(PanicInstallError::SocketBind)?;
sock.set_nonblocking(true)
.map_err(PanicInstallError::SocketBind)?;
let prev = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
let _ = (|| {
let panic_pid = std::process::id();
let nonce_prefix: [u8; 8] = if panic_pid != install_pid {
refresh()?
} else {
iv_random
};
let timestamp = start.elapsed().as_nanos().min(u64::MAX as u128) as u64;
let frame = Frame::new(Status::Critical, panic_pid, timestamp, NONCE_TERMINAL, 0);
let mut buf = [0u8; 32];
frame.encode(&mut buf);
let iv_counter = 1u32;
let mut nonce = [0u8; NONCE_BYTES];
nonce[..8].copy_from_slice(&nonce_prefix);
nonce[8..12].copy_from_slice(&iv_counter.to_le_bytes());
let (ciphertext, tag) = crypto::seal(key.as_bytes(), &nonce, b"", &buf).ok()?;
let mut secure_frame = [0u8; crypto::SECURE_FRAME_BYTES];
secure_frame[..8].copy_from_slice(&nonce_prefix);
secure_frame[8..12].copy_from_slice(&iv_counter.to_le_bytes());
secure_frame[12..44].copy_from_slice(&ciphertext);
secure_frame[44..60].copy_from_slice(&tag);
sock.send(&secure_frame).ok()
})();
prev(info);
}));
Ok(())
}
#[cfg(all(feature = "panic-handler", feature = "secure-udp"))]
pub fn install_panic_handler_secure_udp(
addr: std::net::SocketAddr,
key: Key,
) -> Result<(), PanicInstallError> {
use crate::secure_transport::read_iv_random;
install_with_entropy_provider(addr, key, read_iv_random, || read_iv_random().ok())
}
#[cfg(feature = "accept-degraded-entropy")]
pub fn install_panic_handler_secure_udp_accept_degraded_entropy(
addr: std::net::SocketAddr,
key: Key,
) -> std::io::Result<()> {
use crate::secure_transport::{fallback_iv_random, read_iv_random};
match install_with_entropy_provider(
addr,
key,
|| Ok(read_iv_random().unwrap_or_else(|_| fallback_iv_random())),
|| Some(read_iv_random().unwrap_or_else(|_| fallback_iv_random())),
) {
Ok(()) => Ok(()),
Err(PanicInstallError::SocketBind(e)) => Err(e),
Err(PanicInstallError::EntropyUnavailable(_)) => {
unreachable!("degraded-entropy provider is infallible by construction")
}
}
}
#[cfg(all(test, feature = "panic-handler", feature = "secure-udp"))]
mod tests {
use super::*;
use std::io;
use std::net::SocketAddr;
fn dummy_addr() -> SocketAddr {
"127.0.0.1:65535".parse().unwrap()
}
fn dummy_key() -> Key {
Key::from_bytes([0u8; 32])
}
#[test]
fn install_with_entropy_provider_happy_path_returns_ok() {
let result = install_with_entropy_provider(
dummy_addr(),
dummy_key(),
|| Ok([1u8; 8]),
|| Some([2u8; 8]),
);
assert!(result.is_ok());
let _ = std::panic::take_hook();
}
#[test]
fn install_with_entropy_provider_failure_returns_err_and_does_not_install() {
let err = io::Error::new(io::ErrorKind::NotFound, "no /dev in chroot");
let result = install_with_entropy_provider(
dummy_addr(),
dummy_key(),
|| Err(err),
|| Some([2u8; 8]),
);
match result {
Err(PanicInstallError::EntropyUnavailable(inner)) => {
assert_eq!(inner.kind(), io::ErrorKind::NotFound);
}
Err(PanicInstallError::SocketBind(e)) => {
panic!("expected EntropyUnavailable, got SocketBind({e})")
}
Ok(()) => panic!("expected Err but got Ok"),
}
}
#[test]
fn socket_bind_error_display_and_source() {
let inner = io::Error::from(io::ErrorKind::PermissionDenied);
let err = PanicInstallError::SocketBind(inner);
let msg = format!("{err}");
assert!(
msg.contains("socket bind/connect"),
"Display must mention socket bind/connect; got: {msg}"
);
assert!(
std::error::Error::source(&err).is_some(),
"source() must return the inner io::Error"
);
}
#[cfg(feature = "accept-degraded-entropy")]
#[test]
fn accept_degraded_entropy_always_succeeds() {
install_panic_handler_secure_udp_accept_degraded_entropy(dummy_addr(), dummy_key())
.expect("degraded-entropy install must succeed for loopback addr");
let _ = std::panic::take_hook();
}
}