This version includes fixes for: * CVE-2020-14422: Hash collisions in IPv4Interface and IPv6Interface * CVE-2020-15523: Python uses invalid DLL path after calling Py_SetPath on Windows This version also includes support for OpenSSL 1.1.x builds that use 'no-deprecated' and '--api=1.1.0'[1], and so this removes the previous OpenSSL-related patches. This also backports fixes for security issues, including: * CVE-2019-20907: Infinite loop in the tarfile module This also updates the setuptools and pip packages to 47.1.0 and 20.1.1, respectively. [1]: https://github.com/python/cpython/pull/20566 Signed-off-by: Jeffery To <jeffery.to@gmail.com>lilik-openwrt-22.03
@ -1,13 +1,22 @@ | |||
diff -Nurp a/pip/_vendor/pep517/wrappers.py b/pip/_vendor/pep517/wrappers.py | |||
--- a/pip/_vendor/pep517/wrappers.py 2019-07-30 20:02:13.000000000 +0800 | |||
+++ b/pip/_vendor/pep517/wrappers.py 2020-04-24 17:23:35.764905235 +0800 | |||
@@ -10,6 +10,9 @@ from . import compat | |||
--- a/pip/_vendor/pep517/wrappers.py 2020-05-19 10:39:38.000000000 +0800 | |||
+++ b/pip/_vendor/pep517/wrappers.py 2020-06-30 20:19:05.495033208 +0800 | |||
@@ -14,11 +14,16 @@ try: | |||
import importlib.resources as resources | |||
_in_proc_script = pjoin(dirname(abspath(__file__)), '_in_process.py') | |||
def _in_proc_script_path(): | |||
- return resources.path(__package__, '_in_process.py') | |||
+ if resources.is_resource(__package__, '_in_process.py'): | |||
+ return resources.path(__package__, '_in_process.py') | |||
+ return resources.path(__package__, '_in_process.pyc') | |||
except ImportError: | |||
@contextmanager | |||
def _in_proc_script_path(): | |||
- yield pjoin(dirname(abspath(__file__)), '_in_process.py') | |||
+ _in_proc_script = pjoin(dirname(abspath(__file__)), '_in_process.py') | |||
+ if not os.path.isfile(_in_proc_script): | |||
+ _in_proc_script = pjoin(dirname(abspath(__file__)), '_in_process.pyc') | |||
+ yield _in_proc_script | |||
+if not os.path.isfile(_in_proc_script): | |||
+ _in_proc_script = pjoin(dirname(abspath(__file__)), '_in_process.pyc') | |||
+ | |||
@contextmanager | |||
def tempdir(): |
@ -1,218 +0,0 @@ | |||
From 991f0176e188227647bf4c993d8da81cf794b3ae Mon Sep 17 00:00:00 2001 | |||
From: Christian Heimes <christian@python.org> | |||
Date: Sun, 25 Feb 2018 20:03:07 +0100 | |||
Subject: [PATCH] bpo-30008: SSL module: emulate tls methods | |||
OpenSSL 1.1 compatility: emulate version specific TLS methods with | |||
SSL_CTX_set_min/max_proto_version(). | |||
--- | |||
.../2018-02-25-20-05-51.bpo-30008.6Bmyhr.rst | 4 + | |||
Modules/_ssl.c | 134 ++++++++++++++---- | |||
2 files changed, 108 insertions(+), 30 deletions(-) | |||
create mode 100644 Misc/NEWS.d/next/Library/2018-02-25-20-05-51.bpo-30008.6Bmyhr.rst | |||
--- /dev/null | |||
+++ b/Misc/NEWS.d/next/Library/2018-02-25-20-05-51.bpo-30008.6Bmyhr.rst | |||
@@ -0,0 +1,4 @@ | |||
+The ssl module no longer uses function that are deprecated since OpenSSL | |||
+1.1.0. The version specific TLS methods are emulated with TLS_method() plus | |||
+SSL_CTX_set_min/max_proto_version(). Pseudo random numbers are generated | |||
+with RAND_bytes(). | |||
--- a/Modules/_ssl.c | |||
+++ b/Modules/_ssl.c | |||
@@ -45,14 +45,6 @@ static PySocketModule_APIObject PySocket | |||
#include <sys/poll.h> | |||
#endif | |||
-/* Don't warn about deprecated functions */ | |||
-#ifdef __GNUC__ | |||
-#pragma GCC diagnostic ignored "-Wdeprecated-declarations" | |||
-#endif | |||
-#ifdef __clang__ | |||
-#pragma clang diagnostic ignored "-Wdeprecated-declarations" | |||
-#endif | |||
- | |||
/* Include OpenSSL header files */ | |||
#include "openssl/rsa.h" | |||
#include "openssl/crypto.h" | |||
@@ -205,6 +197,7 @@ static void _PySSLFixErrno(void) { | |||
#ifndef PY_OPENSSL_1_1_API | |||
/* OpenSSL 1.1 API shims for OpenSSL < 1.1.0 and LibreSSL < 2.7.0 */ | |||
+#define ASN1_STRING_get0_data ASN1_STRING_data | |||
#define TLS_method SSLv23_method | |||
#define TLS_client_method SSLv23_client_method | |||
#define TLS_server_method SSLv23_server_method | |||
@@ -896,7 +889,7 @@ _ssl_configure_hostname(PySSLSocket *sel | |||
goto error; | |||
} | |||
} else { | |||
- if (!X509_VERIFY_PARAM_set1_ip(param, ASN1_STRING_data(ip), | |||
+ if (!X509_VERIFY_PARAM_set1_ip(param, ASN1_STRING_get0_data(ip), | |||
ASN1_STRING_length(ip))) { | |||
_setSSLError(NULL, 0, __FILE__, __LINE__); | |||
goto error; | |||
@@ -1372,8 +1365,9 @@ _get_peer_alt_names (X509 *certificate) | |||
goto fail; | |||
} | |||
PyTuple_SET_ITEM(t, 0, v); | |||
- v = PyUnicode_FromStringAndSize((char *)ASN1_STRING_data(as), | |||
- ASN1_STRING_length(as)); | |||
+ v = PyUnicode_FromStringAndSize( | |||
+ (char *)ASN1_STRING_get0_data(as), | |||
+ ASN1_STRING_length(as)); | |||
if (v == NULL) { | |||
Py_DECREF(t); | |||
goto fail; | |||
@@ -3078,44 +3072,124 @@ _ssl__SSLContext_impl(PyTypeObject *type | |||
long options; | |||
SSL_CTX *ctx = NULL; | |||
X509_VERIFY_PARAM *params; | |||
- int result; | |||
+ int result = 0; | |||
#if defined(SSL_MODE_RELEASE_BUFFERS) | |||
unsigned long libver; | |||
#endif | |||
PySSL_BEGIN_ALLOW_THREADS | |||
- if (proto_version == PY_SSL_VERSION_TLS1) | |||
+ switch (proto_version) { | |||
+#if OPENSSL_VERSION_NUMBER <= 0x10100000L | |||
+ /* OpenSSL < 1.1.0 or not LibreSSL | |||
+ * Use old-style methods for OpenSSL 1.0.2 | |||
+ */ | |||
+#if defined(SSL2_VERSION) && !defined(OPENSSL_NO_SSL2) | |||
+ case PY_SSL_VERSION_SSL2: | |||
+ ctx = SSL_CTX_new(SSLv2_method()); | |||
+ break; | |||
+#endif | |||
+#if defined(SSL3_VERSION) && !defined(OPENSSL_NO_SSL3) | |||
+ case PY_SSL_VERSION_SSL3: | |||
+ ctx = SSL_CTX_new(SSLv3_method()); | |||
+ break; | |||
+#endif | |||
+#if defined(TLS1_VERSION) && !defined(OPENSSL_NO_TLS1) | |||
+ case PY_SSL_VERSION_TLS1: | |||
ctx = SSL_CTX_new(TLSv1_method()); | |||
-#if HAVE_TLSv1_2 | |||
- else if (proto_version == PY_SSL_VERSION_TLS1_1) | |||
+ break; | |||
+#endif | |||
+#if defined(TLS1_1_VERSION) && !defined(OPENSSL_NO_TLS1_1) | |||
+ case PY_SSL_VERSION_TLS1_1: | |||
ctx = SSL_CTX_new(TLSv1_1_method()); | |||
- else if (proto_version == PY_SSL_VERSION_TLS1_2) | |||
+ break; | |||
+#endif | |||
+#if defined(TLS1_2_VERSION) && !defined(OPENSSL_NO_TLS1_2) | |||
+ case PY_SSL_VERSION_TLS1_2: | |||
ctx = SSL_CTX_new(TLSv1_2_method()); | |||
+ break; | |||
#endif | |||
-#ifndef OPENSSL_NO_SSL3 | |||
- else if (proto_version == PY_SSL_VERSION_SSL3) | |||
- ctx = SSL_CTX_new(SSLv3_method()); | |||
+#else | |||
+ /* OpenSSL >= 1.1 or LibreSSL | |||
+ * create context with TLS_method for all protocols | |||
+ * no SSLv2_method in OpenSSL 1.1. | |||
+ */ | |||
+#if defined(SSL3_VERSION) && !defined(OPENSSL_NO_SSL3) | |||
+ case PY_SSL_VERSION_SSL3: | |||
+ ctx = SSL_CTX_new(TLS_method()); | |||
+ if (ctx != NULL) { | |||
+ /* OpenSSL 1.1.0 sets SSL_OP_NO_SSLv3 for TLS_method by default */ | |||
+ SSL_CTX_clear_options(ctx, SSL_OP_NO_SSLv3); | |||
+ if (!SSL_CTX_set_min_proto_version(ctx, SSL3_VERSION)) | |||
+ result = -2; | |||
+ if (!SSL_CTX_set_max_proto_version(ctx, SSL3_VERSION)) | |||
+ result = -2; | |||
+ } | |||
+ break; | |||
#endif | |||
-#ifndef OPENSSL_NO_SSL2 | |||
- else if (proto_version == PY_SSL_VERSION_SSL2) | |||
- ctx = SSL_CTX_new(SSLv2_method()); | |||
+#if defined(TLS1_VERSION) && !defined(OPENSSL_NO_TLS1) | |||
+ case PY_SSL_VERSION_TLS1: | |||
+ ctx = SSL_CTX_new(TLS_method()); | |||
+ if (ctx != NULL) { | |||
+ SSL_CTX_clear_options(ctx, SSL_OP_NO_TLSv1); | |||
+ if (!SSL_CTX_set_min_proto_version(ctx, TLS1_VERSION)) | |||
+ result = -2; | |||
+ if (!SSL_CTX_set_max_proto_version(ctx, TLS1_VERSION)) | |||
+ result = -2; | |||
+ } | |||
+ break; | |||
+#endif | |||
+#if defined(TLS1_1_VERSION) && !defined(OPENSSL_NO_TLS1_1) | |||
+ case PY_SSL_VERSION_TLS1_1: | |||
+ ctx = SSL_CTX_new(TLS_method()); | |||
+ if (ctx != NULL) { | |||
+ SSL_CTX_clear_options(ctx, SSL_OP_NO_TLSv1_1); | |||
+ if (!SSL_CTX_set_min_proto_version(ctx, TLS1_1_VERSION)) | |||
+ result = -2; | |||
+ if (!SSL_CTX_set_max_proto_version(ctx, TLS1_1_VERSION)) | |||
+ result = -2; | |||
+ } | |||
+ break; | |||
+#endif | |||
+#if defined(TLS1_2_VERSION) && !defined(OPENSSL_NO_TLS1_2) | |||
+ case PY_SSL_VERSION_TLS1_2: | |||
+ ctx = SSL_CTX_new(TLS_method()); | |||
+ if (ctx != NULL) { | |||
+ SSL_CTX_clear_options(ctx, SSL_OP_NO_TLSv1_2); | |||
+ if (!SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION)) | |||
+ result = -2; | |||
+ if (!SSL_CTX_set_max_proto_version(ctx, TLS1_2_VERSION)) | |||
+ result = -2; | |||
+ } | |||
+ break; | |||
#endif | |||
- else if (proto_version == PY_SSL_VERSION_TLS) /* SSLv23 */ | |||
+#endif /* OpenSSL >= 1.1 */ | |||
+ case PY_SSL_VERSION_TLS: | |||
+ /* SSLv23 */ | |||
ctx = SSL_CTX_new(TLS_method()); | |||
- else if (proto_version == PY_SSL_VERSION_TLS_CLIENT) | |||
+ break; | |||
+ case PY_SSL_VERSION_TLS_CLIENT: | |||
ctx = SSL_CTX_new(TLS_client_method()); | |||
- else if (proto_version == PY_SSL_VERSION_TLS_SERVER) | |||
+ break; | |||
+ case PY_SSL_VERSION_TLS_SERVER: | |||
ctx = SSL_CTX_new(TLS_server_method()); | |||
- else | |||
- proto_version = -1; | |||
+ break; | |||
+ default: | |||
+ result = -1; | |||
+ break; | |||
+ } | |||
PySSL_END_ALLOW_THREADS | |||
- if (proto_version == -1) { | |||
+ if (result == -1) { | |||
PyErr_SetString(PyExc_ValueError, | |||
"invalid protocol version"); | |||
return NULL; | |||
} | |||
- if (ctx == NULL) { | |||
+ else if (result == -2) { | |||
+ PyErr_SetString(PyExc_ValueError, | |||
+ "protocol configuration error"); | |||
+ return NULL; | |||
+ } | |||
+ else if (ctx == NULL) { | |||
_setSSLError(NULL, 0, __FILE__, __LINE__); | |||
return NULL; | |||
} | |||
@@ -5288,7 +5362,7 @@ PySSL_RAND(int len, int pseudo) | |||
if (bytes == NULL) | |||
return NULL; | |||
if (pseudo) { | |||
- ok = RAND_pseudo_bytes((unsigned char*)PyBytes_AS_STRING(bytes), len); | |||
+ ok = (_PyOS_URandom((unsigned char*)PyBytes_AS_STRING(bytes), len) == 0 ? 1 : 0); | |||
if (ok == 0 || ok == 1) | |||
return Py_BuildValue("NO", bytes, ok == 1 ? Py_True : Py_False); | |||
} |
@ -1,63 +0,0 @@ | |||
--- a/Modules/_ssl.c | |||
+++ b/Modules/_ssl.c | |||
@@ -201,6 +201,11 @@ static void _PySSLFixErrno(void) { | |||
#define TLS_method SSLv23_method | |||
#define TLS_client_method SSLv23_client_method | |||
#define TLS_server_method SSLv23_server_method | |||
+#define X509_getm_notBefore X509_get_notBefore | |||
+#define X509_getm_notAfter X509_get_notAfter | |||
+#define OpenSSL_version_num SSLeay | |||
+#define OpenSSL_version SSLeay_version | |||
+#define OPENSSL_VERSION SSLEAY_VERSION | |||
static int X509_NAME_ENTRY_set(const X509_NAME_ENTRY *ne) | |||
{ | |||
@@ -1724,7 +1729,7 @@ _decode_certificate(X509 *certificate) { | |||
Py_DECREF(sn_obj); | |||
(void) BIO_reset(biobuf); | |||
- notBefore = X509_get_notBefore(certificate); | |||
+ notBefore = X509_getm_notBefore(certificate); | |||
ASN1_TIME_print(biobuf, notBefore); | |||
len = BIO_gets(biobuf, buf, sizeof(buf)-1); | |||
if (len < 0) { | |||
@@ -1741,7 +1746,7 @@ _decode_certificate(X509 *certificate) { | |||
Py_DECREF(pnotBefore); | |||
(void) BIO_reset(biobuf); | |||
- notAfter = X509_get_notAfter(certificate); | |||
+ notAfter = X509_getm_notAfter(certificate); | |||
ASN1_TIME_print(biobuf, notAfter); | |||
len = BIO_gets(biobuf, buf, sizeof(buf)-1); | |||
if (len < 0) { | |||
@@ -3282,7 +3287,7 @@ _ssl__SSLContext_impl(PyTypeObject *type | |||
conservative and assume it wasn't fixed until release. We do this check | |||
at runtime to avoid problems from the dynamic linker. | |||
See #25672 for more on this. */ | |||
- libver = SSLeay(); | |||
+ libver = OpenSSL_version_num(); | |||
if (!(libver >= 0x10001000UL && libver < 0x1000108fUL) && | |||
!(libver >= 0x10000000UL && libver < 0x100000dfUL)) { | |||
SSL_CTX_set_mode(self->ctx, SSL_MODE_RELEASE_BUFFERS); | |||
@@ -6450,10 +6455,10 @@ PyInit__ssl(void) | |||
return NULL; | |||
/* OpenSSL version */ | |||
- /* SSLeay() gives us the version of the library linked against, | |||
+ /* OpenSSL_version_num() gives us the version of the library linked against, | |||
which could be different from the headers version. | |||
*/ | |||
- libver = SSLeay(); | |||
+ libver = OpenSSL_version_num(); | |||
r = PyLong_FromUnsignedLong(libver); | |||
if (r == NULL) | |||
return NULL; | |||
@@ -6463,7 +6468,7 @@ PyInit__ssl(void) | |||
r = Py_BuildValue("IIIII", major, minor, fix, patch, status); | |||
if (r == NULL || PyModule_AddObject(m, "OPENSSL_VERSION_INFO", r)) | |||
return NULL; | |||
- r = PyUnicode_FromString(SSLeay_version(SSLEAY_VERSION)); | |||
+ r = PyUnicode_FromString(OpenSSL_version(OPENSSL_VERSION)); | |||
if (r == NULL || PyModule_AddObject(m, "OPENSSL_VERSION", r)) | |||
return NULL; | |||
@ -0,0 +1,111 @@ | |||
From f56c75ed53dcad4d59dff4377ae463d6b96acd3e Mon Sep 17 00:00:00 2001 | |||
From: "Miss Islington (bot)" | |||
<31488909+miss-islington@users.noreply.github.com> | |||
Date: Mon, 13 Jul 2020 06:05:44 -0700 | |||
Subject: [PATCH] bpo-41288: Fix a crash in unpickling invalid NEWOBJ_EX. | |||
(GH-21458) | |||
Automerge-Triggered-By: @tiran | |||
(cherry picked from commit 4f309abf55f0e6f8950ac13d6ec83c22b8d47bf8) | |||
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> | |||
--- | |||
Lib/test/pickletester.py | 18 ++++++++++++ | |||
.../2020-07-13-15-06-35.bpo-41288.8mn5P-.rst | 2 ++ | |||
Modules/_pickle.c | 29 ++++++++++++++----- | |||
3 files changed, 41 insertions(+), 8 deletions(-) | |||
create mode 100644 Misc/NEWS.d/next/Library/2020-07-13-15-06-35.bpo-41288.8mn5P-.rst | |||
diff --git a/Lib/test/pickletester.py b/Lib/test/pickletester.py | |||
index 9401043d78d18..ff7bbb0c8a9bf 100644 | |||
--- a/Lib/test/pickletester.py | |||
+++ b/Lib/test/pickletester.py | |||
@@ -1170,6 +1170,24 @@ def test_compat_unpickle(self): | |||
self.assertIs(type(unpickled), collections.UserDict) | |||
self.assertEqual(unpickled, collections.UserDict({1: 2})) | |||
+ def test_bad_reduce(self): | |||
+ self.assertEqual(self.loads(b'cbuiltins\nint\n)R.'), 0) | |||
+ self.check_unpickling_error(TypeError, b'N)R.') | |||
+ self.check_unpickling_error(TypeError, b'cbuiltins\nint\nNR.') | |||
+ | |||
+ def test_bad_newobj(self): | |||
+ error = (pickle.UnpicklingError, TypeError) | |||
+ self.assertEqual(self.loads(b'cbuiltins\nint\n)\x81.'), 0) | |||
+ self.check_unpickling_error(error, b'cbuiltins\nlen\n)\x81.') | |||
+ self.check_unpickling_error(error, b'cbuiltins\nint\nN\x81.') | |||
+ | |||
+ def test_bad_newobj_ex(self): | |||
+ error = (pickle.UnpicklingError, TypeError) | |||
+ self.assertEqual(self.loads(b'cbuiltins\nint\n)}\x92.'), 0) | |||
+ self.check_unpickling_error(error, b'cbuiltins\nlen\n)}\x92.') | |||
+ self.check_unpickling_error(error, b'cbuiltins\nint\nN}\x92.') | |||
+ self.check_unpickling_error(error, b'cbuiltins\nint\n)N\x92.') | |||
+ | |||
def test_bad_stack(self): | |||
badpickles = [ | |||
b'.', # STOP | |||
diff --git a/Misc/NEWS.d/next/Library/2020-07-13-15-06-35.bpo-41288.8mn5P-.rst b/Misc/NEWS.d/next/Library/2020-07-13-15-06-35.bpo-41288.8mn5P-.rst | |||
new file mode 100644 | |||
index 0000000000000..3c3adbabf16ff | |||
--- /dev/null | |||
+++ b/Misc/NEWS.d/next/Library/2020-07-13-15-06-35.bpo-41288.8mn5P-.rst | |||
@@ -0,0 +1,2 @@ | |||
+Unpickling invalid NEWOBJ_EX opcode with the C implementation raises now | |||
+UnpicklingError instead of crashing. | |||
diff --git a/Modules/_pickle.c b/Modules/_pickle.c | |||
index 55affb2c7c479..42ce62fc7cdf4 100644 | |||
--- a/Modules/_pickle.c | |||
+++ b/Modules/_pickle.c | |||
@@ -5988,23 +5988,30 @@ load_newobj_ex(UnpicklerObject *self) | |||
} | |||
if (!PyType_Check(cls)) { | |||
- Py_DECREF(kwargs); | |||
- Py_DECREF(args); | |||
PyErr_Format(st->UnpicklingError, | |||
"NEWOBJ_EX class argument must be a type, not %.200s", | |||
Py_TYPE(cls)->tp_name); | |||
- Py_DECREF(cls); | |||
- return -1; | |||
+ goto error; | |||
} | |||
if (((PyTypeObject *)cls)->tp_new == NULL) { | |||
- Py_DECREF(kwargs); | |||
- Py_DECREF(args); | |||
- Py_DECREF(cls); | |||
PyErr_SetString(st->UnpicklingError, | |||
"NEWOBJ_EX class argument doesn't have __new__"); | |||
- return -1; | |||
+ goto error; | |||
+ } | |||
+ if (!PyTuple_Check(args)) { | |||
+ PyErr_Format(st->UnpicklingError, | |||
+ "NEWOBJ_EX args argument must be a tuple, not %.200s", | |||
+ Py_TYPE(args)->tp_name); | |||
+ goto error; | |||
+ } | |||
+ if (!PyDict_Check(kwargs)) { | |||
+ PyErr_Format(st->UnpicklingError, | |||
+ "NEWOBJ_EX kwargs argument must be a dict, not %.200s", | |||
+ Py_TYPE(kwargs)->tp_name); | |||
+ goto error; | |||
} | |||
+ | |||
obj = ((PyTypeObject *)cls)->tp_new((PyTypeObject *)cls, args, kwargs); | |||
Py_DECREF(kwargs); | |||
Py_DECREF(args); | |||
@@ -6014,6 +6021,12 @@ load_newobj_ex(UnpicklerObject *self) | |||
} | |||
PDATA_PUSH(self->stack, obj, -1); | |||
return 0; | |||
+ | |||
+error: | |||
+ Py_DECREF(kwargs); | |||
+ Py_DECREF(args); | |||
+ Py_DECREF(cls); | |||
+ return -1; | |||
} | |||
static int |
@ -0,0 +1,62 @@ | |||
From c55479556db015f48fc8bbca17f64d3e65598559 Mon Sep 17 00:00:00 2001 | |||
From: "Miss Islington (bot)" | |||
<31488909+miss-islington@users.noreply.github.com> | |||
Date: Wed, 15 Jul 2020 05:30:53 -0700 | |||
Subject: [PATCH] [3.8] bpo-39017: Avoid infinite loop in the tarfile module | |||
(GH-21454) (GH-21483) | |||
Avoid infinite loop when reading specially crafted TAR files using the tarfile module | |||
(CVE-2019-20907). | |||
(cherry picked from commit 5a8d121a1f3ef5ad7c105ee378cc79a3eac0c7d4) | |||
Co-authored-by: Rishi <rishi_devan@mail.com> | |||
Automerge-Triggered-By: @encukou | |||
--- | |||
Lib/tarfile.py | 2 ++ | |||
Lib/test/recursion.tar | Bin 0 -> 516 bytes | |||
Lib/test/test_tarfile.py | 7 +++++++ | |||
.../2020-07-12-22-16-58.bpo-39017.x3Cg-9.rst | 1 + | |||
4 files changed, 10 insertions(+) | |||
create mode 100644 Lib/test/recursion.tar | |||
create mode 100644 Misc/NEWS.d/next/Library/2020-07-12-22-16-58.bpo-39017.x3Cg-9.rst | |||
diff --git a/Lib/tarfile.py b/Lib/tarfile.py | |||
index d31b9cbb51d65..7a69e1b1aa544 100755 | |||
--- a/Lib/tarfile.py | |||
+++ b/Lib/tarfile.py | |||
@@ -1241,6 +1241,8 @@ def _proc_pax(self, tarfile): | |||
length, keyword = match.groups() | |||
length = int(length) | |||
+ if length == 0: | |||
+ raise InvalidHeaderError("invalid header") | |||
value = buf[match.end(2) + 1:match.start(1) + length - 1] | |||
# Normally, we could just use "utf-8" as the encoding and "strict" | |||
diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py | |||
index 15324a4e48819..b512168d6ea87 100644 | |||
--- a/Lib/test/test_tarfile.py | |||
+++ b/Lib/test/test_tarfile.py | |||
@@ -397,6 +397,13 @@ def test_premature_end_of_archive(self): | |||
with self.assertRaisesRegex(tarfile.ReadError, "unexpected end of data"): | |||
tar.extractfile(t).read() | |||
+ def test_length_zero_header(self): | |||
+ # bpo-39017 (CVE-2019-20907): reading a zero-length header should fail | |||
+ # with an exception | |||
+ with self.assertRaisesRegex(tarfile.ReadError, "file could not be opened successfully"): | |||
+ with tarfile.open(support.findfile('recursion.tar')) as tar: | |||
+ pass | |||
+ | |||
class MiscReadTestBase(CommonReadTest): | |||
def requires_name_attribute(self): | |||
pass | |||
diff --git a/Misc/NEWS.d/next/Library/2020-07-12-22-16-58.bpo-39017.x3Cg-9.rst b/Misc/NEWS.d/next/Library/2020-07-12-22-16-58.bpo-39017.x3Cg-9.rst | |||
new file mode 100644 | |||
index 0000000000000..ad26676f8b856 | |||
--- /dev/null | |||
+++ b/Misc/NEWS.d/next/Library/2020-07-12-22-16-58.bpo-39017.x3Cg-9.rst | |||
@@ -0,0 +1 @@ | |||
+Avoid infinite loop when reading specially crafted TAR files using the tarfile module (CVE-2019-20907). |
@ -0,0 +1,99 @@ | |||
From 668d321476d974c4f51476b33aaca870272523bf Mon Sep 17 00:00:00 2001 | |||
From: "Miss Islington (bot)" | |||
<31488909+miss-islington@users.noreply.github.com> | |||
Date: Sat, 18 Jul 2020 13:39:12 -0700 | |||
Subject: [PATCH] bpo-39603: Prevent header injection in http methods | |||
(GH-18485) | |||
reject control chars in http method in http.client.putrequest to prevent http header injection | |||
(cherry picked from commit 8ca8a2e8fb068863c1138f07e3098478ef8be12e) | |||
Co-authored-by: AMIR <31338382+amiremohamadi@users.noreply.github.com> | |||
--- | |||
Lib/http/client.py | 15 +++++++++++++ | |||
Lib/test/test_httplib.py | 22 +++++++++++++++++++ | |||
.../2020-02-12-14-17-39.bpo-39603.Gt3RSg.rst | 2 ++ | |||
3 files changed, 39 insertions(+) | |||
create mode 100644 Misc/NEWS.d/next/Security/2020-02-12-14-17-39.bpo-39603.Gt3RSg.rst | |||
diff --git a/Lib/http/client.py b/Lib/http/client.py | |||
index 019380a720318..c2ad0471bfee5 100644 | |||
--- a/Lib/http/client.py | |||
+++ b/Lib/http/client.py | |||
@@ -147,6 +147,10 @@ | |||
# _is_allowed_url_pchars_re = re.compile(r"^[/!$&'()*+,;=:@%a-zA-Z0-9._~-]+$") | |||
# We are more lenient for assumed real world compatibility purposes. | |||
+# These characters are not allowed within HTTP method names | |||
+# to prevent http header injection. | |||
+_contains_disallowed_method_pchar_re = re.compile('[\x00-\x1f]') | |||
+ | |||
# We always set the Content-Length header for these methods because some | |||
# servers will otherwise respond with a 411 | |||
_METHODS_EXPECTING_BODY = {'PATCH', 'POST', 'PUT'} | |||
@@ -1087,6 +1091,8 @@ def putrequest(self, method, url, skip_host=False, | |||
else: | |||
raise CannotSendRequest(self.__state) | |||
+ self._validate_method(method) | |||
+ | |||
# Save the method for use later in the response phase | |||
self._method = method | |||
@@ -1177,6 +1183,15 @@ def _encode_request(self, request): | |||
# ASCII also helps prevent CVE-2019-9740. | |||
return request.encode('ascii') | |||
+ def _validate_method(self, method): | |||
+ """Validate a method name for putrequest.""" | |||
+ # prevent http header injection | |||
+ match = _contains_disallowed_method_pchar_re.search(method) | |||
+ if match: | |||
+ raise ValueError( | |||
+ f"method can't contain control characters. {method!r} " | |||
+ f"(found at least {match.group()!r})") | |||
+ | |||
def _validate_path(self, url): | |||
"""Validate a url for putrequest.""" | |||
# Prevent CVE-2019-9740. | |||
diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py | |||
index 8f0e27a1fb836..5a5fcecbc9c15 100644 | |||
--- a/Lib/test/test_httplib.py | |||
+++ b/Lib/test/test_httplib.py | |||
@@ -364,6 +364,28 @@ def test_headers_debuglevel(self): | |||
self.assertEqual(lines[3], "header: Second: val2") | |||
+class HttpMethodTests(TestCase): | |||
+ def test_invalid_method_names(self): | |||
+ methods = ( | |||
+ 'GET\r', | |||
+ 'POST\n', | |||
+ 'PUT\n\r', | |||
+ 'POST\nValue', | |||
+ 'POST\nHOST:abc', | |||
+ 'GET\nrHost:abc\n', | |||
+ 'POST\rRemainder:\r', | |||
+ 'GET\rHOST:\n', | |||
+ '\nPUT' | |||
+ ) | |||
+ | |||
+ for method in methods: | |||
+ with self.assertRaisesRegex( | |||
+ ValueError, "method can't contain control characters"): | |||
+ conn = client.HTTPConnection('example.com') | |||
+ conn.sock = FakeSocket(None) | |||
+ conn.request(method=method, url="/") | |||
+ | |||
+ | |||
class TransferEncodingTest(TestCase): | |||
expected_body = b"It's just a flesh wound" | |||
diff --git a/Misc/NEWS.d/next/Security/2020-02-12-14-17-39.bpo-39603.Gt3RSg.rst b/Misc/NEWS.d/next/Security/2020-02-12-14-17-39.bpo-39603.Gt3RSg.rst | |||
new file mode 100644 | |||
index 0000000000000..990affc3edd9d | |||
--- /dev/null | |||
+++ b/Misc/NEWS.d/next/Security/2020-02-12-14-17-39.bpo-39603.Gt3RSg.rst | |||
@@ -0,0 +1,2 @@ | |||
+Prevent http header injection by rejecting control characters in | |||
+http.client.putrequest(...). |