diff --git a/Cargo.lock b/Cargo.lock index 02c5ddd..531bb58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,7 @@ version = "2.0.0" dependencies = [ "base64", "getrandom", + "libc", "openssl", "rustc_version", "smallvec", @@ -71,9 +72,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.90" +version = "0.2.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4aede83fc3617411dc6993bc8c70919750c1c257c6ca6a502aed6e0e2394ae" +checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966" [[package]] name = "once_cell" diff --git a/Cargo.toml b/Cargo.toml index ef8e145..2d45577 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,17 +3,21 @@ name = "chacha20" description = "chacha20_poly1305 encryption tool" version = "2.0.0" authors = ["Avril "] -edition = "2018" +edition = "2021" license = "gpl-3.0-or-later" [features] +default = ["mmap"] +# Try to map inputs/outputs before using buffers +mmap = ["libc"] # Explicitly clear buffers and cache after use explicit_clear = [] [dependencies] base64 = "0.13" getrandom = "0.2" +libc = { version = "0.2.133", optional = true } openssl = "0.10" smallvec = {version = "1.6", features=["union"]} diff --git a/src/main.rs b/src/main.rs index ba986c9..d488e32 100644 --- a/src/main.rs +++ b/src/main.rs @@ -81,6 +81,27 @@ fn keys() -> Result<(Mode, Key, IV), base64::DecodeError> Ok((mode, key, iv)) } +const USE_MMAP: bool = if cfg!(feature="mmap") { + true +} else { + false +}; + +#[cfg(feature="mmap")] +mod mapped; + +#[allow(unreachable_code)] +fn try_mmap(decrypt: bool, key: Key, iv: IV) -> std::io::Result +{ + #[cfg(feature="mmap")] return mapped::try_process(if decrypt { + cha::decrypter(key, iv).expect("Failed to create decrypter") + } else { + cha::encrypter(key, iv).expect("Failed to create encrypter") + }).map(|_| 0); + + unreachable!("Built without feature `mmap`, but still tried to call into it. This is a bug") +} + fn main() { let (mode, key, iv) = keys().expect("Failed to read keys from argv (base64)"); @@ -88,14 +109,26 @@ fn main() { let stdout = std::io::stdout(); let input = std::io::stdin(); + // Attempt a mapped solution + if USE_MMAP && mode != Mode::Keygen { + match try_mmap(mode == Mode::Decrypt, key, iv) { + Ok(0) => return, + Ok(n) => std::process::exit(n), + Err(err) => if cfg!(debug_assertions) { + eprintln!("Failed to mmap input or output for processing, falling back to stream: {}", err); + } + } + } // Streaming use std::io::Write; match mode { Mode::Encrypt => { + let mut output = stream::Sink::encrypt(stdout.lock(), key, iv).expect("Failed to create encrypter"); std::io::copy(&mut input.lock(), &mut output).expect("Failed to encrypt"); output.flush().expect("Failed to flush stdout"); + }, Mode::Decrypt => { let mut output = stream::Sink::decrypt(stdout.lock(), key, iv).expect("Failed to create decrypter"); diff --git a/src/mapped.rs b/src/mapped.rs new file mode 100644 index 0000000..7b9d3ee --- /dev/null +++ b/src/mapped.rs @@ -0,0 +1,110 @@ +//! Use memory mapping to process the entire stream at once +use super::*; + +use std::os::unix::prelude::*; +use std::{ + io, + fs, + ptr, + ops, + mem::{ + self, + MaybeUninit, + }, + borrow::BorrowMut, + convert::{TryFrom, TryInto,}, +}; +use libc::{ + mmap, munmap, madvise, MAP_FAILED, +}; +use openssl::symm::Crypter; + +#[derive(Debug)] +struct MapInner +{ + mem: ptr::NonNull, + size: usize +} + +impl ops::Drop for MapInner +{ + #[inline] + fn drop(&mut self) + { + unsafe { + munmap(self.mem.as_ptr() as *mut _, self.size); + } + } +} + +#[inline] +pub fn raw_file_size(fd: &(impl AsRawFd + ?Sized)) -> io::Result +{ + use libc::fstat; + let mut stat = MaybeUninit::uninit(); + match unsafe { fstat(fd.as_raw_fd(), stat.as_mut_ptr()) } { + 0 => match unsafe {stat.assume_init()}.st_size { + x if x < 0 => Err(io::Error::new(io::ErrorKind::InvalidInput, format!("File from {} is too large", fd.as_raw_fd()))), + x => Ok(x as u64), + }, + _ => Err(io::Error::last_os_error()), + } +} + +#[derive(Debug)] +pub struct Mapped +{ + mem: MapInner, + file: T +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy, PartialOrd, Ord)] +pub enum MapMode +{ + ReadOnly, + ReadWrite, + WriteOnly, +} +//TODO: impl MapMode -> fn as_prot() -> c_int, fn as_flags() -> c_int + +impl Mapped +{ + pub fn try_new_sized(file: T, size: u64, rw: MapMode) -> io::Result + { + todo!("mem: mmap(0, size, rw.prot(), MAP_SHARED (For rw: *Write*), MAP_PRIVATE [TODO: add support for| MAP_HUGE_] (For rw: ReadOnly), file.as_raw_fd(), 0) != MAP_FAILED (TODO: maybe madvise?)") + } + #[inline(always)] + pub fn try_new(file: T, rw: MapMode) -> io::Result + { + let size = raw_file_size(&file)?; + Self::try_new_sized(file, size, rw) + } +} +/* +impl TryFrom for Mapped +{ + type Error = io::Error; + + fn try_from(from: T) -> Result + { + Self::try_new(from, raw_file_size(&from)?) + } +}*/ + + +fn process_mapped_files(mode: &mut Crypter, input: &mut Mapped, output: &mut Mapped) -> io::Result<()> +where T: AsRawFd + ?Sized, +U: AsRawFd + ?Sized +{ + todo!("mode.update(input.slice(), output.slice()); /* unneeded: mode.finalize(input.slice(), output.slice()) */ "); +} + +#[inline] +pub fn try_process(mut mode: impl BorrowMut) -> io::Result> +{ + let mode = mode.borrow_mut(); + /*let mstdin = Mapped::try_new(input)?; + let mstdout = Mapped::try_new(output)?;*/ //TODO: if failed to map output, but input is successful (TODO: XXX: implement other way around), we can fall back to wrapping output in `Sink`. + todo!("Try to map the stdin and stdout streams, if that fails, return Err(last_os_err)."); + todo!("return Ok(process_mapped_files(mode, mstdin, mstdout, key, iv))") +}