zupt/AUDIT.md

12 KiB
Raw Blame History

Security Audit — Zupt v2.1.7

Date: April 26, 2026 Author: Cristian Cezar Moisés Audit type: Self-audit with formal verification (Jasmin CT proofs, ACSL contracts) and NIST/RFC test vectors Status: No independent third-party audit performed License (audit covers): AGPL-3.0-or-later (Zupt core), GPL-3.0-or-later (VaptVupt codec) — commercial: sac@securityops.co


1. Cryptographic Test Vector Verification — re-validated on v2.1.7

All primitives re-tested against published reference vectors after the v2.1.7 relicense. Identical results to v2.0.0 — as expected, since the relicense did not modify any cryptographic code paths.

Primitive Standard Vectors v2.1.7 Status
SHA-256 FIPS 180-4 3 (empty, "abc", 448-bit) PASS
HMAC-SHA256 RFC 4231 2 (TC2: "Jefe", TC3: 20×0xAA) PASS
SHA3-256 FIPS 202 2 (empty, "abc") PASS
SHAKE-128 FIPS 202 1 (empty, 128-bit output) PASS
X25519 RFC 7748 §6.1 2 (TV1, TV2) PASS
ML-KEM-768 FIPS 203 2 (5-trial roundtrip + implicit rejection) PASS
XXH64 xxHash spec 1 (empty string, seed=0) PASS
Total 13 13/13 PASS

Reproduction: make test-vectors && ./test_vectors

2. Jasmin Constant-Time Verification

Function Purpose v2.1.7 Status
zupt_mac_verify_ct HMAC comparison Linked, CT-proven
zupt_ct_select_32 ML-KEM FO select Linked, CT-proven
zupt_aes256_ctr4 AES-256 4-block pipeline Linked, CT by hardware
zupt_fe_cswap X25519 conditional swap ⚠ Not yet linked (limb representation mismatch: 5×u51 vs 4×u64)
zupt_aes256_blk AES-256 single-block (AES-NI) ⚠ Not yet linked (Jasmin stack offset bug: [rsp+1] instead of [rsp+16])

The two unwired functions remain on the v1.6+ roadmap; their fallback implementations (C masked XOR for fe_cswap, C T-table AES for aes256_blk) are functionally correct and continue to be the production code path.

3. ACSL Formal Annotations

19 security-critical functions annotated with requires/ensures/assigns contracts. Target: frama-c -wp -wp-rte -wp-model Typed+Cast

4. Security Hardening — current status

Feature v2.1.7 Status
mlock() key protection Active
Buffer canaries (keyring) Active
Always-decrypt timing mitigation Active
AFL++ fuzz harnesses Available (make fuzz-build)
TOCTOU mitigation (fstat on fd, not stat on path) Active (since v2.1.4)
X25519 scalar wipe via volatile Active (since v2.1.4)
Password strength warnings Active (since v2.1.6)
Block-level deduplication with content verification Active (since v2.1.5)

5. VaptVupt Codec Tests — re-validated on v2.1.7

Test v2.1.7 Status
Roundtrip text mode 0 / 1 / 2 (each 64 KB) PASS (3)
Roundtrip binary mode 1 (128 KB) PASS
Incompressible fallback to store PASS
Empty / small input (3 bytes) PASS (2)
vv_xxh64zupt_xxh64 alias PASS
Roundtrip large 2 MB (mode 1) PASS
RLE-like data roundtrip PASS
window_log=20 roundtrip PASS
Total 11/11 PASS

6. Test Suite Summary — v2.1.7

Suite Tests v2.1.7 Result What It Covers
NIST/RFC vectors 13 13/13 PASS See §1
VaptVupt unit 11 11/11 PASS See §5
Regression 22 22/22 PASS All codecs, modes, encryption, edge cases, corruption detection
Multi-threaded 14 13/14 PASS N=1/2/4/8, large files, MT+encryption
Post-quantum 10 10/10 PASS Keygen, PQ encrypt/decrypt, wrong key, password compat, PQ+MT, 2 MB
Total 70 69/70 PASS

The single failing case is Solid+N=8 (3 mismatches) — see §11 (newly-recorded known limitation).

Reproduction: make test-all


7. Memory Safety — re-validated on v2.1.7

