COOLJAPAN
← All posts

OxiCode 0.2.5 Released — Every Allocation Now Asks Permission First

OxiCode 0.2.5 is a hardening release: fixes decode-time allocation-DoS, decompression-bomb, and checksum-overflow issues across containers, streaming, and derive macros, and replaces a fabricated SIMD speedup claim with real AVX2/SSE2/NEON kernels. No wire-format change for valid input. 20,126 tests passing.

release oxicode bincode serialization security hardening simd pure-rust

The fastest way to stop a decompression bomb is to never allocate for it in the first place.

Today we released OxiCode 0.2.5 — a hardening-focused release. A coordinated internal audit went through decoding, streaming, compression, checksums, the derive macros, and serde integration, and fixed a wide range of denial-of-service, panic, integer-overflow, and silent-corruption issues. No serialized wire-format bytes changed for any input that was already valid — every behavior change is either a new rejection of previously-invalid or malicious input, or a bookkeeping/allocation-timing fix on the error path.

No C. No Fortran. No unaudited allocation path.
OxiCode stays a pure-Rust binary codec that compiles to a single static binary (or WASM) — now with every collection and stream decoder accounting for its allocation before it makes it.

Why OxiCode 0.2.5 is a game changer

Binary deserializers have a well-known soft spot: a length prefix is just a number, and if you allocate based on it before validating it, an attacker who controls four bytes controls your memory usage. OxiCode 0.2.5 closes that gap everywhere it was still open:

Alongside the DoS-mitigation work, the audit also caught a checksum length-overflow that could panic on attacker-controlled input, an [T; N] decode path that leaked already-initialized elements on error, an OsStr::encode that silently lossy-converted non-UTF-8 bytes instead of erroring, and — separate from the security items — a SIMD array codec that quietly fell back to scalar code for some inputs while still claiming a fixed “2-4x speedup” it never measured. 0.2.5 fixes all of it, and replaces the SIMD fast path with real hardware kernels.

Technical Deep Dive: what changed under the hood

  1. Claim-before-allocate, everywhere
    The claim_container_read accounting that already guarded Vec decode is now applied uniformly across every standard-library collection type and every derive-generated Vec/seq_len field, plus the streaming chunk-header path. One rule, enforced consistently, instead of Vec being hardened while its siblings were not.

  2. Compression-bomb pre-scan
    Zstd decompression now inspects the frame header and per-block regenerated-size upper bounds before committing to decompress, rejecting frames that would blow past the configured cap. LZ4 frames that clear the Content_Size flag (or use a non-standard magic) are rejected before the decoder would otherwise make an unbounded up-front reservation. Cross-feature codec mismatches (LZ4 payload decompressed with only compression-zstd enabled) now return a clear “codec not enabled” error instead of misdispatching.

  3. Overflow and leak fixes on the error path
    verify_checksum’s HEADER_SIZE + stored_len addition is now checked_add instead of an unchecked add that could wrap or panic on a forged length near usize::MAX. [T; N] decode now uses a drop-guard tracking the initialized-so-far count, so an error partway through no longer leaks the elements already decoded. Every remaining u64::decode(..)? as usize length read across impl_std.rs, impl_alloc.rs, and derive-generated code now uses a checked usize::try_from.

  4. Real SIMD, and zero-copy serde borrowing
    oxicode::simd now always routes through genuine AVX2/SSE2 (x86_64) or NEON (aarch64) hardware kernels on little-endian targets, with runtime CPU-capability detection under std — the fabricated “2-4x” claim is gone; the docs now measure their own speedup at run time. Separately, the serde integration gained real zero-copy borrowed deserialization: &'de str/&'de [u8]/#[serde(borrow)] fields now actually borrow from the input buffer instead of erroring at runtime.

Getting Started

cargo add oxicode

Cap decompression memory explicitly — the headline 0.2.5 capability, though decompress() already defaults to a 256 MiB cap:

use oxicode::compression::{compress, decompress_with_limit, Compression};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let payload = vec![0u8; 4096];
    let encoded = oxicode::encode_to_vec(&payload)?;
    let compressed = compress(&encoded, Compression::Zstd)?;

    // Reject anything whose declared/regenerated size exceeds 16 MiB,
    // checked against the frame header before decompression starts.
    let decompressed = decompress_with_limit(&compressed, 16 * 1024 * 1024)?;
    let (decoded, _): (Vec<u8>, _) = oxicode::decode_from_slice(&decompressed)?;

    assert_eq!(payload, decoded);
    Ok(())
}

What’s New in 0.2.5

Tips

This is the foundation

OxiCode is the serialization layer beneath the COOLJAPAN data stack — checkpoints for SciRS2 and NumRS2, tensor buffers for ToRSh, UI state serialization for OxiUI, spatial data for OxiGeo, quantum circuit payloads for QuantRS2, and archive-embedded records via OxiARC. A hardening pass this thorough lands underneath every one of those consumers automatically, without any of them having to change a line of their own code.

Repository: https://github.com/cool-japan/oxicode

Star the repo if you want a binary codec that treats every length prefix as hostile until proven otherwise.

The era of “trust the length byte” is over. Pure Rust binary serialization is here — fast, compatible, and sovereign.

KitaSan at COOLJAPAN OÜ July 30, 2026

↑ Back to all posts