Skip to content

Commit 8451e22

Browse files
gpsheadclaude
andcommitted
Reject non-zero padding bits in base64/base32 decoding
Add leftchar validation after the main decode loop in a2b_base64 (strict_mode only) and a2b_base32 (always). Fix existing test data that incidentally had non-zero padding bits to use characters with zero trailing bits while preserving the same decoded output. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a3d50a4 commit 8451e22

File tree

2 files changed

+53
-33
lines changed

2 files changed

+53
-33
lines changed

Lib/test/test_binascii.py

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,8 @@ def assertExcessPadding(*args):
183183
def assertInvalidLength(*args):
184184
_assertRegexTemplate(r'(?i)Invalid.+number of data characters', *args)
185185

186-
assertExcessPadding(b'ab===', b'i')
187-
assertExcessPadding(b'ab====', b'i')
186+
assertExcessPadding(b'aQ===', b'i')
187+
assertExcessPadding(b'aQ====', b'i')
188188
assertExcessPadding(b'abc==', b'i\xb7')
189189
assertExcessPadding(b'abc===', b'i\xb7')
190190
assertExcessPadding(b'abc====', b'i\xb7')
@@ -201,7 +201,7 @@ def assertInvalidLength(*args):
201201
assertLeadingPadding(b'====abcd', b'i\xb7\x1d')
202202
assertLeadingPadding(b'=====abcd', b'i\xb7\x1d')
203203

204-
assertInvalidLength(b'a=b==', b'i')
204+
assertInvalidLength(b'a=Q==', b'i')
205205
assertInvalidLength(b'a=bc=', b'i\xb7')
206206
assertInvalidLength(b'a=bc==', b'i\xb7')
207207
assertInvalidLength(b'a=bcd', b'i\xb7\x1d')
@@ -241,17 +241,17 @@ def assertNonBase64Data(data, expected, ignorechars):
241241
self.assertEqual(binascii.a2b_base64(data, strict_mode=False, ignorechars=b''),
242242
expected)
243243

244-
assertNonBase64Data(b'\nab==', b'i', ignorechars=b'\n')
245-
assertNonBase64Data(b'ab:(){:|:&};:==', b'i', ignorechars=b':;(){}|&')
246-
assertNonBase64Data(b'a\nb==', b'i', ignorechars=b'\n')
247-
assertNonBase64Data(b'a\x00b==', b'i', ignorechars=b'\x00')
248-
assertNonBase64Data(b'ab:==', b'i', ignorechars=b':')
249-
assertNonBase64Data(b'ab=:=', b'i', ignorechars=b':')
250-
assertNonBase64Data(b'ab==:', b'i', ignorechars=b':')
244+
assertNonBase64Data(b'\naQ==', b'i', ignorechars=b'\n')
245+
assertNonBase64Data(b'aQ:(){:|:&};:==', b'i', ignorechars=b':;(){}|&')
246+
assertNonBase64Data(b'a\nQ==', b'i', ignorechars=b'\n')
247+
assertNonBase64Data(b'a\x00Q==', b'i', ignorechars=b'\x00')
248+
assertNonBase64Data(b'aQ:==', b'i', ignorechars=b':')
249+
assertNonBase64Data(b'aQ=:=', b'i', ignorechars=b':')
250+
assertNonBase64Data(b'aQ==:', b'i', ignorechars=b':')
251251
assertNonBase64Data(b'abc=:', b'i\xb7', ignorechars=b':')
252-
assertNonBase64Data(b'ab==\n', b'i', ignorechars=b'\n')
253-
assertNonBase64Data(b'a\nb==', b'i', ignorechars=bytearray(b'\n'))
254-
assertNonBase64Data(b'a\nb==', b'i', ignorechars=memoryview(b'\n'))
252+
assertNonBase64Data(b'aQ==\n', b'i', ignorechars=b'\n')
253+
assertNonBase64Data(b'a\nQ==', b'i', ignorechars=bytearray(b'\n'))
254+
assertNonBase64Data(b'a\nQ==', b'i', ignorechars=memoryview(b'\n'))
255255

256256
# Same cell in the cache: '\r' >> 3 == '\n' >> 3.
257257
data = self.type2test(b'\r\n')
@@ -335,8 +335,8 @@ def assertInvalidLength(data, strict_mode=True):
335335
strict_mode=False)
336336

337337
def test_base64_nonzero_padding_bits(self):
338-
# RFC 4648 § 3.5: Implementations MUST reject encoded data if it
339-
# contains pad bits that are not zero.
338+
# https://datatracker.ietf.org/doc/html/rfc4648.html#section-3.5
339+
# Decoders MAY reject encoded data if the pad bits are not zero.
340340

341341
# 2 data chars + "==": last char has 4 padding bits
342342
# 'A' = 0, 'B' = 1 → 000000 000001 → byte 0x00, leftover 0001 (non-zero)
@@ -766,19 +766,19 @@ def assertInvalidLength(*args):
766766
assertExcessData(b"ABCDEFG=H")
767767
assertExcessData(b"432Z====55555555")
768768

769-
assertExcessData(b"BE======EF", b"\t\x08")
769+
assertExcessData(b"BE======EA", b"\t\x08")
770770
assertExcessData(b"BEEF====C", b"\t\x08Q")
771-
assertExcessData(b"BEEFC===AK", b"\t\x08Q\x01")
771+
assertExcessData(b"BEEFC===AI", b"\t\x08Q\x01")
772772
assertExcessData(b"BEEFCAK=E", b"\t\x08Q\x01D")
773773

