GeezerVison is showing github.com — Excess Base64 data ignored after padding by default · Issue #145264 · python/cpython
Excess Base64 data ignored after padding by default #145264DescriptionBug report
After adding the ignorechars parameter for the Base64 decoder (see #144001), decoding in non-strict mode is almost equivalent to decoding with ignorechars including all characters. Except for one detail -- in non-strict mode the first valid padding stops decoding. Any following data is silently ignored. This leads to issues like #137687.
This contradicts RFC 4648, section 3.3 which only allows to ignore the pad character if it is present before the end of the encoded data.
Furthermore, such specifications MAY ignore the pad
character, "=", treating it as non-alphabet data, if it is present
before the end of the encoded data. If more than the allowed number
of pad characters is found at the end of the string (e.g., a base 64
string terminated with "==="), the excess pad characters MAY also be
ignored.
b'YW==Jj' and b'YWJ=j' should be decoded to b'abc', not to b'a' or b'ab'.
So, how are we going to fix this issue? We can simply change the behavior by default -- this may be a breaking change, but it is a bugfix, it breaks incorrect behavior. We can start long process of emitting a FutureWarning, and then changing the behavior few releases later. We can add a new option to alter the behavior and start emitting a FutureWarning by default.
In 3.15+ we can simply pass the ignorechars argument to enable RFC 4648 complaining lenient behavior. The question is about the default behavior.
Linked PRs
Reacted by Gregory P. Smith and Alexander Shadchin Activityserhiy-storchaka commented on Feb 27, 2026 For example, using standard CLI utility base64 on Linux:
$ echo YW==Jj | base64 -di
abc
$ echo -n YWJ=j | base64 -di
abc
Using Python:
$ echo -n YW==Jj | python3 -m base64 -d
a
$ echo -n YWJ=j | python3 -m base64 -d
ab added 2 commits that reference this issue on Mar 22, 2026gh-145264: Do not ignore excess Base64 data after the first padded qu… elboulangero commented on May 7, 2026 @serhiy-storchaka I tried the base64 example in various containers and can't reproduce your findings.
debian:latest aka. trixie, which comes with GNU coreutils:
# dpkg -S bin/base64
coreutils: /usr/bin/base64
# dpkg -l | grep coreutils | awk '{print $2 " " $3}'
coreutils 9.7-3
# echo YW==Jj | base64 -di
abase64: invalid input
# echo -n YWJ=j | base64 -di
abbase64: invalid input
Note the leading a or ab in the output, it means that some characters are decoded before the command fails.
ubuntu:latest aka. 26.04, which comes with Rust' uutils:
# dpkg -S bin/base64
coreutils-from-uutils: /usr/bin/base64
# dpkg -l | grep uutils | awk '{print $2 " " $3}'
coreutils-from-uutils 0.0.0~ubuntu25
# base64 --version
base64 (uutils coreutils) 0.8.0
# echo YW==Jj | base64 -di
base64: error: invalid input
# echo -n YWJ=j | base64 -di
base64: error: invalid input
And Alpine 3.23.4, which comes with busybox:
# realpath /bin/base64
/bin/busybox
# busybox | head -1
BusyBox v1.37.0 (2025-12-16 14:19:28 UTC) multi-call binary.
# echo YW==Jj | base64 -di
abase64: truncated input
# echo -n YWJ=j | base64 -di
abbase64: truncated input
What distro did you use for your tests?
In my tests both coreutils and busybox decoded a and ab which is exactly what Python did. Am I missing something? serhiy-storchaka commented on May 7, 2026 @shadchin Interesting. It seems that all these examples worked when the length of the encoded data was not the multiple of 3. They worked for tokens which have fixed length.
@elboulangero It was on Ubuntu 25.10.
I believe that the base64 implementation in Ubuntu 26.04 does not comply with RFC 4648, because it a) does not error '=' not at the end even without the -i option, b) interprets '=' not at the end as the padding instead of ignoring it.
$ echo -n QQ==QQ== | base64 -d | hd
00000000 41 41 |AA|
00000002
$ echo -n QQQQ | base64 -d | hd
00000000 41 04 10 |A..|
00000003
But this is not related to Python implementation. elboulangero commented on May 8, 2026
@elboulangero It was on Ubuntu 25.10.
Trying in Ubuntu 25.10 container then!
# dpkg -S bin/base64
coreutils-from-uutils: /usr/bin/base64
# dpkg -l | grep uutils | awk '{print $2 " " $3}'
coreutils-from-uutils 0.0.0~ubuntu24
# base64 --version
base64 (uutils coreutils) 0.2.2
# echo YW==Jj | base64 -di; echo
abc
# echo -n YWJ=j | base64 -di; echo
abc
So I can confirm your findings now, as you can see! And it also means that Ubuntu 25.10 was an outlier, it was the only one to behave this way.
Ubuntu 25.10 came with uutils 0.2.2, and Ubuntu 26.04 comes with uutils 0.8.0, so it looks like the behavior changed between those 2 versions. elboulangero commented on May 8, 2026
I believe that the base64 implementation in Ubuntu 26.04 does not comply with RFC 464
FWIW I get the same output on a Debian trixie system (GNU coreutils 9.7-3):
# echo -n QQ==QQ== | base64 -d | hd
00000000 41 41 |AA|
00000002
# echo -n QQQQ | base64 -d | hd
00000000 41 04 10 |A..|
00000003
I suppose uutils has to balance between RFC compliance and bug-for-bug compatibility with GNU coreutils.
(note: uutils is the Rust re-implementation of GNU coreutils that is now default in Ubuntu) elboulangero commented on May 8, 2026 @serhiy-storchaka I have one last question. Should this change be backported to older Python releases? I ask as I'm doing CVE backport for Debian. From the outside, it's not clear why it was backported to 3.13 and no further. serhiy-storchaka commented on May 8, 2026 The issue looked like potential security issue, but we did not have real examples of possible attacks, so it was not classified as security issue. This change can potentially break the user code (and it turned out that it really breaks the user code), so it was safer to only apply it to maintained versions. If something goes wrong, it will be easier to revert or alter it in maintained versions. serhiy-storchaka commented on May 9, 2026 elboulangero commented on May 11, 2026
MetadataMetadataDevelopmentNo branches or pull requestsIssue actions
|