-
Notifications
You must be signed in to change notification settings - Fork 181
tls_codec: 0.4.3 prep #2365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
tls_codec: 0.4.3 prep #2365
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,12 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |
|
|
||
| ## [Unreleased] | ||
|
|
||
| ## 0.4.3 | ||
|
|
||
| - [#2351](https://github.com/RustCrypto/formats/pull/2351): Implement `Size`, `SerializeBytes`, and `DeserializeBytes` for `String` (and `Size`/`SerializeBytes` for `str` / `&str`), encoding the UTF-8 bytes as a `VLByteVec`. Also implement `SerializeBytes` for `ContentLength` and `VLByteSlice`. | ||
| - [#2322](https://github.com/RustCrypto/formats/pull/2322): Add `VLByteVec` and `SecretVLByteVec`, which are `#[serde(transparent)]` wrappers serializing via `serde_bytes`. They produce a much more compact representation in `serde` formats that distinguish byte arrays from sequences of `u8` (e.g. CBOR, MessagePack, bincode). Their `serde` output is not compatible with `VLBytes` / `SecretVLBytes`, but their `Deserialize` impls are backwards-compatible: in self-describing `serde` formats they also accept the legacy `VLBytes` / `SecretVLBytes` encoding (a struct with a `vec` field containing a sequence of `u8`). Deprecate `VLBytes` and `SecretVLBytes` in favour of `VLByteVec` and `SecretVLByteVec`. | ||
| - [#1656](https://github.com/RustCrypto/formats/pull/1656) Add `TlsVarInt` type for variable-length integers. | ||
|
|
||
| ### Fixed | ||
| - [#2348](https://github.com/RustCrypto/formats/pull/2348) Use `write_all` everywhere instead of write to prevent partial writes from going undetected. The `Error::InvalidWriteLength` variant is deprecated as it is no longer returned. | ||
| - [#XXXX](https://github.com/RustCrypto/formats/pull/XXXX) Element-vector deserialization (`Vec<T>`, `TlsVecU*<T>`, `SecretTlsVecU*<T>`), for both the `Deserialize` (`std::io::Read`) and `DeserializeBytes` implementations, now measures actual byte consumption instead of relying on `tls_serialized_len()` and enforces the declared length exactly. This makes the two implementations agree for non-canonical inner encodings (e.g. non-minimal varint lengths) and rejects input whose elements overshoot the declared vector boundary. | ||
| - Element vectors now reject zero-length elements that would otherwise never advance the read cursor, preventing an infinite loop and unbounded allocation on malicious input. | ||
| - Byte-vector deserialization (`TlsByteVecU*`, `VLBytes`, `VLByteVec`) now uses `checked_add` when computing the content range, returning `Error::InvalidVectorLength` instead of overflowing `usize` on targets where the length field is as wide as the pointer width. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| - Read-based byte-vector deserialization (`TlsByteVecU*`, `VLBytes`, `VLByteVec`) no longer eagerly allocates a buffer sized by the untrusted length field, avoiding large allocations from bogus length prefixes. | ||
| - Fixed swapped doc comments on the `Deserializable*` / `Undeserializable*` type aliases generated by `#[conditionally_deserializable]`. | ||
| - Avoid potential overflows on summing up lengths. | ||
|
|
||
| ## 0.4.2 | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,7 +27,7 @@ | |
| //! function returns an `Error::UnknownValue` with a `u64` value of the unknown | ||
| //! type. | ||
| //! | ||
| //! ``` | ||
| //! ```no_run | ||
| //! # #[cfg(feature = "std")] | ||
| //! # { | ||
| //! use tls_codec_derive::{TlsDeserialize, TlsSerialize, TlsSize}; | ||
|
|
@@ -761,6 +761,35 @@ fn define_discriminant_constants( | |
| Ok(quote! { #(#discriminant_constants)* }) | ||
| } | ||
|
|
||
| /// Builds an expression that sums `base` and all `terms` into a `usize`, | ||
| /// guarding against overflow only on targets where it can actually occur. | ||
| /// | ||
| /// On 64-bit targets every length is bounded by addressable memory | ||
| /// (`isize::MAX`), so the sum can't overflow `usize`; a plain, branch-free | ||
| /// addition is emitted to keep the serialization hot path free of checks. On | ||
| /// narrower targets the serialized form of a large structure can carry enough | ||
| /// length-prefix / discriminant overhead to exceed `usize::MAX`, so we saturate | ||
| /// rather than silently wrapping to a small value (a wrapped length would be | ||
| /// written to the wire as a truncated, mismatched length prefix). | ||
|
Comment on lines
+770
to
+773
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if it would be better to panic in the derive macro instead, since saturating could cause an incorrectly short length to be written. This would be caught by a debug assertion in |
||
| /// | ||
| /// This mirrors `tls_codec::len_add` but is emitted inline so the helper does | ||
| /// not need to be part of `tls_codec`'s public API. | ||
| fn sum_lengths(terms: &[TokenStream2], base: TokenStream2) -> TokenStream2 { | ||
| quote! { | ||
| { | ||
| #[cfg(target_pointer_width = "64")] | ||
| let __tls_len: usize = #base #(+ #terms)*; | ||
| #[cfg(not(target_pointer_width = "64"))] | ||
| let __tls_len: usize = { | ||
| let mut __tls_len: usize = #base; | ||
| #(__tls_len = __tls_len.saturating_add(#terms);)* | ||
| __tls_len | ||
| }; | ||
| __tls_len | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[allow(unused_variables)] | ||
| fn impl_tls_size(parsed_ast: TlsStruct) -> TokenStream2 { | ||
| match parsed_ast { | ||
|
|
@@ -779,13 +808,18 @@ fn impl_tls_size(parsed_ast: TlsStruct) -> TokenStream2 { | |
| .iter() | ||
| .map(|p| p.for_trait("Size")) | ||
| .collect::<Vec<_>>(); | ||
| let field_lengths = prefixes | ||
| .iter() | ||
| .zip(members.iter()) | ||
| .map(|(prefix, member)| quote! { #prefix::tls_serialized_len(&self.#member) }) | ||
| .collect::<Vec<_>>(); | ||
| let serialized_len = sum_lengths(&field_lengths, quote! { 0usize }); | ||
| let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); | ||
| quote! { | ||
| impl #impl_generics tls_codec::Size for #ident #ty_generics #where_clause { | ||
| #[inline] | ||
| fn tls_serialized_len(&self) -> usize { | ||
| #(#prefixes::tls_serialized_len(&self.#members) + )* | ||
| 0 | ||
| #serialized_len | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -811,21 +845,35 @@ fn impl_tls_size(parsed_ast: TlsStruct) -> TokenStream2 { | |
| let variant_id = &variant.ident; | ||
| let members = &variant.members; | ||
| let bindings = make_n_ids(members.len()); | ||
| let prefixes = variant.member_prefixes.iter().map(|p| p.for_trait("Size")).collect::<Vec<_>>(); | ||
| let prefixes = variant | ||
| .member_prefixes | ||
| .iter() | ||
| .map(|p| p.for_trait("Size")) | ||
| .collect::<Vec<_>>(); | ||
| let field_lengths = prefixes | ||
| .iter() | ||
| .zip(bindings.iter()) | ||
| .map(|(prefix, binding)| quote! { #prefix::tls_serialized_len(#binding) }) | ||
| .collect::<Vec<_>>(); | ||
| let variant_len = sum_lengths(&field_lengths, quote! { 0usize }); | ||
| quote! { | ||
| #ident::#variant_id { #(#members: #bindings,)* } => 0 #(+ #prefixes::tls_serialized_len(#bindings))*, | ||
| #ident::#variant_id { #(#members: #bindings,)* } => #variant_len, | ||
| } | ||
| }) | ||
| .collect::<Vec<_>>(); | ||
| let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); | ||
| let total_len = sum_lengths( | ||
| &[quote! { field_len }], | ||
| quote! { core::mem::size_of::<#repr>() }, | ||
| ); | ||
| quote! { | ||
| impl #impl_generics tls_codec::Size for #ident #ty_generics #where_clause { | ||
| #[inline] | ||
| fn tls_serialized_len(&self) -> usize { | ||
| let field_len = match self { | ||
| #(#field_arms)* | ||
| }; | ||
| core::mem::size_of::<#repr>() + field_len | ||
| #total_len | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -1414,9 +1462,9 @@ fn impl_conditionally_deserializable(mut annotated_item: ItemStruct) -> TokenStr | |
| quote! { | ||
| #annotated_item | ||
|
|
||
| #[doc = #doc_string_deserializable] | ||
| #annotated_item_visibility type #undeserializable_ident #original_ty_generics = #annotated_item_ident #undeserializable_ty_generics; | ||
| #[doc = #doc_string_undeserializable] | ||
| #annotated_item_visibility type #undeserializable_ident #original_ty_generics = #annotated_item_ident #undeserializable_ty_generics; | ||
| #[doc = #doc_string_deserializable] | ||
| #annotated_item_visibility type #deserializable_ident #original_ty_generics = #annotated_item_ident #deserializable_ty_generics; | ||
|
|
||
| #deserialize_implementation | ||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| #![no_main] | ||
| #![allow(deprecated)] | ||
|
|
||
| //! Round-trip fuzzing of the slice-based [`DeserializeBytes`] path. | ||
| //! | ||
| //! The existing `inverse` target only exercises the `Read`-based | ||
| //! [`Deserialize`] implementation. This one covers `tls_deserialize_bytes` | ||
| //! and additionally checks that both paths agree on a valid serialization. | ||
|
|
||
| use libfuzzer_sys::fuzz_target; | ||
| use tls_codec::{Deserialize, DeserializeBytes, Serialize, Size, VLBytes}; | ||
|
|
||
| fuzz_target!(|expected: VLBytes| { | ||
| let serialized = expected.tls_serialize_detached().unwrap(); | ||
|
|
||
| // Assert that the serialized length matches the predicted length. | ||
| assert_eq!(expected.tls_serialized_len(), serialized.len()); | ||
|
|
||
| // Slice-based deserialization round-trips and consumes all bytes. | ||
| let (got, remainder) = VLBytes::tls_deserialize_bytes(&serialized).unwrap(); | ||
| assert!(remainder.is_empty()); | ||
| assert_eq!(expected, got); | ||
|
|
||
| // The `Read`-based path must agree with the slice-based path. | ||
| let mut read_slice = serialized.as_slice(); | ||
| let got_read = VLBytes::tls_deserialize(&mut read_slice).unwrap(); | ||
| assert!(read_slice.is_empty()); | ||
| assert_eq!(expected, got_read); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.