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

base64ct: decode_in_place rejects non-zero trailing bits like decode - #2459

Open
yuxi-liu-wired wants to merge 1 commit into
RustCrypto:masterfrom
yuxi-liu-wired:fix/base64ct-decode-in-place-last-block
Open

yuxi-liu-wired wants to merge 1 commit into
RustCrypto:masterfrom
yuxi-liu-wired:fix/base64ct-decode-in-place-last-block

Conversation

@yuxi-liu-wired

Copy link
Copy Markdown
Contributor

Encoding::decode checks that the last block round-trips (validate_last_block, added in #680 for #679), so an encoding with non-zero trailing bits is rejected. decode_in_place never got that check:

let mut buf = *b"AB==";
Base64::decode(b"AB==", &mut [0u8; 3])   // Err(InvalidEncoding)
Base64::decode_in_place(&mut buf)        // Ok([0x00]), same as for "AA=="

let mut buf = *b"Mi";                    // the #679 example
Base64Unpadded::decode_in_place(&mut buf) // Ok([0x32]), same as for "Mg"

So decode and decode_in_place disagree on which strings are valid Base64, and with decode_in_place several different strings decode to the same bytes. That is exactly what #679 fixed for decode.

The input buffer is overwritten while decoding, so this PR saves its last block (at most 4 bytes, including padding) first, then runs the same validate_last_block on the result.

Tests in tests/proptests.rs:

  • decode_in_place_equiv: a proptest that decode_in_place accepts exactly what decode_vec accepts, with the same output, for Base64 and Base64Unpadded.
  • decode_in_place_rejects_trailing_bits: a regression test for "AB==" / "AB", and a check that "AA==" still decodes.

Both fail on master (minimal failing input: "++").

This PR was produced by AI agents (Claude) while fuzzing base64ct against the base64 crate.

`decode` checks that the last block round-trips (`validate_last_block`,
added in RustCrypto#680 for RustCrypto#679), so an encoding with non-zero trailing bits such as
"AB==" (canonical: "AA==" for 0x00) is rejected. `decode_in_place` never
got that check: it accepted "AB==", "Mi" (unpadded) and similar, returning
the same bytes as the canonical form. So two different strings decoded to
the same output, and `decode` and `decode_in_place` disagreed on what is
valid Base64.

Save the last input block (at most 4 bytes) before it is overwritten and
validate it against the decoded output, as `decode` does.

Test: a proptest that `decode_in_place` and `decode_vec` accept the same
inputs with the same output, and a regression test for "AB==" / "AB".

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