cargo / adler2 / audit
cargo : adler2 @ 2.0.1
PE Patrick Elsen signed 2026-05-27 published 2026-05-27

Claims

algorithm-impl-boundsalgorithm-impl-correctalgorithm-impl-safealgorithm-impl-testedhas-binarieshas-build-exechas-fuzz-testshas-install-exechas-integration-testshas-property-testshas-unit-testsimpl-algorithmimpl-concurrencyimpl-cryptoimpl-datastructureimpl-interpreterimpl-jitimpl-parserimpl-protocolis-benignuses-concurrencyuses-cryptouses-environmentuses-execuses-filesystemuses-interpreteruses-jituses-networkuses-unsafe

Summary

adler2 2.0.1 is a small, single-purpose, unsafe-free Rust port of the Adler-32 checksum with zero runtime dependencies. Two low-severity quality findings (sparse testing, stale upstream-crate references); no security, safety, or correctness issues. Safe to deploy.

Report

Subject

adler2 is a clean-room Rust implementation of the Adler-32 checksum, the integrity check used by the zlib compression format. The crate is a maintained fork of the archived upstream adler crate. The public API exposes an Adler32 struct (with Hasher impl), a convenience adler32_slice function over a byte slice, and an adler32 function over any BufRead. The crate supports no_std (with default-features = false) and has zero runtime dependencies; the only declared dependency is the optional rustc-std-workspace-core, gated behind the internal rustc-dep-of-std feature for libstd integration. Justifies impl-algorithm.

Methodology

The audit examined the published adler2-2.0.1 crate against its VCS source at the commit recorded in .cargo_vcs_info.json on github.com/oyvindln/adler2. The diff between contents/ and vcs/ showed only the expected cargo normalisations (rewritten Cargo.toml, addition of .cargo_vcs_info.json and Cargo.lock, removal of .git, .github, .gitignore); no source files differ. The crate ships no binaries and no compiled artifacts (justifies has-binaries), no build script (build = false in Cargo.toml, no build.rs, no proc-macro entry points; justifies has-build-exec), and cargo crates have no install-time hooks (justifies has-install-exec).

The source tree consists of two files: src/lib.rs (public API and tests) and src/algo.rs (the core compute routine). Both were read end-to-end. The crate is annotated #![forbid(unsafe_code)] at the root, so the compiler guarantees no unsafe blocks, raw-pointer manipulation, or FFI exist anywhere in the crate (justifies uses-unsafe).

The codebase was reviewed for network I/O (std::net, reqwest, ureq, HTTP clients), process spawning (std::process, Command), dynamic code execution, environment-variable access (std::env), filesystem operations (std::fs, File::open), thread spawning, async runtimes, and cryptographic libraries. None were found. The adler32 function accepts a BufRead, but the crate does not itself open files; the caller chooses the reader (justifies uses-filesystem, uses-network, uses-exec, uses-environment, uses-concurrency, uses-crypto, uses-jit, uses-interpreter). Adler-32 is a non-cryptographic checksum; the crate neither uses nor implements cryptography (justifies impl-crypto). The crate does not implement a parser, interpreter, JIT, protocol, data structure, or concurrency primitive (justifies impl-parser, impl-interpreter, impl-jit, impl-protocol, impl-datastructure, impl-concurrency).

Tools used: openvet 0.6.0 for workspace creation, claim/finding/annotation management, and validation; diff (GNU diffutils) for the byte-level comparison between contents/ and vcs/; git (2.51) for the upstream checkout; grep/ripgrep for capability surveys.

Results

The comparison between the published crate contents and the upstream Git repository shows that the code files match byte-for-byte; manifest differences are limited to cargo's standard normalisation.

The implementation is a straightforward, integer-only port of the well-known chunked 4-way Adler-32 algorithm described in Intel's Fletcher-checksum paper. The chunk-size constant of 5552 * 4 is sized so that the per-lane u32 accumulators cannot overflow before each modular reduction; the analysis holds for all-0xff input, the worst case. All arithmetic is on plain u32/u16 (no unsafe, no wrapping types), the algorithm is linear in input length and uses constant auxiliary space, and there are no inputs that can trigger pathological behaviour (justifies algorithm-impl-bounds, algorithm-impl-safe). The output of the implementation matches the published Wikipedia reference vector ("Wikipedia" → 0x11E60398), so the implementation is functionally correct against an external reference (justifies algorithm-impl-correct).

The crate ships a small in-crate test module in src/lib.rs (justifies has-unit-tests). It has no tests/ directory of integration tests, no quickcheck/proptest-style randomised tests, and no fuzz/ harness (justifies has-integration-tests, has-property-tests and has-fuzz-tests). Unit tests cover the published Wikipedia reference vector, all-zero/all-0xff/mixed patterns at multiple sizes that cross the chunked path, resumption via from_checksum, and the BufRead path (justifies algorithm-impl-tested).