Tool Command v2.1.7 Result
AddressSanitizer (plain) ./zupt_asan compress / extract Zero errors
AddressSanitizer (encrypted, PBKDF2) ./zupt_asan compress -p Zero errors
AddressSanitizer (PQ hybrid) ./zupt_asan compress --pq / extract --pq Zero errors
AddressSanitizer (multi-threaded N=4) ./zupt_asan compress -t 4 Zero errors
AddressSanitizer (deduplication) ./zupt_asan compress --dedup Zero errors
UndefinedBehaviorSanitizer Built with -fsanitize=address,undefined Zero errors

Reproduction:

make test-asan
./zupt_asan compress /tmp/t.zupt /path/to/data/
./zupt_asan extract -o /tmp/out/ /tmp/t.zupt
./zupt_asan keygen -o /tmp/k.key
./zupt_asan keygen --pub -o /tmp/pub.key -k /tmp/k.key
./zupt_asan compress --pq /tmp/pub.key /tmp/pq.zupt /path/to/data/
./zupt_asan extract --pq /tmp/k.key -o /tmp/pqout/ /tmp/pq.zupt
./zupt_asan compress -t 4 /tmp/mt.zupt /path/to/data/
./zupt_asan compress --dedup /tmp/d.zupt /path/to/data/

8. Compiler Warning Audit

Compiler Flags v2.1.7 Warnings
GCC 13.x -Wall -Wextra -O2 -std=c11 Zero
GCC 13.x (pedantic) -Wall -Wextra -Wpedantic -O2 -std=c11 Zero

9. Constant-Time Analysis — unchanged from v2.0.0

Function Location CT Method Jasmin Verified? Risk Level
HMAC comparison zupt_crypto.c 4×u64 XOR accumulation Yeszupt_mac_verify_ct linked None (Jasmin proven)
ML-KEM FO select zupt_mlkem.c 4×u64 masked select Yeszupt_ct_select_32 linked None (Jasmin proven)
AES-256 CTR (4-block) zupt_aes256.c (AES-NI dispatch) Hardware AES-NI + AVX Yeszupt_aes256_ctr4 linked None (CT by hardware)
ML-KEM NTT butterfly zupt_mlkem.c Montgomery reduction (branchless) No Low
ML-KEM CBD sampling zupt_mlkem.c Bitwise operations only No Low
X25519 fe_cswap zupt_x25519.c Masked XOR swap No (limb mismatch) Low (C is branchless)
X25519 Montgomery ladder zupt_x25519.c Fixed 255 iterations No Low
AES-256 encrypt (C fallback) zupt_aes256.c Table-based S-box No HIGH on shared HW without AES-NI
SHA-256 zupt_sha256.c Table-based constants No Low (not secret-indexed)
Keccak-f[1600] zupt_keccak.c Bitwise XOR/ROT only No None
Key wipe zupt_crypto.c explicit_bzero / volatile No Low

Jasmin Assembly Verification

Functions confirmed active in binary via nm:

T zupt_mac_verify_ct      ← Jasmin assembly, CT proven
T zupt_ct_select_32       ← Jasmin assembly, CT proven
T zupt_aes256_ctr4        ← Jasmin assembly, CT by hardware

Assembly generated by jasminc 2026.03.0. Constant-time enforced by Jasmin type system: secret-typed variables cannot flow into branch conditions or memory indices.


10. Bugs Found and Fixed (v0.5.1 → v2.1.7)

Bug Severity Version Fixed Impact
Huffman Kraft-inequality violation Critical v0.5.1 Data corruption on specific inputs
Heap-buffer-overflow in LZ match finder Critical v0.5.1 Potential code execution
rand() CSPRNG fallback Critical v0.5.1 Predictable encryption keys
ML-KEM poly_basemul OOB Critical v1.0.0 Buffer overread in NTT
ML-KEM missing poly_tomont Critical v1.0.0 Public key in wrong domain
ML-KEM inverted FO cmov Critical v1.0.0 Always selected rejection key
ML-KEM inv_ntt wrong table High v1.0.0 NTT roundtrip failure
PQ nonce mismatch High v1.0.0 Encrypt/decrypt used different nonces
X25519 AA + a24*E formula High v1.1.0 Wrong curve, not interoperable
copy_match_scalar overlap corruption High v2.0.0 Silent corruption on offset 47 (8-byte bulk overlap)
vva_encode_sequences heap overflow High v2.0.0 nseq×5+1 sizing insufficient on large literal runs
SIGILL on Linux Mint (AES dispatch) High v2.0.0 Jasmin AES uses VEX → needs OSXSAVE+XCR0, not just AES-NI
TOCTOU race in get_device_size High v2.1.4 Path swap between stat and open; CodeQL alert
LZHP prediction encoding desync High v2.1.3 Data corruption on structured data

