Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions Lib/test/test_base64.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,12 +311,12 @@ def test_b64decode_padding_error(self):

def test_b64decode_invalid_chars(self):
# issue 1466065: Test some invalid characters.
tests = ((b'%3d==', b'\xdd', b'%$'),
(b'$3d==', b'\xdd', b'%$'),
tests = ((b'%3Q==', b'\xdd', b'%$'),
(b'$3Q==', b'\xdd', b'%$'),
(b'[==', b'', b'[='),
(b'YW]3=', b'am', b']'),
(b'3{d==', b'\xdd', b'{}'),
(b'3d}==', b'\xdd', b'{}'),
(b'YW]0=', b'am', b']'),
(b'3{Q==', b'\xdd', b'{}'),
(b'3Q}==', b'\xdd', b'{}'),
(b'@@', b'', b'@!'),
(b'!', b'', b'@!'),
(b"YWJj\n", b"abc", b'\n'),
Expand Down
128 changes: 99 additions & 29 deletions Lib/test/test_binascii.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,8 @@ def assertExcessPadding(*args):
def assertInvalidLength(*args):
_assertRegexTemplate(r'(?i)Invalid.+number of data characters', *args)

assertExcessPadding(b'ab===', b'i')
assertExcessPadding(b'ab====', b'i')
assertExcessPadding(b'aQ===', b'i')
assertExcessPadding(b'aQ====', b'i')
assertExcessPadding(b'abc==', b'i\xb7')
assertExcessPadding(b'abc===', b'i\xb7')
assertExcessPadding(b'abc====', b'i\xb7')
Expand All @@ -201,7 +201,7 @@ def assertInvalidLength(*args):
assertLeadingPadding(b'====abcd', b'i\xb7\x1d')
assertLeadingPadding(b'=====abcd', b'i\xb7\x1d')

assertInvalidLength(b'a=b==', b'i')
assertInvalidLength(b'a=Q==', b'i')
assertInvalidLength(b'a=bc=', b'i\xb7')
assertInvalidLength(b'a=bc==', b'i\xb7')
assertInvalidLength(b'a=bcd', b'i\xb7\x1d')
Expand Down Expand Up @@ -241,17 +241,17 @@ def assertNonBase64Data(data, expected, ignorechars):
self.assertEqual(binascii.a2b_base64(data, strict_mode=False, ignorechars=b''),
expected)

assertNonBase64Data(b'\nab==', b'i', ignorechars=b'\n')
assertNonBase64Data(b'ab:(){:|:&};:==', b'i', ignorechars=b':;(){}|&')
assertNonBase64Data(b'a\nb==', b'i', ignorechars=b'\n')
assertNonBase64Data(b'a\x00b==', b'i', ignorechars=b'\x00')
assertNonBase64Data(b'ab:==', b'i', ignorechars=b':')
assertNonBase64Data(b'ab=:=', b'i', ignorechars=b':')
assertNonBase64Data(b'ab==:', b'i', ignorechars=b':')
assertNonBase64Data(b'\naQ==', b'i', ignorechars=b'\n')
assertNonBase64Data(b'aQ:(){:|:&};:==', b'i', ignorechars=b':;(){}|&')
assertNonBase64Data(b'a\nQ==', b'i', ignorechars=b'\n')
assertNonBase64Data(b'a\x00Q==', b'i', ignorechars=b'\x00')
assertNonBase64Data(b'aQ:==', b'i', ignorechars=b':')
assertNonBase64Data(b'aQ=:=', b'i', ignorechars=b':')
assertNonBase64Data(b'aQ==:', b'i', ignorechars=b':')
assertNonBase64Data(b'abc=:', b'i\xb7', ignorechars=b':')
assertNonBase64Data(b'ab==\n', b'i', ignorechars=b'\n')
assertNonBase64Data(b'a\nb==', b'i', ignorechars=bytearray(b'\n'))
assertNonBase64Data(b'a\nb==', b'i', ignorechars=memoryview(b'\n'))
assertNonBase64Data(b'aQ==\n', b'i', ignorechars=b'\n')
assertNonBase64Data(b'a\nQ==', b'i', ignorechars=bytearray(b'\n'))
assertNonBase64Data(b'a\nQ==', b'i', ignorechars=memoryview(b'\n'))

# Same cell in the cache: '\r' >> 3 == '\n' >> 3.
data = self.type2test(b'\r\n')
Expand Down Expand Up @@ -334,6 +334,34 @@ def assertInvalidLength(data, strict_mode=True):
assertInvalidLength(b'A\tB\nC ??DE', # only 5 valid characters
strict_mode=False)

def test_base64_nonzero_padding_bits(self):
# https://datatracker.ietf.org/doc/html/rfc4648.html#section-3.5
# Decoders MAY reject encoded data if the pad bits are not zero.

# 2 data chars + "==": last char has 4 padding bits
# 'A' = 0, 'B' = 1 ->000000 000001 ->byte 0x00, leftover 0001 (non-zero)
with self.assertRaises(binascii.Error):
binascii.a2b_base64(self.type2test(b'AB=='), strict_mode=True)
# 'A' = 0, 'P' = 15 ->000000 001111 ->byte 0x00, leftover 1111 (non-zero)
with self.assertRaises(binascii.Error):
binascii.a2b_base64(self.type2test(b'AP=='), strict_mode=True)

# 3 data chars + "=": last char has 2 padding bits
# 'A' = 0, 'A' = 0, 'B' = 1 ->000000 000000 000001 ->bytes 0x00 0x00,
# leftover 01 (non-zero)
with self.assertRaises(binascii.Error):
binascii.a2b_base64(self.type2test(b'AAB='), strict_mode=True)
# 'A' = 0, 'A' = 0, 'D' = 3 ->leftover 11 (non-zero)
with self.assertRaises(binascii.Error):
binascii.a2b_base64(self.type2test(b'AAD='), strict_mode=True)

# Verify that zero padding bits are accepted
binascii.a2b_base64(self.type2test(b'AA=='), strict_mode=True)
binascii.a2b_base64(self.type2test(b'AAA='), strict_mode=True)

# Full quads with no padding have no leftover bits --always valid
binascii.a2b_base64(self.type2test(b'AAAA'), strict_mode=True)

def test_base64_alphabet(self):
alphabet = (b'!"#$%&\'()*+,-012345689@'
b'ABCDEFGHIJKLMNPQRSTUVXYZ[`abcdefhijklmpqr')
Expand Down Expand Up @@ -738,19 +766,19 @@ def assertInvalidLength(*args):
assertExcessData(b"ABCDEFG=H")
assertExcessData(b"432Z====55555555")

assertExcessData(b"BE======EF", b"\t\x08")
assertExcessData(b"BE======EA", b"\t\x08")
assertExcessData(b"BEEF====C", b"\t\x08Q")
assertExcessData(b"BEEFC===AK", b"\t\x08Q\x01")
assertExcessData(b"BEEFC===AI", b"\t\x08Q\x01")
assertExcessData(b"BEEFCAK=E", b"\t\x08Q\x01D")

assertExcessPadding(b"BE=======", b"\t")
assertExcessPadding(b"BE========", b"\t")
assertExcessPadding(b"BEEF=====", b"\t\x08")
assertExcessPadding(b"BEEF======", b"\t\x08")
assertExcessPadding(b"BEEA=====", b"\t\x08")
assertExcessPadding(b"BEEA======", b"\t\x08")
assertExcessPadding(b"BEEFC====", b"\t\x08Q")
assertExcessPadding(b"BEEFC=====", b"\t\x08Q")
assertExcessPadding(b"BEEFCAK==", b"\t\x08Q\x01")
assertExcessPadding(b"BEEFCAK===", b"\t\x08Q\x01")
assertExcessPadding(b"BEEFCAI==", b"\t\x08Q\x01")
assertExcessPadding(b"BEEFCAI===", b"\t\x08Q\x01")
assertExcessPadding(b"BEEFCAKE=", b"\t\x08Q\x01D")
assertExcessPadding(b"BEEFCAKE==", b"\t\x08Q\x01D")
assertExcessPadding(b"BEEFCAKE===", b"\t\x08Q\x01D")
Expand Down Expand Up @@ -790,16 +818,16 @@ def assertInvalidLength(*args):
assertIncorrectPadding(b"BE===", b"\t")
assertIncorrectPadding(b"BE====", b"\t")
assertIncorrectPadding(b"BE=====", b"\t")
assertIncorrectPadding(b"BEEF=", b"\t\x08")
assertIncorrectPadding(b"BEEF==", b"\t\x08")
assertIncorrectPadding(b"BEEF===", b"\t\x08")
assertIncorrectPadding(b"BEEA=", b"\t\x08")
assertIncorrectPadding(b"BEEA==", b"\t\x08")
assertIncorrectPadding(b"BEEA===", b"\t\x08")
assertIncorrectPadding(b"BEEFC=", b"\t\x08Q")
assertIncorrectPadding(b"BEEFC==", b"\t\x08Q")

assertDiscontinuousPadding(b"BE=EF===", b"\t\x08")
assertDiscontinuousPadding(b"BE==EF==", b"\t\x08")
assertDiscontinuousPadding(b"BE=EA===", b"\t\x08")
assertDiscontinuousPadding(b"BE==EA==", b"\t\x08")
assertDiscontinuousPadding(b"BEEF=C==", b"\t\x08Q")
assertDiscontinuousPadding(b"BEEFC=AK", b"\t\x08Q\x01")
assertDiscontinuousPadding(b"BEEFC=AI", b"\t\x08Q\x01")

assertInvalidLength(b"A")
assertInvalidLength(b"ABC")
Expand All @@ -819,10 +847,52 @@ def assertInvalidLength(*args):

assertInvalidLength(b"B=E=====", b"\t")
assertInvalidLength(b"B==E====", b"\t")
assertInvalidLength(b"BEE=F===", b"\t\x08")
assertInvalidLength(b"BEE==F==", b"\t\x08")
assertInvalidLength(b"BEEFCA=K", b"\t\x08Q\x01")
assertInvalidLength(b"BEEFCA=====K", b"\t\x08Q\x01")
assertInvalidLength(b"BEE=A===", b"\t\x08")
assertInvalidLength(b"BEE==A==", b"\t\x08")
assertInvalidLength(b"BEEFCA=I", b"\t\x08Q\x01")
assertInvalidLength(b"BEEFCA=====I", b"\t\x08Q\x01")

def test_base32_nonzero_padding_bits(self):
# https://datatracker.ietf.org/doc/html/rfc4648.html#section-3.5
# Decoders MAY reject encoded data if the pad bits are not zero.

# 2 data chars + "======": last char has 2 padding bits
# 'AB' ->00000 00001 ->byte 0x00, leftover 01 (non-zero)
with self.assertRaises(binascii.Error):
binascii.a2b_base32(self.type2test(b'AB======'))
# 'AD' ->00000 00011 ->byte 0x00, leftover 11 (non-zero)
with self.assertRaises(binascii.Error):
binascii.a2b_base32(self.type2test(b'AD======'))

# 4 data chars + "====": last char has 4 padding bits
# 'AAAB' ->00000 00000 00000 00001 ->bytes 0x00 0x00, leftover 0001
with self.assertRaises(binascii.Error):
binascii.a2b_base32(self.type2test(b'AAAB===='))
# 'AAAP' ->leftover 1111
with self.assertRaises(binascii.Error):
binascii.a2b_base32(self.type2test(b'AAAP===='))

# 5 data chars + "===": last char has 1 padding bit
# 'AAAAB' ->4*00000 + 00001 ->bytes 0x00*3, leftover 1 (non-zero)
with self.assertRaises(binascii.Error):
binascii.a2b_base32(self.type2test(b'AAAAB==='))

# 7 data chars + "=": last char has 3 padding bits
# 'AAAAAAB' ->6*00000 + 00001 ->bytes 0x00*4, leftover 001
with self.assertRaises(binascii.Error):
binascii.a2b_base32(self.type2test(b'AAAAAAB='))
# 'AAAAAAH' ->leftover 111
with self.assertRaises(binascii.Error):
binascii.a2b_base32(self.type2test(b'AAAAAAH='))

# Verify that zero padding bits are accepted
binascii.a2b_base32(self.type2test(b'AA======'))
binascii.a2b_base32(self.type2test(b'AAAA===='))
binascii.a2b_base32(self.type2test(b'AAAAA==='))
binascii.a2b_base32(self.type2test(b'AAAAAAA='))

# Full octet with no padding --always valid
binascii.a2b_base32(self.type2test(b'AAAAAAAA'))

def test_base32_alphabet(self):
alphabet = b'0Aa1Bb2Cc3Dd4Ee5Ff6Gg7Hh8Ii9JjKk'
Expand Down
20 changes: 20 additions & 0 deletions Modules/binascii.c
Original file line number Diff line number Diff line change
Expand Up @@ -902,6 +902,16 @@ binascii_a2b_base64_impl(PyObject *module, Py_buffer *data, int strict_mode,
goto error_end;
}

/* https://datatracker.ietf.org/doc/html/rfc4648.html#section-3.5
* Decoders MAY reject non-zero padding bits. */
if (strict_mode && leftchar != 0) {
state = get_binascii_state(module);
if (state) {
PyErr_SetString(state->Error, "Non-zero padding bits");
}
goto error_end;
}

Py_XDECREF(table_obj);
return PyBytesWriter_FinishWithPointer(writer, bin_data);

Expand Down Expand Up @@ -1652,6 +1662,16 @@ binascii_a2b_base32_impl(PyObject *module, Py_buffer *data,
goto error;
}

/* https://datatracker.ietf.org/doc/html/rfc4648.html#section-3.5
* Decoders MAY reject non-zero padding bits. */
if (leftchar != 0) {
state = get_binascii_state(module);
if (state) {
PyErr_SetString(state->Error, "Non-zero padding bits");
}
goto error;
}

Py_XDECREF(table_obj);
return PyBytesWriter_FinishWithPointer(writer, bin_data);

Expand Down
Loading