diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index f5a5c4c41..ae963cb24 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,13 +2,13 @@ name: Rust on: push: - branches: [ master ] + branches: [ 0.10.x ] pull_request: - branches: [ master ] + branches: [ 0.10.x ] env: CARGO_TERM_COLOR: always - RUSTFLAGS: '-D warnings' + # RUSTFLAGS: '-D warnings' CARGO_INCREMENTAL: 0 RUST_BACKTRACE: short @@ -46,71 +46,21 @@ jobs: - name: Run cargo fmt run: cargo fmt --check - publish: - runs-on: ubuntu-20.04 + release-plz: + runs-on: ubuntu-latest needs: [tests, clippy, cargo-fmt] - if: github.ref == 'refs/heads/master' - + if: github.ref == 'refs/heads/0.10.x' steps: - - uses: actions/checkout@v2 - with: - # fetch tags for cargo ws publish - # might be a simple `fetch-tags: true` option soon, see https://github.com/actions/checkout/pull/579 - fetch-depth: 0 - - - name: Setup - run: | - git config user.name github-actions - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - cargo install --git https://github.com/miraclx/cargo-workspaces --rev b2d49b9e575e29fd2395352e4d0df47def025039 cargo-workspaces - export GIT_PREVIOUS_TAG=$(git describe --tags --abbrev=0) - echo "GIT_PREVIOUS_TAG=${GIT_PREVIOUS_TAG}" >> $GITHUB_ENV - echo "[ pre run] current latest git tag is \"${GIT_PREVIOUS_TAG}\"" - - - name: Publish to crates.io and tag the commit - id: tag-and-publish - env: - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - run: | - cargo ws publish --all --yes --exact --force '*' \ - --skip-published --no-git-commit --allow-dirty \ - --tag-existing --tag-prefix 'v' \ - --tag-msg 'crates.io snapshot' --tag-msg $'%{\n - %n: https://crates.io/crates/%n/%v}' \ - --no-individual-tags --no-git-push - export GIT_LATEST_TAG=$(git describe --tags --abbrev=0) - echo "GIT_LATEST_TAG=${GIT_LATEST_TAG}" >> $GITHUB_ENV - echo "[post run] current latest git tag is \"${GIT_LATEST_TAG}\"" - echo "::set-output name=tagged::$( [[ "$GIT_LATEST_TAG" == "$GIT_PREVIOUS_TAG" ]] && echo 0 || echo 1 )" - - # returning multi-line outputs gets truncated to include only the first line - # we have to escape the newline chars, runner auto unescapes them later - # https://github.community/t/set-output-truncates-multiline-strings/16852/3 - GIT_TAG_MESSAGE="$(git tag -l --format='%(body)' ${GIT_LATEST_TAG})" - GIT_TAG_MESSAGE="${GIT_TAG_MESSAGE//'%'/'%25'}" - GIT_TAG_MESSAGE="${GIT_TAG_MESSAGE//$'\n'/'%0A'}" - GIT_TAG_MESSAGE="${GIT_TAG_MESSAGE//$'\r'/'%0D'}" - echo "::set-output name=git_tag_message::${GIT_TAG_MESSAGE}" - - - name: Push tags to GitHub (if any) - if: steps.tag-and-publish.outputs.tagged == 1 - run: git push --tags - - - name: Extract release notes - if: steps.tag-and-publish.outputs.tagged == 1 - id: extract-release-notes - uses: ffurrer2/extract-release-notes@c24866884b7a0d2fd2095be2e406b6f260479da8 - - - name: Create release - if: steps.tag-and-publish.outputs.tagged == 1 - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ env.GIT_LATEST_TAG }} - release_name: ${{ env.GIT_LATEST_TAG }} - body: | - ${{ steps.extract-release-notes.outputs.release_notes }} - - #### Crate Links - - ${{ steps.tag-and-publish.outputs.git_tag_message }} + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.CUSTOM_GITHUB_TOKEN }} + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + - name: Run release-plz + uses: MarcoIeni/release-plz-action@v0.5 + env: + # https://marcoieni.github.io/release-plz/github-action.html#triggering-further-workflow-runs + GITHUB_TOKEN: ${{ secrets.CUSTOM_GITHUB_TOKEN }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/Cargo.toml b/Cargo.toml index d35274f34..4714b6ec0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,11 +4,10 @@ members = [ "borsh-derive", "borsh-derive-internal", "borsh-schema-derive-internal", - "fuzz/fuzz-run", - "benchmarks", ] +exclude = [ "fuzz/fuzz-run", "benchmarks" ] -[workspace.metadata.workspaces] +[workspace.package] # shared version of all public crates in the workspace version = "0.10.3" -exclude = [ "fuzz/*", "benchmarks" ] +rust-version = "1.66.0" diff --git a/benchmarks/Cargo.toml b/benchmarks/Cargo.toml index 450bcda23..f48bc623d 100644 --- a/benchmarks/Cargo.toml +++ b/benchmarks/Cargo.toml @@ -13,7 +13,9 @@ bench = false rand_xorshift = "0.2.0" rand = "0.7.0" borsh = { path = "../borsh", default-features = false } -serde = { version = "1.0", features = ["derive"] } +# Building with serde 1.0.204 breaks due to the use of ‘diagnostic’ +# attribute. Remove once MSRV is updated. +serde = { version = "1.0, <1.0.204", features = ["derive"] } speedy-derive = "0.5" speedy = "0.5" diff --git a/borsh-derive-internal/Cargo.toml b/borsh-derive-internal/Cargo.toml index 53b14e527..7670a8a77 100644 --- a/borsh-derive-internal/Cargo.toml +++ b/borsh-derive-internal/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "borsh-derive-internal" -version = "0.0.0" +version.workspace = true +rust-version.workspace = true authors = ["Near Inc "] edition = "2018" license = "Apache-2.0" diff --git a/borsh-derive/Cargo.toml b/borsh-derive/Cargo.toml index 923059f8c..7a04e1b5a 100644 --- a/borsh-derive/Cargo.toml +++ b/borsh-derive/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "borsh-derive" -version = "0.0.0" +version.workspace = true +rust-version.workspace = true authors = ["Near Inc "] edition = "2018" license = "Apache-2.0" @@ -16,8 +17,8 @@ Binary Object Representation Serializer for Hashing proc-macro = true [dependencies] -borsh-derive-internal = { path = "../borsh-derive-internal" } -borsh-schema-derive-internal = { path = "../borsh-schema-derive-internal" } +borsh-derive-internal = { version = "0.10.3", path = "../borsh-derive-internal" } +borsh-schema-derive-internal = { version = "0.10.3", path = "../borsh-schema-derive-internal" } syn = {version = "1", features = ["full", "fold"] } proc-macro-crate = "0.1.5" proc-macro2 = "1" diff --git a/borsh-schema-derive-internal/Cargo.toml b/borsh-schema-derive-internal/Cargo.toml index 9e3ce7c20..9fcf74207 100644 --- a/borsh-schema-derive-internal/Cargo.toml +++ b/borsh-schema-derive-internal/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "borsh-schema-derive-internal" -version = "0.0.0" +version.workspace = true +rust-version.workspace = true authors = ["Near Inc "] edition = "2018" license = "Apache-2.0" diff --git a/borsh/Cargo.toml b/borsh/Cargo.toml index 141609adc..fdbc4ef2a 100644 --- a/borsh/Cargo.toml +++ b/borsh/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "borsh" -version = "0.0.0" +version.workspace = true +rust-version.workspace = true authors = ["Near Inc "] edition = "2018" license = "MIT OR Apache-2.0" @@ -21,7 +22,7 @@ name = "generate_schema_schema" path = "src/generate_schema_schema.rs" [dependencies] -borsh-derive = { path = "../borsh-derive" } +borsh-derive = { version = "0.10.3", path = "../borsh-derive" } hashbrown = ">=0.11,<0.14" bytes = { version = "1", optional = true } diff --git a/borsh/src/de/mod.rs b/borsh/src/de/mod.rs index 862b1632e..9e667c30c 100644 --- a/borsh/src/de/mod.rs +++ b/borsh/src/de/mod.rs @@ -3,7 +3,7 @@ use core::mem::MaybeUninit; use core::{ convert::{TryFrom, TryInto}, hash::{BuildHasher, Hash}, - mem::{forget, size_of}, + mem::size_of, }; #[cfg(any(test, feature = "bytes"))] @@ -377,21 +377,18 @@ where { #[inline] fn deserialize_reader(reader: &mut R) -> Result { + if size_of::() == 0 { + return Err(Error::new( + ErrorKind::InvalidInput, + "Vectors of zero-sized types are not allowed due to deny-of-service concerns on deserialization.", + )); + } + let len = u32::deserialize_reader(reader)?; if len == 0 { Ok(Vec::new()) } else if let Some(vec_bytes) = T::vec_from_reader(len, reader)? { Ok(vec_bytes) - } else if size_of::() == 0 { - let mut result = vec![T::deserialize_reader(reader)?]; - - let p = result.as_mut_ptr(); - unsafe { - forget(result); - let len = len.try_into().map_err(|_| ErrorKind::InvalidInput)?; - let result = Vec::from_raw_parts(p, len, len); - Ok(result) - } } else { // TODO(16): return capacity allocation when we can safely do that. let mut result = Vec::with_capacity(hint::cautious::(len)); @@ -770,6 +767,6 @@ where impl BorshDeserialize for PhantomData { fn deserialize_reader(_: &mut R) -> Result { - Ok(Self::default()) + Ok(PhantomData) } } diff --git a/borsh/src/ser/mod.rs b/borsh/src/ser/mod.rs index 56eb628ce..56697fbf8 100644 --- a/borsh/src/ser/mod.rs +++ b/borsh/src/ser/mod.rs @@ -1,12 +1,13 @@ use core::convert::TryFrom; use core::hash::BuildHasher; use core::marker::PhantomData; +use core::mem::size_of; use crate::maybestd::{ borrow::{Cow, ToOwned}, boxed::Box, collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque}, - io::{ErrorKind, Result, Write}, + io::{Error, ErrorKind, Result, Write}, string::String, vec::Vec, }; @@ -267,6 +268,12 @@ where { #[inline] fn serialize(&self, writer: &mut W) -> Result<()> { + if size_of::() == 0 { + return Err(Error::new( + ErrorKind::InvalidInput, + "Vectors of zero-sized types are not allowed due to deny-of-service concerns on deserialization.", + )); + } self.as_slice().serialize(writer) } } diff --git a/borsh/tests/test_zero_size.rs b/borsh/tests/test_zero_size.rs index b47943d40..ca56b0076 100644 --- a/borsh/tests/test_zero_size.rs +++ b/borsh/tests/test_zero_size.rs @@ -1,11 +1,36 @@ +use borsh::to_vec; use borsh::BorshDeserialize; +use borsh::BorshSerialize; -#[derive(BorshDeserialize, PartialEq, Debug)] -struct A; +#[derive(BorshDeserialize, BorshSerialize, PartialEq, Debug)] +struct A(); #[test] -fn test_deserialize_vector_to_many_zero_size_struct() { +fn test_deserialize_zero_size() { let v = [0u8, 0u8, 0u8, 64u8]; - let a = Vec::::try_from_slice(&v).unwrap(); - assert_eq!(A {}, a[usize::pow(2, 30) - 1]) + let res = Vec::::try_from_slice(&v); + assert!(res.is_err()); +} + +#[test] +fn test_serialize_zero_size() { + let v = vec![A()]; + let res = to_vec(&v); + assert!(res.is_err()); +} + +#[derive(BorshDeserialize, BorshSerialize, PartialEq, Debug)] +struct B(u32); +#[test] +fn test_deserialize_non_zero_size() { + let v = [1, 0, 0, 0, 64, 0, 0, 0]; + let res = Vec::::try_from_slice(&v); + assert!(res.is_ok()); +} + +#[test] +fn test_serialize_non_zero_size() { + let v = vec![B(1)]; + let res = to_vec(&v); + assert!(res.is_ok()); } diff --git a/fuzz/fuzz-run/Cargo.toml b/fuzz/fuzz-run/Cargo.toml index c926c4328..285ce25b1 100644 --- a/fuzz/fuzz-run/Cargo.toml +++ b/fuzz/fuzz-run/Cargo.toml @@ -12,3 +12,8 @@ path = "src/main.rs" [dependencies] honggfuzz = "0.5" borsh = { path = "../../borsh" } +# This is transitive dependency specified here only to limit the +# version. We need to limit the version because building +# serde 1.0.204 breaks due to the use of ‘diagnostic’ attribute. Drop +# this dependency once MSRV is updated. +serde = "1.0, <1.0.204" diff --git a/release-plz.toml b/release-plz.toml new file mode 100644 index 000000000..ed4ab1dc2 --- /dev/null +++ b/release-plz.toml @@ -0,0 +1,16 @@ +[workspace] +# Use `borsh` crate CHANGELOG as top-level one +changelog_update = false + +[[package]] +name = "borsh" +changelog_update = true +changelog_path = "./CHANGELOG.md" + +[[package]] +name = "borsh-fuzz" +publish = false + +[[package]] +name = "benchmarks" +publish = false