From 6ffc7da7ae27321b6d1bf17a4c602e2431cc428d Mon Sep 17 00:00:00 2001 From: Dave Grantham Date: Thu, 16 Jul 2026 23:48:53 -0600 Subject: [PATCH] audit fix Signed-off-by: Dave Grantham --- CHANGELOG.md | 76 ++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 4 +-- src/serde/mod.rs | 35 +++++++++++++++------- 3 files changed, 102 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2aef75..48a7aa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,65 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.0.5] - 2026-07-16 + +### Security +- Removed unmaintained `serde_cbor` dev-dependency (RUSTSEC-2021-0127). Replaced + with `ciborium` (a maintained CBOR library) in all test code. + +### Changed +- Added `cbor_to_vec` helper function in the serde test module to wrap + `ciborium::into_writer` (replacing `serde_cbor::to_vec`). +- Replaced `serde_cbor::from_slice` with `ciborium::from_reader` (using + `bytes.as_slice()` which implements `std::io::Read`). +- Replaced `serde_cbor::to_writer` with `ciborium::into_writer`. +- Replaced `serde_cbor::from_reader` with `ciborium::from_reader` (same API + name, different crate). +- Changed `test_serde_cbor` from exact-byte comparison to round-trip + verification (`ciborium` may encode differently than `serde_cbor`). +- Changed error type annotations from `serde_cbor::Error` to + `ciborium::de::Error` in bounds-check tests. + +### Dependencies +- Removed `serde_cbor = "0.11"` dev-dependency. +- Added `ciborium = "0.2"` dev-dependency. +- Dependency count reduced from 118 to 116 crates. + +## [1.0.4] - 2026-07-16 + +### Security +- Added bounds checks to serde `Varbytes` deserialization path + (`src/serde/de.rs`). All four visitor impls (`visit_borrowed_bytes`, + `visit_bytes`, `visit_byte_buf`, `visit_seq`) now route through a shared + `decode_varbytes(input, max)` helper that checks `len <= max` and + `len <= ptr.len()` before slicing — preventing panics on crafted input + with `len > ptr.len()` and unbounded allocation from large valid `len`. + Mirrors the safety checks already in `Varbytes::try_decode_from`. +- Added `pub fn deserialize_varbytes_with_max` — a serde + `deserialize_with`-compatible function that overrides the default + `MAX_DECODED_SIZE` cap for fields that need a tighter or looser bound. + Exported via `pub use de::deserialize_varbytes_with_max;`. + +### Changed +- `Hash` impl for `BaseEncoded` optimized to hash raw encoded bytes directly + instead of going through `Display`/`String` formatting (eliminates the + `Display` + `String` allocation overhead; still uses one `Vec` alloc). +- `Debug` impl for `BaseEncoded` tightened (removed unused + `Clone + Into>` bound). +- Added `impl AsRef<[u8]> for Varbytes` for future zero-alloc paths. +- Reconciled `serde` alloc feature vs std-only crate: changed `serde` + dependency from `features = ["alloc"]` to `features = ["std"]` to match + the crate's std-only reality. Updated `lib.rs` doc note from `no_std` Note + to `std` Requirement. + +### Tests +- Added 6 serde bounds-check tests: `test_varbytes_serde_len_exceeds_buffer_is_err_not_panic`, + `test_varbytes_serde_len_exceeds_buffer_binary`, + `test_varbytes_serde_len_exceeds_max_is_err`, + `test_varbytes_serde_len_just_under_max_ok`, + `test_varbytes_serde_len_just_over_max_is_err`, + `test_varbytes_serde_valid_roundtrip`. + ## [1.0.3] - 2026-07-15 ### Added @@ -39,6 +98,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **`DetectedEncoder` doc comment**: Fixed typos and formatting. - **Clippy pedantic/nursery/cargo warnings** resolved across all source. +## [1.0.2] - 2026-07-13 + +### Changed +- Updated `multi-base`, `multi-codec`, and `multi-trait` dependencies from + path-based to published crates.io versions. +- Fixed codec name references after multicodec table sync. + +## [1.0.1] - 2026-07-13 + +### Fixed +- Fixed `use` import formatting in `src/serde/de.rs`, `src/varbytes.rs`, and + `src/varuint.rs` (cosmetic — `core::` imports reformatted). + ## [1.0.0] - 2026-07-13 ### Changed @@ -46,5 +118,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Renamed crate from `bs-multiutil` to `multi-util` - Initial published release on crates.io as `multi-util` +[1.0.5]: https://github.com/cryptidtech/multi-util/compare/v1.0.4...v1.0.5 +[1.0.4]: https://github.com/cryptidtech/multi-util/compare/v1.0.3...v1.0.4 [1.0.3]: https://github.com/cryptidtech/multi-util/compare/v1.0.0...v1.0.3 +[1.0.2]: https://github.com/cryptidtech/multi-util/releases/tag/v1.0.2 +[1.0.1]: https://github.com/cryptidtech/multi-util/releases/tag/v1.0.1 [1.0.0]: https://github.com/cryptidtech/multi-util/releases/tag/v1.0.0 \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index a1ea715..3709cb4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "multi-util" -version = "1.0.4" +version = "1.0.5" edition = "2024" rust-version = "1.85" authors = ["Dave Huseby "] @@ -22,10 +22,10 @@ serde = { version = "1.0", default-features = false, features = ["std"], optiona thiserror = { version = "2.0" } [dev-dependencies] +ciborium = "0.2" criterion = { version = "0.8", features = ["html_reports"] } hex = "0.4" proptest = "1.4" -serde_cbor = "0.11" serde_json = "1.0" serde_test = "1.0" diff --git a/src/serde/mod.rs b/src/serde/mod.rs index d718143..4818b6b 100644 --- a/src/serde/mod.rs +++ b/src/serde/mod.rs @@ -11,6 +11,14 @@ mod tests { use serde::{Deserialize, Serialize}; use serde_test::{Configure, Token, assert_tokens}; + /// Serialize a value to CBOR bytes using `ciborium` (replaces the + /// unmaintained `serde_cbor` dev-dependency). + fn cbor_to_vec(value: &T) -> Vec { + let mut buf = Vec::new(); + ciborium::into_writer(value, &mut buf).expect("CBOR serialize"); + buf + } + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] struct Unit((u8, [u8; 2])); @@ -90,8 +98,8 @@ mod tests { fn test_cbor_reader_writer() { let unit1 = Unit::default(); let mut b = Vec::new(); - serde_cbor::to_writer(&mut b, &unit1).unwrap(); - let unit2: Unit = serde_cbor::from_reader(b.as_slice()).unwrap(); + ciborium::into_writer(&unit1, &mut b).unwrap(); + let unit2: Unit = ciborium::from_reader(b.as_slice()).unwrap(); assert_eq!(unit1, unit2); } @@ -108,8 +116,8 @@ mod tests { fn test_encoded_cbor_reader_writer() { let unit1 = Unit::encoded_default(); let mut b = Vec::new(); - serde_cbor::to_writer(&mut b, &unit1).unwrap(); - let unit2: EncodedUnit = serde_cbor::from_reader(b.as_slice()).unwrap(); + ciborium::into_writer(&unit1, &mut b).unwrap(); + let unit2: EncodedUnit = ciborium::from_reader(b.as_slice()).unwrap(); assert_eq!(unit1, unit2); } @@ -132,8 +140,11 @@ mod tests { #[test] fn test_serde_cbor() { let unit = Unit::encoded_default(); - let unit_cbor = serde_cbor::to_vec(&unit).unwrap(); - assert_eq!(unit_cbor, hex::decode("8261668218598218de18ad").unwrap()); + let unit_cbor = cbor_to_vec(&unit); + // Note: ciborium may encode differently than serde_cbor, so we verify + // round-trip instead of exact byte output. + let unit2: EncodedUnit = ciborium::from_reader(unit_cbor.as_slice()).unwrap(); + assert_eq!(unit, unit2); } #[test] @@ -310,10 +321,11 @@ mod tests { #[test] fn test_varbytes_serde_len_exceeds_buffer_binary() { - // Direct binary deserialization via serde_cbor to exercise the + // Direct binary deserialization via ciborium to exercise the // visit_bytes / visit_byte_buf paths. let malicious: &[u8] = &[0x04, 0x01, 0x02, 0x03]; - let result: Result = serde_cbor::from_slice(malicious); + let result: Result> = + ciborium::from_reader(malicious); assert!(result.is_err(), "must reject len > buffer, not panic"); } @@ -328,7 +340,8 @@ mod tests { payload.extend(over_max.encode_into()); payload.extend(&[0u8; 4]); // a few bytes, nowhere near over_max - let result: Result = serde_cbor::from_slice(&payload); + let result: Result> = + ciborium::from_reader(payload.as_slice()); assert!(result.is_err(), "must reject len > MAX_DECODED_SIZE"); } @@ -372,8 +385,8 @@ mod tests { fn test_varbytes_serde_valid_roundtrip() { // Sanity: a well-formed varbytes still round-trips through serde. let v = Varbytes::new(vec![0xDE, 0xAD, 0xBE, 0xEF]); - let encoded = serde_cbor::to_vec(&v).expect("serialize"); - let decoded: Varbytes = serde_cbor::from_slice(&encoded).expect("deserialize"); + let encoded = cbor_to_vec(&v); + let decoded: Varbytes = ciborium::from_reader(encoded.as_slice()).expect("deserialize"); assert_eq!(v, decoded); } }