Skip to content

v0.8.0

Latest

Choose a tag to compare

@jbcde jbcde released this 16 Apr 04:14

Security

  • Fixed: Tightened MAX_ARGON2_M_COST from 4 GiB to 1 GiB (kelly-report-2.md, finding K-9). An attacker-crafted header specifying maximum m_cost combined with Rayon's parallel key derivation could force parallel_threads × m_cost bytes of memory allocation during decrypt — on an 8-core machine, 16 GiB of Argon2id memory before any ciphertext was touched, enough to OOM a 16 GiB workstation. The new 1 GiB cap reduces worst-case to 4 GiB on the same machine. Default encryption params (64 MiB) are unaffected; no legitimate file uses m_cost above 1 GiB.

  • Fixed: Tightened MAX_CHUNK_COUNT from u32::MAX (~4.29 billion) to 2^40 (~1.1 trillion) (kelly-report-2.md, finding K-10). Each chunk requires independent Argon2id key derivation on decrypt — an attacker delivering a file claiming billions of chunks could pin the victim's CPU for hours to years of sustained compute. The new ceiling still covers any plausible file (1 EB at 1 MiB chunks = 10^12, within 2^40) while cutting the attacker's compute-DoS budget by a factor of ~16 million.

  • Fixed: Non-chunked header parser (parse_header_line) had no length ceiling — an attacker-crafted header with megabytes of data in a hash field could force hex decoding of the oversized field before the file was rejected (kelly-review.md, finding K-4). Header line scan is now capped at 64 KiB, matching the existing cap in the chunked decrypt path.

  • Fixed: Keyfile read (--keyfile) was unbounded — a path pointing at a large file (attacker-influenced or accidental) would load the entire file into RAM as password material (kelly-report-2.md, finding K-12). Keyfile size is now capped at 1 MiB with a clear error message.

  • Fixed: Memory locking (mlock) failure was silent — derived keys could fall back to unlocked (swappable) memory with no indication to the user (kelly-report-2.md, finding K-13). A one-time warning is now emitted to stderr when mlock fails, unless --silent is set.

  • Fixed: Keyfile generated by keygen and export-pubkey on Windows inherited the parent directory's ACL, potentially leaving private key material readable by other users (kelly-review.md, finding K-8). Windows keyfiles now have an explicit owner-only DACL applied via SetFileSecurityW. Unix permissions (0600) were already enforced since v0.5.2.

  • Fixed: Non-chunked decrypt no longer OOMs on files larger than available RAM (kelly-review.md, finding K-2). The previous code path called fs::read(path) which loaded the entire file into memory before any validation — an adversarial 100 GiB file delivered to a victim on a 16 GiB workstation would trigger OOM before the ciphertext hash could be checked. Non-chunked decrypt now routes to a new mmap-backed pipeline when file_size > available_memory / 2: the ciphertext body is streamed to a SecureTempFile with incremental SHA-256, then each cipher cascade layer runs via memory-mapped I/O against ping-pong temp files, with plaintext written directly into the output mapping (no per-layer Vec<u8> allocation). The final base64-encoded plaintext is mmap'd, UTF-8 validated by sequential scan, and stream-decoded to the output sink. Peak physical RAM is bounded by copy buffers and the OS page cache — never by file size. Small files (below the threshold) continue to use the existing fast RAM path unchanged.

Added

  • cascade::decrypt_nonchunked_mmap() — public API for bounded-RAM non-chunked decrypt via mmap. Used internally by the CLI when file_size > available_memory / 2.
  • cascade::decrypt_layers_mmap() — lower-level primitive that runs the cipher cascade with mmap'd intermediate buffers.
  • crypto::decrypt_mmap() and per-cipher MmapDecryptFn implementations for all 20 algorithms — write plaintext directly into a caller-provided output buffer instead of returning Vec<u8>.
  • buffer::process_mmap() / buffer::transform_in_place_mmap() — mmap-backed layer processing and in-place byte transforms.
  • encoder::decode_streaming() — streaming base64 decode via base64::read::DecoderReader, reads a length prefix and streams exactly that many bytes to a writer.
  • SecureTempFile::append(), file(), file_mut(), set_len() — accessors for mmap integration.
  • New memmap2 = "0.9" dependency.

Changed

  • All caps (MAX_ARGON2_M_COST, MAX_CHUNK_COUNT, header line length, keyfile size) apply only to the decrypt/parse path — encryption behavior is unaffected.
  • buffer::get_available_memory() visibility widened from pub(crate) to pub so the CLI routing logic can query it.

Documentation

  • Added security note that --pubkey header encryption provides confidentiality but not sender authenticity — anyone with the recipient's public key can encrypt to them (kelly-report-2.md, finding K-11).
  • Added security note that AES requires AES-NI for constant-time operation; ChaCha20-based algorithms (-C, -X) recommended on hardware without AES-NI (kelly-report-2.md, finding K-15).

Compatibility

  • No wire format changes. All files encrypted by any prior version decrypt under v0.8.0. No legitimate file's Argon2 params, chunk count, header line length, or keyfile size approaches the new caps.
  • The mmap decrypt path is transparent: files produced by any prior version decrypt bit-identically whether routed through the RAM or mmap path (the threshold only affects resource usage, not output).