11. Known Limitations

Limitation Impact Mitigation Status
Solid mode + multi-threaded archives over ~3 MB ⚠ NEW Decompression error "Solid block N decompression failed" on extract. Reproduces on v2.1.6 and v2.1.7 (not introduced by relicense). Compression succeeds; extraction fails on a specific block. The CLI clamps --solid to N=1 with a stderr note; this bug appears in the inner solid-stream framing when the on-disk archive happens to span ≥4 blocks of ~512 KB each. Use plain --solid (without -t >1) for solid archives; non-solid MT works correctly. Investigation in progress in zupt_format.c:14281452 (block_seq accounting around encryption-header consumption). Open — pre-existing, not relicense-induced
Table-based AES (C fallback) Cache-timing on shared hardware Jasmin AES-NI 4-block pipeline (zupt_aes256_ctr4) IS linked and is the default on x86_64+AVX. Single-block Jasmin (zupt_aes256_blk) has stack offset bug. Partial — 4-block path covered
Table-based SHA-256 Theoretical cache-timing Not used on secret-indexed data Accepted
PBKDF2 not quantum-safe Quantum password brute-force Use --pq mode Documented
No fuzzing performed Undiscovered bugs AFL++ harnesses available via make fuzz-build; not yet executed in production Planned
No independent audit Self-assessed only Open source under AGPL-3.0 + Jasmin formal proofs + commercial support available at sac@securityops.co Planned
X25519 Jasmin not linked C fallback for fe_cswap C is branchless but compiler-dependent. Limb mismatch (Jasmin: 4×u64, C: 5×u51) is the blocker. Open

12. v2.1.7 Relicense + VaptVupt Upgrade — Audit Implications

The v2.1.7 release does two distinct things, both of which were validated separately:

(A) Relicense from MIT to AGPL-3.0-or-later (Zupt core only). VaptVupt was already GPL-3.0-or-later in upstream and remains so. The relicense touches only license metadata (SPDX headers, copyright lines, LICENSE files, --help strings, README badges, packaging metadata). No cryptographic, compression, threading, encryption, or control-flow code was modified by the relicense itself.

(B) Upgrade VaptVupt 2.40.0 → 2.46.1. The 12 VaptVupt files (src/vv_*.c, src/vaptvupt_api.c, include/vaptvupt*.h, include/vv_*.h) were replaced from upstream master at github.com/cristiancmoises/vaptvupt. Public API and vv_options_t are byte-identical; no caller changes required. Most relevantly, v2.46.1 includes a memory-safety fix for a 16,384-byte leak on three decoder error paths in vva_decode_sequences_impl — closing a denial-of-service vector where many malformed compressed frames could exhaust memory.

Audit results on the upgraded + relicensed tree:

  • All NIST/RFC test vectors continue to pass on v2.1.7.
  • All ASAN/UBSAN sweeps continue to be clean on v2.1.7.
  • The single MT failing case (Solid+N=8) is verified pre-existing on v2.1.6 by stashing v2.1.7 changes, rebuilding the v2.1.6 tree, and reproducing the same failure on the same fixture — it is independent of both the relicense and the codec upgrade.

The relicense formally establishes a commercial-licensing channel at sac@securityops.co for users whose use case is incompatible with AGPL's source-disclosure obligations.


13. Reproduction

# Full validation
make clean && make
make test-all       # NIST vectors + VV unit + regression + threaded + PQ
make test-asan      # AddressSanitizer build
# (run ASAN sweep manually per §7)

# Spot-check the relicense
./zupt --version           # should print: zupt 2.1.7
./zupt 2>&1 | grep -i lic  # should print: License: AGPL-3.0-or-later (commercial: sac@securityops.co)
grep -c "SPDX-License-Identifier" src/*.c src/*.h include/*.h tests/*.c
# (should be > 30, all sources covered)

© 2026 Cristian Cezar Moisés — AGPL-3.0-or-later Commercial licensing: sac@securityops.co