Sitelet https://github.com/RustCrypto/formats/pull/2460
Skip to content

base32ct: reject non-zero trailing bits in the last symbol - #2460

Open
yuxi-liu-wired wants to merge 1 commit into
RustCrypto:masterfrom
yuxi-liu-wired:fix/base32ct-reject-trailing-bits
Open

yuxi-liu-wired wants to merge 1 commit into
RustCrypto:masterfrom
yuxi-liu-wired:fix/base32ct-reject-trailing-bits

Conversation

@yuxi-liu-wired

Copy link
Copy Markdown
Contributor

RFC 4648 3.5 requires the unused low bits of the last symbol of a partial block to be zero ("canonical encoding"). Encoding::decode ignores them, so several strings decode to the same bytes:

Base32Unpadded::decode_vec("me")        // Ok(b"a")  canonical: 0x61 = 01100 001|00
Base32Unpadded::decode_vec("mf")        // Ok(b"a")  last symbol 00101, trailing bits 01
Base32Unpadded::decode_vec("mh")        // Ok(b"a")  trailing bits 11
Base32::decode_vec("mf======")          // Ok(b"a")

base64ct rejects the equivalent Base64 inputs since #680 (for #679).

A remainder of 2, 4, 5 or 7 symbols leaves 2, 4, 1 or 3 unused bits in its last symbol. This PR rejects the input when any of those bits is set. The check is folded into the existing err accumulator, which keeps the decoder free of input-dependent branches.

Tests in tests/proptests.rs:

  • decode_is_canonical: a proptest that every accepted input decodes and re-encodes to itself.
  • reject_non_zero_trailing_bits: a regression test for "mf", "mh" and "mf======", and a check that "me" still decodes.

Both fail on master.

This PR was produced by AI agents (Claude) while fuzzing the RustCrypto encoding crates. The same bug class showed up in base64ct's decode_in_place.

RFC 4648 3.5 requires the unused low bits of the last symbol of a partial
block to be zero. `decode` ignored them, so several strings decoded to the
same bytes: "me", "mf" and "mh" all decode to "a" ("me" is the canonical
encoding). base64ct rejects the equivalent Base64 inputs since RustCrypto#680 (RustCrypto#679).

A remainder of 2/4/5/7 symbols leaves 2/4/1/3 unused bits in its last
symbol; reject the input when any of them is set.

Tests: a proptest that every accepted input is the canonical encoding of
its output (decode then encode gives the input back), and a regression
test for "mf" / "mh" / "mf======".

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant