Issue #27599: Fixed buffer overrun in binascii.b2a_qp() and binascii.a2b_qp().
diff --git a/Lib/test/test_binascii.py b/Lib/test/test_binascii.py
index d40b672..209126b 100644
--- a/Lib/test/test_binascii.py
+++ b/Lib/test/test_binascii.py
@@ -176,35 +176,106 @@
             self.assertEqual(binascii.hexlify(unicode('a', 'ascii')), '61')
 
     def test_qp(self):
+        type2test = self.type2test
+        a2b_qp = binascii.a2b_qp
+        b2a_qp = binascii.b2a_qp
+
+        a2b_qp(data=b"", header=False)  # Keyword arguments allowed
+
         # A test for SF bug 534347 (segfaults without the proper fix)
         try:
-            binascii.a2b_qp("", **{1:1})
+            a2b_qp(b"", **{1:1})
         except TypeError:
             pass
         else:
             self.fail("binascii.a2b_qp(**{1:1}) didn't raise TypeError")
-        self.assertEqual(binascii.a2b_qp("= "), "= ")
-        self.assertEqual(binascii.a2b_qp("=="), "=")
-        self.assertEqual(binascii.a2b_qp("=AX"), "=AX")
-        self.assertRaises(TypeError, binascii.b2a_qp, foo="bar")
-        self.assertEqual(binascii.a2b_qp("=00\r\n=00"), "\x00\r\n\x00")
-        self.assertEqual(
-            binascii.b2a_qp("\xff\r\n\xff\n\xff"),
-            "=FF\r\n=FF\r\n=FF"
-        )
-        self.assertEqual(
-            binascii.b2a_qp("0"*75+"\xff\r\n\xff\r\n\xff"),
-            "0"*75+"=\r\n=FF\r\n=FF\r\n=FF"
-        )
 
-        self.assertEqual(binascii.b2a_qp('\0\n'), '=00\n')
-        self.assertEqual(binascii.b2a_qp('\0\n', quotetabs=True), '=00\n')
-        self.assertEqual(binascii.b2a_qp('foo\tbar\t\n'), 'foo\tbar=09\n')
-        self.assertEqual(binascii.b2a_qp('foo\tbar\t\n', quotetabs=True), 'foo=09bar=09\n')
+        self.assertEqual(a2b_qp(type2test(b"=")), b"")
+        self.assertEqual(a2b_qp(type2test(b"= ")), b"= ")
+        self.assertEqual(a2b_qp(type2test(b"==")), b"=")
+        self.assertEqual(a2b_qp(type2test(b"=\nAB")), b"AB")
+        self.assertEqual(a2b_qp(type2test(b"=\r\nAB")), b"AB")
+        self.assertEqual(a2b_qp(type2test(b"=\rAB")), b"")  # ?
+        self.assertEqual(a2b_qp(type2test(b"=\rAB\nCD")), b"CD")  # ?
+        self.assertEqual(a2b_qp(type2test(b"=AB")), b"\xab")
+        self.assertEqual(a2b_qp(type2test(b"=ab")), b"\xab")
+        self.assertEqual(a2b_qp(type2test(b"=AX")), b"=AX")
+        self.assertEqual(a2b_qp(type2test(b"=XA")), b"=XA")
+        self.assertEqual(a2b_qp(type2test(b"=AB")[:-1]), b"=A")
 
-        self.assertEqual(binascii.b2a_qp('.'), '=2E')
-        self.assertEqual(binascii.b2a_qp('.\n'), '=2E\n')
-        self.assertEqual(binascii.b2a_qp('a.\n'), 'a.\n')
+        self.assertEqual(a2b_qp(type2test(b'_')), b'_')
+        self.assertEqual(a2b_qp(type2test(b'_'), header=True), b' ')
+
+        self.assertRaises(TypeError, b2a_qp, foo="bar")
+        self.assertEqual(a2b_qp(type2test(b"=00\r\n=00")), b"\x00\r\n\x00")
+        self.assertEqual(b2a_qp(type2test(b"\xff\r\n\xff\n\xff")),
+                         b"=FF\r\n=FF\r\n=FF")
+        self.assertEqual(b2a_qp(type2test(b"0"*75+b"\xff\r\n\xff\r\n\xff")),
+                         b"0"*75+b"=\r\n=FF\r\n=FF\r\n=FF")
+
+        self.assertEqual(b2a_qp(type2test(b'\x7f')), b'=7F')
+        self.assertEqual(b2a_qp(type2test(b'=')), b'=3D')
+
+        self.assertEqual(b2a_qp(type2test(b'_')), b'_')
+        self.assertEqual(b2a_qp(type2test(b'_'), header=True), b'=5F')
+        self.assertEqual(b2a_qp(type2test(b'x y'), header=True), b'x_y')
+        self.assertEqual(b2a_qp(type2test(b'x '), header=True), b'x=20')
+        self.assertEqual(b2a_qp(type2test(b'x y'), header=True, quotetabs=True),
+                         b'x=20y')
+        self.assertEqual(b2a_qp(type2test(b'x\ty'), header=True), b'x\ty')
+
+        self.assertEqual(b2a_qp(type2test(b' ')), b'=20')
+        self.assertEqual(b2a_qp(type2test(b'\t')), b'=09')
+        self.assertEqual(b2a_qp(type2test(b' x')), b' x')
+        self.assertEqual(b2a_qp(type2test(b'\tx')), b'\tx')
+        self.assertEqual(b2a_qp(type2test(b' x')[:-1]), b'=20')
+        self.assertEqual(b2a_qp(type2test(b'\tx')[:-1]), b'=09')
+        self.assertEqual(b2a_qp(type2test(b'\0')), b'=00')
+
+        self.assertEqual(b2a_qp(type2test(b'\0\n')), b'=00\n')
+        self.assertEqual(b2a_qp(type2test(b'\0\n'), quotetabs=True), b'=00\n')
+
+        self.assertEqual(b2a_qp(type2test(b'x y\tz')), b'x y\tz')
+        self.assertEqual(b2a_qp(type2test(b'x y\tz'), quotetabs=True),
+                         b'x=20y=09z')
+        self.assertEqual(b2a_qp(type2test(b'x y\tz'), istext=False),
+                         b'x y\tz')
+        self.assertEqual(b2a_qp(type2test(b'x \ny\t\n')),
+                         b'x=20\ny=09\n')
+        self.assertEqual(b2a_qp(type2test(b'x \ny\t\n'), quotetabs=True),
+                         b'x=20\ny=09\n')
+        self.assertEqual(b2a_qp(type2test(b'x \ny\t\n'), istext=False),
+                         b'x =0Ay\t=0A')
+        self.assertEqual(b2a_qp(type2test(b'x \ry\t\r')),
+                         b'x \ry\t\r')
+        self.assertEqual(b2a_qp(type2test(b'x \ry\t\r'), quotetabs=True),
+                         b'x=20\ry=09\r')
+        self.assertEqual(b2a_qp(type2test(b'x \ry\t\r'), istext=False),
+                         b'x =0Dy\t=0D')
+        self.assertEqual(b2a_qp(type2test(b'x \r\ny\t\r\n')),
+                         b'x=20\r\ny=09\r\n')
+        self.assertEqual(b2a_qp(type2test(b'x \r\ny\t\r\n'), quotetabs=True),
+                         b'x=20\r\ny=09\r\n')
+        self.assertEqual(b2a_qp(type2test(b'x \r\ny\t\r\n'), istext=False),
+                         b'x =0D=0Ay\t=0D=0A')
+
+        self.assertEqual(b2a_qp(type2test(b'x \r\n')[:-1]), b'x \r')
+        self.assertEqual(b2a_qp(type2test(b'x\t\r\n')[:-1]), b'x\t\r')
+        self.assertEqual(b2a_qp(type2test(b'x \r\n')[:-1], quotetabs=True),
+                         b'x=20\r')
+        self.assertEqual(b2a_qp(type2test(b'x\t\r\n')[:-1], quotetabs=True),
+                         b'x=09\r')
+        self.assertEqual(b2a_qp(type2test(b'x \r\n')[:-1], istext=False),
+                         b'x =0D')
+        self.assertEqual(b2a_qp(type2test(b'x\t\r\n')[:-1], istext=False),
+                         b'x\t=0D')
+
+        self.assertEqual(b2a_qp(type2test(b'.')), b'=2E')
+        self.assertEqual(b2a_qp(type2test(b'.\n')), b'=2E\n')
+        self.assertEqual(b2a_qp(type2test(b'.\r')), b'=2E\r')
+        self.assertEqual(b2a_qp(type2test(b'.\0')), b'=2E=00')
+        self.assertEqual(b2a_qp(type2test(b'a.\n')), b'a.\n')
+        self.assertEqual(b2a_qp(type2test(b'.a')[:-1]), b'=2E')
 
     def test_empty_string(self):
         # A test for SF bug #1022953.  Make sure SystemError is not raised.