774774
assertExcessPadding(b"BE=======", b"\t")
775775
assertExcessPadding(b"BE========", b"\t")
776-
assertExcessPadding(b"BEEF=====", b"\t\x08")
777-
assertExcessPadding(b"BEEF======", b"\t\x08")
776+
assertExcessPadding(b"BEEA=====", b"\t\x08")
777+
assertExcessPadding(b"BEEA======", b"\t\x08")
778778
assertExcessPadding(b"BEEFC====", b"\t\x08Q")
779779
assertExcessPadding(b"BEEFC=====", b"\t\x08Q")
780-
assertExcessPadding(b"BEEFCAK==", b"\t\x08Q\x01")
781-
assertExcessPadding(b"BEEFCAK===", b"\t\x08Q\x01")
780+
assertExcessPadding(b"BEEFCAI==", b"\t\x08Q\x01")
781+
assertExcessPadding(b"BEEFCAI===", b"\t\x08Q\x01")
782782
assertExcessPadding(b"BEEFCAKE=", b"\t\x08Q\x01D")
783783
assertExcessPadding(b"BEEFCAKE==", b"\t\x08Q\x01D")
784784
assertExcessPadding(b"BEEFCAKE===", b"\t\x08Q\x01D")
@@ -818,16 +818,16 @@ def assertInvalidLength(*args):
818818
assertIncorrectPadding(b"BE===", b"\t")
819819
assertIncorrectPadding(b"BE====", b"\t")
820820
assertIncorrectPadding(b"BE=====", b"\t")
821-
assertIncorrectPadding(b"BEEF=", b"\t\x08")
822-
assertIncorrectPadding(b"BEEF==", b"\t\x08")
823-
assertIncorrectPadding(b"BEEF===", b"\t\x08")
821+
assertIncorrectPadding(b"BEEA=", b"\t\x08")
822+
assertIncorrectPadding(b"BEEA==", b"\t\x08")
823+
assertIncorrectPadding(b"BEEA===", b"\t\x08")
824824
assertIncorrectPadding(b"BEEFC=", b"\t\x08Q")
825825
assertIncorrectPadding(b"BEEFC==", b"\t\x08Q")
826826

827-
assertDiscontinuousPadding(b"BE=EF===", b"\t\x08")
828-
assertDiscontinuousPadding(b"BE==EF==", b"\t\x08")
827+
assertDiscontinuousPadding(b"BE=EA===", b"\t\x08")
828+
assertDiscontinuousPadding(b"BE==EA==", b"\t\x08")
829829
assertDiscontinuousPadding(b"BEEF=C==", b"\t\x08Q")
830-
assertDiscontinuousPadding(b"BEEFC=AK", b"\t\x08Q\x01")
830+
assertDiscontinuousPadding(b"BEEFC=AI", b"\t\x08Q\x01")
831831

832832
assertInvalidLength(b"A")
833833
assertInvalidLength(b"ABC")
@@ -847,14 +847,14 @@ def assertInvalidLength(*args):
847847

848848
assertInvalidLength(b"B=E=====", b"\t")
849849
assertInvalidLength(b"B==E====", b"\t")
850-
assertInvalidLength(b"BEE=F===", b"\t\x08")
851-
assertInvalidLength(b"BEE==F==", b"\t\x08")
852-
assertInvalidLength(b"BEEFCA=K", b"\t\x08Q\x01")
853-
assertInvalidLength(b"BEEFCA=====K", b"\t\x08Q\x01")
850+
assertInvalidLength(b"BEE=A===", b"\t\x08")
851+
assertInvalidLength(b"BEE==A==", b"\t\x08")
852+
assertInvalidLength(b"BEEFCA=I", b"\t\x08Q\x01")
853+
assertInvalidLength(b"BEEFCA=====I", b"\t\x08Q\x01")
854854

855855
def test_base32_nonzero_padding_bits(self):
856-
# RFC 4648 § 3.5: Implementations MUST reject encoded data if it
857-
# contains pad bits that are not zero.
856+
# https://datatracker.ietf.org/doc/html/rfc4648.html#section-3.5
857+
# Decoders MAY reject encoded data if the pad bits are not zero.
858858

859859
# 2 data chars + "======": last char has 2 padding bits
860860
# 'AB' → 00000 00001 → byte 0x00, leftover 01 (non-zero)

Modules/binascii.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,16 @@ binascii_a2b_base64_impl(PyObject *module, Py_buffer *data, int strict_mode,
902902
goto error_end;
903903
}
904904

905+
/* https://datatracker.ietf.org/doc/html/rfc4648.html#section-3.5
906+
* Decoders MAY reject non-zero padding bits. */
907+
if (strict_mode && leftchar != 0) {
908+
state = get_binascii_state(module);
909+
if (state) {
910+
PyErr_SetString(state->Error, "Non-zero padding bits");
911+
}
912+
goto error_end;
913+
}
914+
905915
Py_XDECREF(table_obj);
906916
return PyBytesWriter_FinishWithPointer(writer, bin_data);
907917

@@ -1652,6 +1662,16 @@ binascii_a2b_base32_impl(PyObject *module, Py_buffer *data,
16521662
goto error;
16531663
}
16541664

1665+
/* https://datatracker.ietf.org/doc/html/rfc4648.html#section-3.5
1666+
* Decoders MAY reject non-zero padding bits. */
1667+
if (leftchar != 0) {
1668+
state = get_binascii_state(module);
1669+
if (state) {
1670+
PyErr_SetString(state->Error, "Non-zero padding bits");
1671+
}
1672+
goto error;
1673+
}
1674+
16551675
Py_XDECREF(table_obj);
16561676
return PyBytesWriter_FinishWithPointer(writer, bin_data);
16571677

0 commit comments

Comments
 (0)