There is no indication of malicious behaviour, obfuscation, exfiltration, target-conditional payloads, or supply-chain tampering. The crate is single-purpose and the implementation matches its documentation (justifies is-benign).

Two low-severity quality findings are noted: limited testing depth — no fuzz or property tests against a reference implementation (FINDING-1) — and stale upstream-crate references in README.md and the html_root_url attribute (FINDING-2). Neither affects runtime behaviour.

Conclusion

The crate is a small, single-purpose, unsafe-free implementation of a standard checksum. Both findings are low-severity quality issues with no runtime impact. Safe to deploy.

Findings(2)

FINDING-1 quality low

Test suite lacks randomized or differential testing

The crate ships a small set of unit tests (src/lib.rs lines 222-287) covering all-zero, all-0xff, mixed, and the Wikipedia reference vector. There are no property tests, fuzz tests, or differential tests against a known-good Adler-32 implementation (e.g. zlib's). The chunked 4-way algorithm in src/algo.rs is non-trivial and the per-chunk overflow analysis depends on the CHUNK_SIZE constant being correct; randomized testing comparing the output against a naive byte-at-a-time implementation would defend that invariant. Severity is low because Adler-32 is a stable, well-known checksum and the existing tests do exercise the chunked path at boundary sizes (1024, 1MB).

FINDING-2 quality low

README has stale crate-name references

README.md is the renamed-fork's README but still links to the upstream adler crate's crates.io badge, docs.rs badge, and CI workflow URL (jonas-schievink/adler). src/lib.rs line 11 also points html_root_url at https://docs.rs/adler2/2.0.0 rather than 2.0.1. These are cosmetic and don't affect crate behaviour; consumers reading docs.rs will see the correct documentation regardless.

Annotations(4)

Cargo.toml

Cargo.toml, line 20-20

build = false

build = false confirms no build script. No build.rs in the crate. Justifies has-build-exec = false.

Cargo.toml, line 72-75

[features]
default = ["std"]
rustc-dep-of-std = ["core"]
std = []

Default feature is std. The std feature is empty; the only optional dependency, rustc-std-workspace-core, is gated behind rustc-dep-of-std for libstd integration only and is not a feature pulled by default consumers.

README.md

README.md, line 5-7

[![crates.io](https://img.shields.io/crates/v/adler.svg)](https://crates.io/crates/adler)
[![docs.rs](https://docs.rs/adler/badge.svg)](https://docs.rs/adler/)
![CI](https://github.com/jonas-schievink/adler/workflows/CI/badge.svg)

Badges still reference the upstream adler crate (crates.io/crates/adler, docs.rs/adler, jonas-schievink/adler CI workflow). See FINDING-2.

src/algo.rs

src/algo.rs, line 52-108

        const MOD: u32 = 65521;
        const CHUNK_SIZE: usize = 5552 * 4;

        let mut a = u32::from(self.a);
        let mut b = u32::from(self.b);
        let mut a_vec = U32X4([0; 4]);
        let mut b_vec = a_vec;

        let (bytes, remainder) = bytes.split_at(bytes.len() - bytes.len() % 4);

        // iterate over 4 bytes at a time
        let chunk_iter = bytes.chunks_exact(CHUNK_SIZE);
        let remainder_chunk = chunk_iter.remainder();
        for chunk in chunk_iter {
            for byte_vec in chunk.chunks_exact(4) {
                let val = U32X4::from(byte_vec);
                a_vec += val;
                b_vec += a_vec;
            }

            b += CHUNK_SIZE as u32 * a;
            a_vec %= MOD;
            b_vec %= MOD;
            b %= MOD;
        }
        // special-case the final chunk because it may be shorter than the rest
        for byte_vec in remainder_chunk.chunks_exact(4) {
            let val = U32X4::from(byte_vec);
            a_vec += val;
            b_vec += a_vec;
        }
        b += remainder_chunk.len() as u32 * a;
        a_vec %= MOD;
        b_vec %= MOD;
        b %= MOD;

        // combine the sub-sum results into the main sum
        b_vec *= 4;
        b_vec.0[1] += MOD - a_vec.0[1];
        b_vec.0[2] += (MOD - a_vec.0[2]) * 2;
        b_vec.0[3] += (MOD - a_vec.0[3]) * 3;
        for &av in a_vec.0.iter() {
            a += av;
        }
        for &bv in b_vec.0.iter() {
            b += bv;
        }

        // iterate over the remaining few bytes in serial
        for &byte in remainder.iter() {
            a += u32::from(byte);
            b += a;
        }

        self.a = (a % MOD) as u16;
        self.b = (b % MOD) as u16;
    }

Implementation of the Adler-32 algorithm using the chunked 4-way parallel technique from Intel's Fletcher-checksum paper. CHUNK_SIZE = 5552 * 4 is sized so that the inner u32 accumulators cannot overflow between modular reductions: the worst-case byte value is 255 across 22208 bytes, and the resulting accumulator stays below 2^32. The per-chunk b += CHUNK_SIZE * a followed by % MOD keeps the scalar accumulator bounded. The tail loop handles up to 3 trailing bytes. Justifies impl-algorithm = true and algorithm-impl-safe = true.

src/lib.rs

src/lib.rs, line 16-16

#![forbid(unsafe_code)]

#![forbid(unsafe_code)] at the crate root. The compiler rejects any unsafe block, unsafe fn, unsafe impl, or unsafe trait in this crate. Justifies uses-unsafe = false.

src/lib.rs, line 204-220

#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn adler32<R: BufRead>(mut reader: R) -> io::Result<u32> {
    let mut h = Adler32::new();
    loop {
        let len = {
            let buf = reader.fill_buf()?;
            if buf.is_empty() {
                return Ok(h.checksum());
            }

            h.write_slice(buf);
            buf.len()
        };
        reader.consume(len);
    }
}

The adler32 function reads bytes from a BufRead until EOF and feeds them into the checksum. The crate itself does not open files or perform filesystem operations; the caller chooses the reader. No filesystem syscalls anywhere in the crate.

src/lib.rs, line 222-287

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn zeroes() {
        assert_eq!(adler32_slice(&[]), 1);
        assert_eq!(adler32_slice(&[0]), 1 | 1 << 16);
        assert_eq!(adler32_slice(&[0, 0]), 1 | 2 << 16);
        assert_eq!(adler32_slice(&[0; 100]), 0x00640001);
        assert_eq!(adler32_slice(&[0; 1024]), 0x04000001);
        assert_eq!(adler32_slice(&[0; 1024 * 1024]), 0x00f00001);
    }

    #[test]
    fn ones() {
        assert_eq!(adler32_slice(&[0xff; 1024]), 0x79a6fc2e);
        assert_eq!(adler32_slice(&[0xff; 1024 * 1024]), 0x8e88ef11);
    }

    #[test]
    fn mixed() {
        assert_eq!(adler32_slice(&[1]), 2 | 2 << 16);
        assert_eq!(adler32_slice(&[40]), 41 | 41 << 16);

        assert_eq!(adler32_slice(&[0xA5; 1024 * 1024]), 0xd5009ab1);
    }

    /// Example calculation from https://en.wikipedia.org/wiki/Adler-32.
    #[test]
    fn wiki() {
        assert_eq!(adler32_slice(b"Wikipedia"), 0x11E60398);
    }

    #[test]
    fn resume() {
        let mut adler = Adler32::new();
        adler.write_slice(&[0xff; 1024]);
        let partial = adler.checksum();
        assert_eq!(partial, 0x79a6fc2e); // from above
        adler.write_slice(&[0xff; 1024 * 1024 - 1024]);
        assert_eq!(adler.checksum(), 0x8e88ef11); // from above

        // Make sure that we can resume computing from the partial checksum via `from_checksum`.
        let mut adler = Adler32::from_checksum(partial);
        adler.write_slice(&[0xff; 1024 * 1024 - 1024]);
        assert_eq!(adler.checksum(), 0x8e88ef11); // from above
    }

    #[cfg(feature = "std")]
    #[test]
    fn bufread() {
        use std::io::BufReader;
        fn test(data: &[u8], checksum: u32) {
            // `BufReader` uses an 8 KB buffer, so this will test buffer refilling.
            let mut buf = BufReader::new(data);
            let real_sum = adler32(&mut buf).unwrap();
            assert_eq!(checksum, real_sum);
        }

        test(&[], 1);
        test(&[0; 1024], 0x04000001);
        test(&[0; 1024 * 1024], 0x00f00001);
        test(&[0xA5; 1024 * 1024], 0xd5009ab1);
    }
}

Unit tests cover empty input, all-zero, all-0xff, mixed bytes, the Wikipedia reference vector ("Wikipedia" -> 0x11E60398), resumption via from_checksum, and the BufRead path across BufReader's 8KB refill boundary. No property tests, fuzz tests, or differential tests against a reference implementation. Justifies algorithm-impl-tested = true (covers documented inputs and a published reference vector) but see FINDING-1 for the testing-depth limitation.

src/lib.rs, line 11-11

#![doc(html_root_url = "https://docs.rs/adler2/2.0.0")]

html_root_url points at /2.0.0 but the crate is 2.0.1. See FINDING-2.