diff --git a/Misc/NEWS b/Misc/NEWS
index 7da118a..84f9e6d 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -42,6 +42,8 @@
 Library
 -------
 
+- Issue #27599: Fixed buffer overrun in binascii.b2a_qp() and binascii.a2b_qp().
+
 - Issue #25969: Update the lib2to3 grammar to handle the unpacking
   generalizations added in 3.5.
 
diff --git a/Modules/binascii.c b/Modules/binascii.c
index 8162045..dce511d 100644
--- a/Modules/binascii.c
+++ b/Modules/binascii.c
@@ -1222,7 +1222,8 @@
                 odata[out++] = '=';
                 in++;
             }
-            else if (((data[in] >= 'A' && data[in] <= 'F') ||
+            else if ((in + 1 < datalen) &&
+                     ((data[in] >= 'A' && data[in] <= 'F') ||
                       (data[in] >= 'a' && data[in] <= 'f') ||
                       (data[in] >= '0' && data[in] <= '9')) &&
                      ((data[in+1] >= 'A' && data[in+1] <= 'F') ||
@@ -1321,7 +1322,8 @@
             (data[in] == '=') ||
             (header && data[in] == '_') ||
             ((data[in] == '.') && (linelen == 0) &&
-             (data[in+1] == '\n' || data[in+1] == '\r' || data[in+1] == 0)) ||
+             (in + 1 == datalen || data[in+1] == '\n' ||
+              data[in+1] == '\r' || data[in+1] == 0)) ||
             (!istext && ((data[in] == '\r') || (data[in] == '\n'))) ||
             ((data[in] == '\t' || data[in] == ' ') && (in + 1 == datalen)) ||
             ((data[in] < 33) &&
@@ -1400,13 +1402,13 @@
             (data[in] == '=') ||
             (header && data[in] == '_') ||
             ((data[in] == '.') && (linelen == 0) &&
-             (data[in+1] == '\n' || data[in+1] == '\r' || data[in+1] == 0)) ||
+             (in + 1 == datalen || data[in+1] == '\n' ||
+              data[in+1] == '\r' || data[in+1] == 0)) ||
             (!istext && ((data[in] == '\r') || (data[in] == '\n'))) ||
             ((data[in] == '\t' || data[in] == ' ') && (in + 1 == datalen)) ||
             ((data[in] < 33) &&
              (data[in] != '\r') && (data[in] != '\n') &&
-             (quotetabs ||
-            (!quotetabs && ((data[in] != '\t') && (data[in] != ' '))))))
+             (quotetabs || ((data[in] != '\t') && (data[in] != ' ')))))
         {
             if ((linelen + 3 )>= MAXLINESIZE) {
                 odata[out++] = '=';