diff --git a/.gitignore b/.gitignore index fb34299..2576193 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/Python-2.7.16-noexe.tar.xz +SOURCES/Python-2.7.17-noexe.tar.xz diff --git a/.python2.metadata b/.python2.metadata index 1ad3965..dd6dfd3 100644 --- a/.python2.metadata +++ b/.python2.metadata @@ -1 +1 @@ -c3f14ebccf0b8848a154eb510c7fcf6a8bb038f4 SOURCES/Python-2.7.16-noexe.tar.xz +e63124a9a86b4b52c09384915a0842adf00b9d45 SOURCES/Python-2.7.17-noexe.tar.xz diff --git a/SOURCES/00146-hashlib-fips.patch b/SOURCES/00146-hashlib-fips.patch index badb629..52ae30e 100644 --- a/SOURCES/00146-hashlib-fips.patch +++ b/SOURCES/00146-hashlib-fips.patch @@ -1,44 +1,252 @@ -diff -up Python-2.7.2/Lib/hashlib.py.hashlib-fips Python-2.7.2/Lib/hashlib.py ---- Python-2.7.2/Lib/hashlib.py.hashlib-fips 2011-06-11 11:46:24.000000000 -0400 -+++ Python-2.7.2/Lib/hashlib.py 2011-09-14 00:21:26.194252001 -0400 -@@ -6,9 +6,12 @@ +From ece76465680b0df5b3fce7bf8ff1ff0253933889 Mon Sep 17 00:00:00 2001 +From: Petr Viktorin +Date: Mon, 2 Sep 2019 17:33:29 +0200 +Subject: [PATCH 01/11] Remove HASH_OBJ_CONSTRUCTOR + +See https://github.com/python/cpython/commit/c7e219132aff1e21cb9ccb0a9b570dc6c750039b +--- + Modules/_hashopenssl.c | 59 ------------------------------------------ + 1 file changed, 59 deletions(-) + +diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c +index 78445ebabdd3..cb81e9765251 100644 +--- a/Modules/_hashopenssl.c ++++ b/Modules/_hashopenssl.c +@@ -48,10 +48,6 @@ + * to allow the user to optimize based on the platform they're using. */ + #define HASHLIB_GIL_MINSIZE 2048 + +-#ifndef HASH_OBJ_CONSTRUCTOR +-#define HASH_OBJ_CONSTRUCTOR 0 +-#endif +- + #if defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER >= 0x00908000) + #define _OPENSSL_SUPPORTS_SHA2 + #endif +@@ -384,53 +380,6 @@ EVP_repr(PyObject *self) + return PyString_FromString(buf); + } - __doc__ = """hashlib module - A common interface to many hash functions. +-#if HASH_OBJ_CONSTRUCTOR +-static int +-EVP_tp_init(EVPobject *self, PyObject *args, PyObject *kwds) +-{ +- static char *kwlist[] = {"name", "string", NULL}; +- PyObject *name_obj = NULL; +- Py_buffer view = { 0 }; +- char *nameStr; +- const EVP_MD *digest; +- +- if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|s*:HASH", kwlist, +- &name_obj, &view)) { +- return -1; +- } +- +- if (!PyArg_Parse(name_obj, "s", &nameStr)) { +- PyErr_SetString(PyExc_TypeError, "name must be a string"); +- PyBuffer_Release(&view); +- return -1; +- } +- +- digest = EVP_get_digestbyname(nameStr); +- if (!digest) { +- PyErr_SetString(PyExc_ValueError, "unknown hash function"); +- PyBuffer_Release(&view); +- return -1; +- } +- EVP_DigestInit(self->ctx, digest); +- +- self->name = name_obj; +- Py_INCREF(self->name); +- +- if (view.obj) { +- if (view.len >= HASHLIB_GIL_MINSIZE) { +- Py_BEGIN_ALLOW_THREADS +- EVP_hash(self, view.buf, view.len); +- Py_END_ALLOW_THREADS +- } else { +- EVP_hash(self, view.buf, view.len); +- } +- PyBuffer_Release(&view); +- } +- +- return 0; +-} +-#endif +- --new(name, string='') - returns a new hash object implementing the -- given hash function; initializing the hash -- using the given string data. -+new(name, string='', usedforsecurity=True) -+ - returns a new hash object implementing the given hash function; -+ initializing the hash using the given string data. -+ -+ "usedforsecurity" is a non-standard extension for better supporting -+ FIPS-compliant environments (see below) + PyDoc_STRVAR(hashtype_doc, + "A hash represents the object used to calculate a checksum of a\n\ +@@ -487,9 +436,6 @@ static PyTypeObject EVPtype = { + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + #endif +-#if HASH_OBJ_CONSTRUCTOR +- (initproc)EVP_tp_init, /* tp_init */ +-#endif + }; - Named constructor functions are also available, these are much faster - than using new(): -@@ -24,6 +27,20 @@ the zlib module. - Choose your hash function wisely. Some have known collision weaknesses. - sha384 and sha512 will be slow on 32 bit platforms. + static PyObject * +@@ -928,11 +874,6 @@ init_hashlib(void) + return; + } -+Our implementation of hashlib uses OpenSSL. -+ -+OpenSSL has a "FIPS mode", which, if enabled, may restrict the available hashes -+to only those that are compliant with FIPS regulations. For example, it may -+deny the use of MD5, on the grounds that this is not secure for uses such as -+authentication, system integrity checking, or digital signatures. -+ -+If you need to use such a hash for non-security purposes (such as indexing into -+a data structure for speed), you can override the keyword argument -+"usedforsecurity" from True to False to signify that your code is not relying -+on the hash for security purposes, and this will allow the hash to be usable -+even in FIPS mode. This is not a standard feature of Python 2.7's hashlib, and -+is included here to better support FIPS mode. +-#if HASH_OBJ_CONSTRUCTOR +- Py_INCREF(&EVPtype); +- PyModule_AddObject(m, "HASH", (PyObject *)&EVPtype); +-#endif +- + /* these constants are used by the convenience constructors */ + INIT_CONSTRUCTOR_CONSTANTS(md5); + INIT_CONSTRUCTOR_CONSTANTS(sha1); + +From d7339af75678c760f6d6c0eb455b0eb889c22574 Mon Sep 17 00:00:00 2001 +From: Petr Viktorin +Date: Mon, 2 Sep 2019 18:02:25 +0200 +Subject: [PATCH 02/11] Add the usedforsecurity argument to _hashopenssl + +--- + Modules/_hashopenssl.c | 63 ++++++++++++++++++++++++++++++++---------- + 1 file changed, 48 insertions(+), 15 deletions(-) + +diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c +index cb81e9765251..f2dbc095cc66 100644 +--- a/Modules/_hashopenssl.c ++++ b/Modules/_hashopenssl.c +@@ -441,7 +441,7 @@ static PyTypeObject EVPtype = { + static PyObject * + EVPnew(PyObject *name_obj, + const EVP_MD *digest, const EVP_MD_CTX *initial_ctx, +- const unsigned char *cp, Py_ssize_t len) ++ const unsigned char *cp, Py_ssize_t len, int usedforsecurity) + { + EVPobject *self; + +@@ -456,7 +456,23 @@ EVPnew(PyObject *name_obj, + if (initial_ctx) { + EVP_MD_CTX_copy(self->ctx, initial_ctx); + } else { +- EVP_DigestInit(self->ctx, digest); ++ EVP_MD_CTX_init(self->ctx); + - Hash objects have these methods: - - update(arg): Update the hash object with the string arg. Repeated calls - are equivalent to a single call with the concatenation of all -@@ -63,76 +80,41 @@ algorithms = __always_supported ++ /* ++ If the user has declared that this digest is being used in a ++ non-security role (e.g. indexing into a data structure), set ++ the exception flag for openssl to allow it ++ */ ++ if (!usedforsecurity) { ++#ifdef EVP_MD_CTX_FLAG_NON_FIPS_ALLOW ++ EVP_MD_CTX_set_flags(self->ctx, EVP_MD_CTX_FLAG_NON_FIPS_ALLOW); ++#endif ++ } ++ if (!EVP_DigestInit_ex(self->ctx, digest, NULL)) { ++ _setException(PyExc_ValueError); ++ Py_DECREF(self); ++ return NULL; ++ } + } + + if (cp && len) { +@@ -485,15 +501,16 @@ The MD5 and SHA1 algorithms are always supported.\n"); + static PyObject * + EVP_new(PyObject *self, PyObject *args, PyObject *kwdict) + { +- static char *kwlist[] = {"name", "string", NULL}; ++ static char *kwlist[] = {"name", "string", "usedforsecurity", NULL}; + PyObject *name_obj = NULL; + Py_buffer view = { 0 }; + PyObject *ret_obj; + char *name; + const EVP_MD *digest; ++ int usedforsecurity = 1; + +- if (!PyArg_ParseTupleAndKeywords(args, kwdict, "O|s*:new", kwlist, +- &name_obj, &view)) { ++ if (!PyArg_ParseTupleAndKeywords(args, kwdict, "O|s*i:new", kwlist, ++ &name_obj, &view, &usedforsecurity)) { + return NULL; + } + +@@ -506,7 +523,7 @@ EVP_new(PyObject *self, PyObject *args, PyObject *kwdict) + digest = EVP_get_digestbyname(name); + + ret_obj = EVPnew(name_obj, digest, NULL, (unsigned char*)view.buf, +- view.len); ++ view.len, usedforsecurity); + PyBuffer_Release(&view); + + return ret_obj; +@@ -771,30 +788,46 @@ generate_hash_name_list(void) + * the generic one passing it a python string and are noticeably + * faster than calling a python new() wrapper. Thats important for + * code that wants to make hashes of a bunch of small strings. ++ * ++ * For usedforsecurity=False, the optimization is not used. + */ + #define GEN_CONSTRUCTOR(NAME) \ + static PyObject * \ +- EVP_new_ ## NAME (PyObject *self, PyObject *args) \ ++ EVP_new_ ## NAME (PyObject *self, PyObject *args, PyObject *kwdict) \ + { \ ++ static char *kwlist[] = {"string", "usedforsecurity", NULL}; \ + Py_buffer view = { 0 }; \ + PyObject *ret_obj; \ ++ int usedforsecurity=1; \ + \ +- if (!PyArg_ParseTuple(args, "|s*:" #NAME , &view)) { \ ++ if (!PyArg_ParseTupleAndKeywords( \ ++ args, kwdict, "|s*i:" #NAME, kwlist, \ ++ &view, &usedforsecurity \ ++ )) { \ + return NULL; \ + } \ +- \ +- ret_obj = EVPnew( \ +- CONST_ ## NAME ## _name_obj, \ +- NULL, \ +- CONST_new_ ## NAME ## _ctx_p, \ +- (unsigned char*)view.buf, view.len); \ ++ if (usedforsecurity == 0) { \ ++ ret_obj = EVPnew( \ ++ CONST_ ## NAME ## _name_obj, \ ++ EVP_get_digestbyname(#NAME), \ ++ NULL, \ ++ (unsigned char*)view.buf, view.len, \ ++ usedforsecurity); \ ++ } else { \ ++ ret_obj = EVPnew( \ ++ CONST_ ## NAME ## _name_obj, \ ++ NULL, \ ++ CONST_new_ ## NAME ## _ctx_p, \ ++ (unsigned char*)view.buf, view.len, \ ++ usedforsecurity); \ ++ } \ + PyBuffer_Release(&view); \ + return ret_obj; \ + } + + /* a PyMethodDef structure for the constructor */ + #define CONSTRUCTOR_METH_DEF(NAME) \ +- {"openssl_" #NAME, (PyCFunction)EVP_new_ ## NAME, METH_VARARGS, \ ++ {"openssl_" #NAME, (PyCFunction)EVP_new_ ## NAME, METH_VARARGS|METH_KEYWORDS, \ + PyDoc_STR("Returns a " #NAME \ + " hash object; optionally initialized with a string") \ + } + +From c8102e61fb3ade364d4bb7f2fe3f3452e2018ecd Mon Sep 17 00:00:00 2001 +From: David Malcolm +Date: Mon, 2 Sep 2019 17:59:53 +0200 +Subject: [PATCH 03/11] hashlib.py: Avoid the builtin constructor + +--- + Lib/hashlib.py | 58 +++++++++++++------------------------------------- + 1 file changed, 15 insertions(+), 43 deletions(-) + +diff --git a/Lib/hashlib.py b/Lib/hashlib.py +index bbd06b9996ee..404ed6891fb9 100644 +--- a/Lib/hashlib.py ++++ b/Lib/hashlib.py +@@ -69,65 +69,37 @@ 'pbkdf2_hmac') @@ -85,20 +293,23 @@ diff -up Python-2.7.2/Lib/hashlib.py.hashlib-fips Python-2.7.2/Lib/hashlib.py return f except (AttributeError, ValueError): - return __get_builtin_constructor(name) -+ raise - +- - -def __py_new(name, string=''): - """new(name, string='') - Return a new hashing object using the named algorithm; - optionally initialized with a string. - """ - return __get_builtin_constructor(name)(string) -- -- ++ raise + + -def __hash_new(name, string=''): +- """new(name, string='') - Return a new hashing object using the named algorithm; +- optionally initialized with a string. +def __hash_new(name, string='', usedforsecurity=True): - """new(name, string='') - Return a new hashing object using the named algorithm; - optionally initialized with a string. ++ """new(name, string='', usedforsecurity=True) - Return a new hashing object ++ using the named algorithm; optionally initialized with a string. ++ + Override 'usedforsecurity' to False when using for non-security purposes in + a FIPS environment """ @@ -106,58 +317,184 @@ diff -up Python-2.7.2/Lib/hashlib.py.hashlib-fips Python-2.7.2/Lib/hashlib.py - return _hashlib.new(name, string) + return _hashlib.new(name, string, usedforsecurity) except ValueError: -- # If the _hashlib module (OpenSSL) doesn't support the named -- # hash, try using our builtin implementations. -- # This allows for SHA224/256 and SHA384/512 support even though -- # the OpenSSL library prior to 0.9.8 doesn't provide them. + # If the _hashlib module (OpenSSL) doesn't support the named + # hash, try using our builtin implementations. + # This allows for SHA224/256 and SHA384/512 support even though + # the OpenSSL library prior to 0.9.8 doesn't provide them. - return __get_builtin_constructor(name)(string) -- + raise + try: - import _hashlib - new = __hash_new - __get_hash = __get_openssl_constructor - algorithms_available = algorithms_available.union( - _hashlib.openssl_md_meth_names) - except ImportError: -- new = __py_new -- __get_hash = __get_builtin_constructor -+ # We don't build the legacy modules -+ raise - - for __func_name in __always_supported: - # try them all, some may not work due to the OpenSSL -@@ -143,4 +125,4 @@ for __func_name in __always_supported: +@@ -218,4 +190,4 @@ def prf(msg, inner=inner, outer=outer): # Cleanup locals() del __always_supported, __func_name, __get_hash -del __py_new, __hash_new, __get_openssl_constructor +del __hash_new, __get_openssl_constructor -diff -up Python-2.7.2/Lib/test/test_hashlib.py.hashlib-fips Python-2.7.2/Lib/test/test_hashlib.py ---- Python-2.7.2/Lib/test/test_hashlib.py.hashlib-fips 2011-06-11 11:46:25.000000000 -0400 -+++ Python-2.7.2/Lib/test/test_hashlib.py 2011-09-14 01:08:55.525254195 -0400 -@@ -32,6 +32,19 @@ def hexstr(s): + +From 2ade3e5a6c5732c0692c4cc2235a2bbe0948f50b Mon Sep 17 00:00:00 2001 +From: David Malcolm +Date: Mon, 2 Sep 2019 17:56:46 +0200 +Subject: [PATCH 04/11] Adjust docstrings & comments + +--- + Lib/hashlib.py | 29 ++++++++++++++++++++++------- + Modules/_hashopenssl.c | 9 ++++++++- + 2 files changed, 30 insertions(+), 8 deletions(-) + +diff --git a/Lib/hashlib.py b/Lib/hashlib.py +index 404ed6891fb9..46d0b470ab4a 100644 +--- a/Lib/hashlib.py ++++ b/Lib/hashlib.py +@@ -6,9 +6,12 @@ + + __doc__ = """hashlib module - A common interface to many hash functions. + +-new(name, string='') - returns a new hash object implementing the +- given hash function; initializing the hash +- using the given string data. ++new(name, string='', usedforsecurity=True) ++ - returns a new hash object implementing the given hash function; ++ initializing the hash using the given string data. ++ ++ "usedforsecurity" is a non-standard extension for better supporting ++ FIPS-compliant environments (see below) + + Named constructor functions are also available, these are much faster + than using new(): +@@ -25,6 +28,20 @@ + Choose your hash function wisely. Some have known collision weaknesses. + sha384 and sha512 will be slow on 32 bit platforms. + ++Our implementation of hashlib uses OpenSSL. ++ ++OpenSSL has a "FIPS mode", which, if enabled, may restrict the available hashes ++to only those that are compliant with FIPS regulations. For example, it may ++deny the use of MD5, on the grounds that this is not secure for uses such as ++authentication, system integrity checking, or digital signatures. ++ ++If you need to use such a hash for non-security purposes (such as indexing into ++a data structure for speed), you can override the keyword argument ++"usedforsecurity" from True to False to signify that your code is not relying ++on the hash for security purposes, and this will allow the hash to be usable ++even in FIPS mode. This is not a standard feature of Python 2.7's hashlib, and ++is included here to better support FIPS mode. ++ + Hash objects have these methods: + - update(arg): Update the hash object with the string arg. Repeated calls + are equivalent to a single call with the concatenation of all +@@ -82,6 +99,7 @@ def __get_openssl_constructor(name): + # Use the C function directly (very fast) + return f + except (AttributeError, ValueError): ++ # RHEL only: Fallbacks removed; we always use OpenSSL for hashes. + raise + + +@@ -95,10 +113,7 @@ def __hash_new(name, string='', usedforsecurity=True): + try: + return _hashlib.new(name, string, usedforsecurity) + except ValueError: +- # If the _hashlib module (OpenSSL) doesn't support the named +- # hash, try using our builtin implementations. +- # This allows for SHA224/256 and SHA384/512 support even though +- # the OpenSSL library prior to 0.9.8 doesn't provide them. ++ # RHEL only: Fallbacks removed; we always use OpenSSL for hashes. + raise + + +diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c +index f2dbc095cc66..d24432e048bf 100644 +--- a/Modules/_hashopenssl.c ++++ b/Modules/_hashopenssl.c +@@ -496,7 +496,14 @@ PyDoc_STRVAR(EVP_new__doc__, + An optional string argument may be provided and will be\n\ + automatically hashed.\n\ + \n\ +-The MD5 and SHA1 algorithms are always supported.\n"); ++The MD5 and SHA1 algorithms are always supported.\n \ ++\n\ ++An optional \"usedforsecurity=True\" keyword argument is provided for use in\n\ ++environments that enforce FIPS-based restrictions. Some implementations of\n\ ++OpenSSL can be configured to prevent the usage of non-secure algorithms (such\n\ ++as MD5). If you have a non-security use for these algorithms (e.g. a hash\n\ ++table), you can override this argument by marking the callsite as\n\ ++\"usedforsecurity=False\"."); + + static PyObject * + EVP_new(PyObject *self, PyObject *args, PyObject *kwdict) + +From 6698e1d84c3f19bbb4438b2b2c78a5ef8bd5ad42 Mon Sep 17 00:00:00 2001 +From: Petr Viktorin +Date: Thu, 29 Aug 2019 10:25:28 +0200 +Subject: [PATCH 05/11] Expose OpenSSL FIPS_mode as _hashlib.get_fips_mode + +--- + Modules/_hashopenssl.c | 22 ++++++++++++++++++++++ + 1 file changed, 22 insertions(+) + +diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c +index d24432e048bf..74f9ab9ec150 100644 +--- a/Modules/_hashopenssl.c ++++ b/Modules/_hashopenssl.c +@@ -860,10 +860,32 @@ GEN_CONSTRUCTOR(sha384) + GEN_CONSTRUCTOR(sha512) + #endif + ++static PyObject * ++_hashlib_get_fips_mode(PyObject *module, PyObject *unused) ++{ ++ // XXX: This function skips error checking. ++ // This is only appropriate for RHEL. ++ ++ // From the OpenSSL docs: ++ // "If the library was built without support of the FIPS Object Module, ++ // then the function will return 0 with an error code of ++ // CRYPTO_R_FIPS_MODE_NOT_SUPPORTED (0x0f06d065)." ++ // In RHEL: ++ // * we do build with FIPS, so the function always succeeds ++ // * even if it didn't, people seem used to errors being left on the ++ // OpenSSL error stack. ++ ++ // For more info, see: ++ // https://bugzilla.redhat.com/show_bug.cgi?id=1745499 ++ ++ return PyInt_FromLong(FIPS_mode()); ++} ++ + /* List of functions exported by this module */ + + static struct PyMethodDef EVP_functions[] = { + {"new", (PyCFunction)EVP_new, METH_VARARGS|METH_KEYWORDS, EVP_new__doc__}, ++ {"get_fips_mode", (PyCFunction)_hashlib_get_fips_mode, METH_NOARGS, NULL}, + CONSTRUCTOR_METH_DEF(md5), + CONSTRUCTOR_METH_DEF(sha1), + #ifdef _OPENSSL_SUPPORTS_SHA2 + +From 9a8833619658c6be5ca72c60189a64da05536d85 Mon Sep 17 00:00:00 2001 +From: David Malcolm +Date: Mon, 2 Sep 2019 18:00:26 +0200 +Subject: [PATCH 06/11] Adjust tests + +--- + Lib/test/test_hashlib.py | 118 ++++++++++++++++++++++++--------------- + 1 file changed, 74 insertions(+), 44 deletions(-) + +diff --git a/Lib/test/test_hashlib.py b/Lib/test/test_hashlib.py +index b8d6388feaf9..b03fc84f82b4 100644 +--- a/Lib/test/test_hashlib.py ++++ b/Lib/test/test_hashlib.py +@@ -34,6 +34,8 @@ def hexstr(s): r = r + h[(i >> 4) & 0xF] + h[i & 0xF] return r -+def openssl_enforces_fips(): -+ # Use the "openssl" command (if present) to try to determine if the local -+ # OpenSSL is configured to enforce FIPS -+ from subprocess import Popen, PIPE -+ try: -+ p = Popen(['openssl', 'md5'], -+ stdin=PIPE, stdout=PIPE, stderr=PIPE) -+ except OSError: -+ # "openssl" command not found -+ return False -+ stdout, stderr = p.communicate(input=b'abc') -+ return b'unknown cipher' in stderr -+OPENSSL_ENFORCES_FIPS = openssl_enforces_fips() ++from _hashlib import get_fips_mode ++ class HashLibTestCase(unittest.TestCase): supported_hash_names = ( 'md5', 'MD5', 'sha1', 'SHA1', -@@ -61,10 +74,10 @@ class HashLibTestCase(unittest.TestCase) +@@ -63,10 +65,10 @@ def __init__(self, *args, **kwargs): # of hashlib.new given the algorithm name. for algorithm, constructors in self.constructors_to_test.items(): constructors.add(getattr(hashlib, algorithm)) @@ -171,7 +508,7 @@ diff -up Python-2.7.2/Lib/test/test_hashlib.py.hashlib-fips Python-2.7.2/Lib/tes constructors.add(_test_algorithm_via_hashlib_new) _hashlib = self._conditional_import_module('_hashlib') -@@ -78,28 +91,13 @@ class HashLibTestCase(unittest.TestCase) +@@ -80,28 +82,13 @@ def _test_algorithm_via_hashlib_new(data=None, _alg=algorithm): if constructor: constructors.add(constructor) @@ -201,7 +538,7 @@ diff -up Python-2.7.2/Lib/test/test_hashlib.py.hashlib-fips Python-2.7.2/Lib/tes c.hexdigest() def test_algorithms_attribute(self): -@@ -115,28 +113,9 @@ class HashLibTestCase(unittest.TestCase) +@@ -122,28 +109,9 @@ def test_unknown_hash(self): self.assertRaises(ValueError, hashlib.new, 'spam spam spam spam spam') self.assertRaises(TypeError, hashlib.new, 1) @@ -231,7 +568,7 @@ diff -up Python-2.7.2/Lib/test/test_hashlib.py.hashlib-fips Python-2.7.2/Lib/tes self.assertTrue(hexstr(h.digest()) == h.hexdigest()) def test_large_update(self): -@@ -145,16 +125,16 @@ class HashLibTestCase(unittest.TestCase) +@@ -153,16 +121,16 @@ def test_large_update(self): abcs = aas + bees + cees for name in self.supported_hash_names: @@ -251,7 +588,7 @@ diff -up Python-2.7.2/Lib/test/test_hashlib.py.hashlib-fips Python-2.7.2/Lib/tes self.assertEqual(m1.digest(), m3.digest(), name+' new problem.') def check(self, name, data, digest): -@@ -162,7 +142,7 @@ class HashLibTestCase(unittest.TestCase) +@@ -170,7 +138,7 @@ def check(self, name, data, digest): # 2 is for hashlib.name(...) and hashlib.new(name, ...) self.assertGreaterEqual(len(constructors), 2) for hash_object_constructor in constructors: @@ -260,54 +597,53 @@ diff -up Python-2.7.2/Lib/test/test_hashlib.py.hashlib-fips Python-2.7.2/Lib/tes self.assertEqual( computed, digest, "Hash algorithm %s constructed using %s returned hexdigest" -@@ -172,7 +152,8 @@ class HashLibTestCase(unittest.TestCase) +@@ -195,7 +163,7 @@ def check_update(self, name, data, digest): def check_unicode(self, algorithm_name): # Unicode objects are not allowed as input. - expected = hashlib.new(algorithm_name, str(u'spam')).hexdigest() -+ expected = hashlib.new(algorithm_name, str(u'spam'), -+ usedforsecurity=False).hexdigest() ++ expected = hashlib.new(algorithm_name, str(u'spam'), usedforsecurity=False).hexdigest() self.check(algorithm_name, u'spam', expected) def test_unicode(self): -@@ -354,6 +335,70 @@ class HashLibTestCase(unittest.TestCase) - self.assertEqual(expected_hash, hasher.hexdigest()) +@@ -393,6 +361,68 @@ def hash_in_chunks(chunk_size): + self.assertEqual(expected_hash, hasher.hexdigest()) + def test_issue9146(self): + # Ensure that various ways to use "MD5" from "hashlib" don't segfault: + m = hashlib.md5(usedforsecurity=False) + m.update(b'abc\n') + self.assertEquals(m.hexdigest(), "0bee89b07a248e27c83fc3d5951213c1") -+ ++ + m = hashlib.new('md5', usedforsecurity=False) + m.update(b'abc\n') + self.assertEquals(m.hexdigest(), "0bee89b07a248e27c83fc3d5951213c1") -+ ++ + m = hashlib.md5(b'abc\n', usedforsecurity=False) + self.assertEquals(m.hexdigest(), "0bee89b07a248e27c83fc3d5951213c1") -+ ++ + m = hashlib.new('md5', b'abc\n', usedforsecurity=False) + self.assertEquals(m.hexdigest(), "0bee89b07a248e27c83fc3d5951213c1") + -+ def assertRaisesUnknownCipher(self, callable_obj=None, *args, **kwargs): ++ def assertRaisesDisabledForFIPS(self, callable_obj=None, *args, **kwargs): + try: + callable_obj(*args, **kwargs) + except ValueError, e: -+ if not e.args[0].endswith('unknown cipher'): ++ if not e.args[0].endswith('disabled for FIPS'): + self.fail('Incorrect exception raised') + else: + self.fail('Exception was not raised') + -+ @unittest.skipUnless(OPENSSL_ENFORCES_FIPS, ++ @unittest.skipUnless(get_fips_mode(), + 'FIPS enforcement required for this test.') -+ def test_hashlib_fips_mode(self): ++ def test_hashlib_fips_mode(self): + # Ensure that we raise a ValueError on vanilla attempts to use MD5 + # in hashlib in a FIPS-enforced setting: -+ self.assertRaisesUnknownCipher(hashlib.md5) -+ self.assertRaisesUnknownCipher(hashlib.new, 'md5') ++ self.assertRaisesDisabledForFIPS(hashlib.md5) ++ self.assertRaisesDisabledForFIPS(hashlib.new, 'md5') + -+ @unittest.skipUnless(OPENSSL_ENFORCES_FIPS, ++ @unittest.skipUnless(get_fips_mode(), + 'FIPS enforcement required for this test.') + def test_hashopenssl_fips_mode(self): + # Verify the _hashlib module's handling of md5: @@ -317,56 +653,43 @@ diff -up Python-2.7.2/Lib/test/test_hashlib.py.hashlib-fips Python-2.7.2/Lib/tes + + # Ensure that _hashlib raises a ValueError on vanilla attempts to + # use MD5 in a FIPS-enforced setting: -+ self.assertRaisesUnknownCipher(_hashlib.openssl_md5) -+ self.assertRaisesUnknownCipher(_hashlib.new, 'md5') ++ self.assertRaisesDisabledForFIPS(_hashlib.openssl_md5) ++ self.assertRaisesDisabledForFIPS(_hashlib.new, 'md5') + + # Ensure that in such a setting we can whitelist a callsite with + # usedforsecurity=False and have it succeed: + m = _hashlib.openssl_md5(usedforsecurity=False) + m.update('abc\n') + self.assertEquals(m.hexdigest(), "0bee89b07a248e27c83fc3d5951213c1") -+ ++ + m = _hashlib.new('md5', usedforsecurity=False) + m.update('abc\n') + self.assertEquals(m.hexdigest(), "0bee89b07a248e27c83fc3d5951213c1") -+ ++ + m = _hashlib.openssl_md5('abc\n', usedforsecurity=False) + self.assertEquals(m.hexdigest(), "0bee89b07a248e27c83fc3d5951213c1") -+ ++ + m = _hashlib.new('md5', 'abc\n', usedforsecurity=False) + self.assertEquals(m.hexdigest(), "0bee89b07a248e27c83fc3d5951213c1") -+ -+ + + class KDFTests(unittest.TestCase): pbkdf2_test_vectors = [ - (b'password', b'salt', 1, None), -diff -up Python-2.7.2/Modules/Setup.dist.hashlib-fips Python-2.7.2/Modules/Setup.dist ---- Python-2.7.2/Modules/Setup.dist.hashlib-fips 2011-09-14 00:21:26.163252001 -0400 -+++ Python-2.7.2/Modules/Setup.dist 2011-09-14 00:21:26.201252001 -0400 -@@ -248,14 +248,14 @@ imageop imageop.c # Operations on images - # Message-Digest Algorithm, described in RFC 1321. The necessary files - # md5.c and md5.h are included here. - --_md5 md5module.c md5.c -+#_md5 md5module.c md5.c - - - # The _sha module implements the SHA checksum algorithms. - # (NIST's Secure Hash Algorithms.) --_sha shamodule.c --_sha256 sha256module.c --_sha512 sha512module.c -+#_sha shamodule.c -+#_sha256 sha256module.c -+#_sha512 sha512module.c - - - # SGI IRIX specific modules -- off by default. -diff -up Python-2.7.2/setup.py.hashlib-fips Python-2.7.2/setup.py ---- Python-2.7.2/setup.py.hashlib-fips 2011-09-14 00:21:25.722252001 -0400 -+++ Python-2.7.2/setup.py 2011-09-14 00:21:26.203252001 -0400 -@@ -768,21 +768,6 @@ class PyBuildExt(build_ext): + +From 31e527aa4f57845dfb0c3dd4f0e9192af5a5b4e2 Mon Sep 17 00:00:00 2001 +From: David Malcolm +Date: Mon, 2 Sep 2019 18:00:47 +0200 +Subject: [PATCH 07/11] Don't build non-OpenSSL hash implementations + +--- + setup.py | 15 --------------- + 1 file changed, 15 deletions(-) + +diff --git a/setup.py b/setup.py +index 33cecc687573..272d2f1b5bb8 100644 +--- a/setup.py ++++ b/setup.py +@@ -874,21 +874,6 @@ def detect_modules(self): print ("warning: openssl 0x%08x is too old for _hashlib" % openssl_ver) missing.append('_hashlib') @@ -388,345 +711,134 @@ diff -up Python-2.7.2/setup.py.hashlib-fips Python-2.7.2/setup.py # Modules that provide persistent dictionary-like semantics. You will # probably want to arrange for at least one of them to be available on ---- Python-2.7.8/Modules/_hashopenssl.c.orig 2014-06-30 04:05:41.000000000 +0200 -+++ Python-2.7.8/Modules/_hashopenssl.c 2014-07-14 14:21:59.546386572 +0200 -@@ -36,6 +36,8 @@ - #endif - - /* EVP is the preferred interface to hashing in OpenSSL */ -+#include -+#include - #include - #include - #include -@@ -67,11 +69,19 @@ - - static PyTypeObject EVPtype; - -+/* Struct to hold all the cached information we need on a specific algorithm. -+ We have one of these per algorithm */ -+typedef struct { -+ PyObject *name_obj; -+ EVP_MD_CTX ctxs[2]; -+ /* ctx_ptrs will point to ctxs unless an error occurred, when it will -+ be NULL: */ -+ EVP_MD_CTX *ctx_ptrs[2]; -+ PyObject *error_msgs[2]; -+} EVPCachedInfo; - --#define DEFINE_CONSTS_FOR_NEW(Name) \ -- static PyObject *CONST_ ## Name ## _name_obj = NULL; \ -- static EVP_MD_CTX CONST_new_ ## Name ## _ctx; \ -- static EVP_MD_CTX *CONST_new_ ## Name ## _ctx_p = NULL; -+#define DEFINE_CONSTS_FOR_NEW(Name) \ -+ static EVPCachedInfo cached_info_ ##Name; - - DEFINE_CONSTS_FOR_NEW(md5) - DEFINE_CONSTS_FOR_NEW(sha1) -@@ -117,6 +127,48 @@ - } - } - -+static void -+mc_ctx_init(EVP_MD_CTX *ctx, int usedforsecurity) -+{ -+ EVP_MD_CTX_init(ctx); -+ -+ /* -+ If the user has declared that this digest is being used in a -+ non-security role (e.g. indexing into a data structure), set -+ the exception flag for openssl to allow it -+ */ -+ if (!usedforsecurity) { -+#ifdef EVP_MD_CTX_FLAG_NON_FIPS_ALLOW -+ EVP_MD_CTX_set_flags(ctx, -+ EVP_MD_CTX_FLAG_NON_FIPS_ALLOW); -+#endif -+ } -+} -+ -+/* Get an error msg for the last error as a PyObject */ -+static PyObject * -+error_msg_for_last_error(void) -+{ -+ char *errstr; -+ -+ errstr = ERR_error_string(ERR_peek_last_error(), NULL); -+ ERR_clear_error(); -+ -+ return PyString_FromString(errstr); /* Can be NULL */ -+} -+ -+static void -+set_evp_exception(void) -+{ -+ char *errstr; -+ -+ errstr = ERR_error_string(ERR_peek_last_error(), NULL); -+ ERR_clear_error(); -+ -+ PyErr_SetString(PyExc_ValueError, errstr); -+} -+ -+ - /* Internal methods for a hash object */ - - static void -@@ -315,14 +367,15 @@ - static int - EVP_tp_init(EVPobject *self, PyObject *args, PyObject *kwds) - { -- static char *kwlist[] = {"name", "string", NULL}; -+ static char *kwlist[] = {"name", "string", "usedforsecurity", NULL}; - PyObject *name_obj = NULL; -+ int usedforsecurity = 1; - Py_buffer view = { 0 }; - char *nameStr; - const EVP_MD *digest; - -- if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|s*:HASH", kwlist, -- &name_obj, &view)) { -+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|s*i:HASH", kwlist, -+ &name_obj, &view, &usedforsecurity)) { - return -1; - } - -@@ -338,7 +391,12 @@ - PyBuffer_Release(&view); - return -1; - } -- EVP_DigestInit(&self->ctx, digest); -+ mc_ctx_init(&self->ctx, usedforsecurity); -+ if (!EVP_DigestInit_ex(&self->ctx, digest, NULL)) { -+ set_evp_exception(); -+ PyBuffer_Release(&view); -+ return -1; -+ } - - self->name = name_obj; - Py_INCREF(self->name); -@@ -422,7 +480,8 @@ - static PyObject * - EVPnew(PyObject *name_obj, - const EVP_MD *digest, const EVP_MD_CTX *initial_ctx, -- const unsigned char *cp, Py_ssize_t len) -+ const unsigned char *cp, Py_ssize_t len, -+ int usedforsecurity) - { - EVPobject *self; - -@@ -437,7 +496,12 @@ - if (initial_ctx) { - EVP_MD_CTX_copy(&self->ctx, initial_ctx); - } else { -- EVP_DigestInit(&self->ctx, digest); -+ mc_ctx_init(&self->ctx, usedforsecurity); -+ if (!EVP_DigestInit_ex(&self->ctx, digest, NULL)) { -+ set_evp_exception(); -+ Py_DECREF(self); -+ return NULL; -+ } - } - - if (cp && len) { -@@ -461,20 +525,28 @@ - An optional string argument may be provided and will be\n\ - automatically hashed.\n\ - \n\ --The MD5 and SHA1 algorithms are always supported.\n"); -+The MD5 and SHA1 algorithms are always supported.\n\ -+\n\ -+An optional \"usedforsecurity=True\" keyword argument is provided for use in\n\ -+environments that enforce FIPS-based restrictions. Some implementations of\n\ -+OpenSSL can be configured to prevent the usage of non-secure algorithms (such\n\ -+as MD5). If you have a non-security use for these algorithms (e.g. a hash\n\ -+table), you can override this argument by marking the callsite as\n\ -+\"usedforsecurity=False\"."); - - static PyObject * - EVP_new(PyObject *self, PyObject *args, PyObject *kwdict) - { -- static char *kwlist[] = {"name", "string", NULL}; -+ static char *kwlist[] = {"name", "string", "usedforsecurity", NULL}; - PyObject *name_obj = NULL; - Py_buffer view = { 0 }; - PyObject *ret_obj; - char *name; - const EVP_MD *digest; -+ int usedforsecurity = 1; - -- if (!PyArg_ParseTupleAndKeywords(args, kwdict, "O|s*:new", kwlist, -- &name_obj, &view)) { -+ if (!PyArg_ParseTupleAndKeywords(args, kwdict, "O|s*i:new", kwlist, -+ &name_obj, &view, &usedforsecurity)) { - return NULL; - } - -@@ -487,7 +559,7 @@ - digest = EVP_get_digestbyname(name); - - ret_obj = EVPnew(name_obj, digest, NULL, (unsigned char*)view.buf, -- view.len); -+ view.len, usedforsecurity); - PyBuffer_Release(&view); - - return ret_obj; -@@ -713,51 +785,111 @@ - - - /* -- * This macro generates constructor function definitions for specific -- * hash algorithms. These constructors are much faster than calling -- * the generic one passing it a python string and are noticably -- * faster than calling a python new() wrapper. Thats important for -+ * This macro and function generates a family of constructor function -+ * definitions for specific hash algorithms. These constructors are much -+ * faster than calling the generic one passing it a python string and are -+ * noticably faster than calling a python new() wrapper. That's important for - * code that wants to make hashes of a bunch of small strings. - */ - #define GEN_CONSTRUCTOR(NAME) \ - static PyObject * \ -- EVP_new_ ## NAME (PyObject *self, PyObject *args) \ -+ EVP_new_ ## NAME (PyObject *self, PyObject *args, PyObject *kwdict) \ - { \ -- Py_buffer view = { 0 }; \ -- PyObject *ret_obj; \ -- \ -- if (!PyArg_ParseTuple(args, "|s*:" #NAME , &view)) { \ -- return NULL; \ -- } \ -- \ -- ret_obj = EVPnew( \ -- CONST_ ## NAME ## _name_obj, \ -- NULL, \ -- CONST_new_ ## NAME ## _ctx_p, \ -- (unsigned char*)view.buf, view.len); \ -- PyBuffer_Release(&view); \ -- return ret_obj; \ -+ return implement_specific_EVP_new(self, args, kwdict, \ -+ "|s*i:" #NAME, \ -+ &cached_info_ ## NAME ); \ - } - -+static PyObject * -+implement_specific_EVP_new(PyObject *self, PyObject *args, PyObject *kwdict, -+ const char *format, -+ EVPCachedInfo *cached_info) -+{ -+ static char *kwlist[] = {"string", "usedforsecurity", NULL}; -+ Py_buffer view = { 0 }; -+ int usedforsecurity = 1; -+ int idx; -+ PyObject *ret_obj = NULL; -+ -+ assert(cached_info); -+ -+ if (!PyArg_ParseTupleAndKeywords(args, kwdict, format, kwlist, -+ &view, &usedforsecurity)) { -+ return NULL; -+ } -+ -+ idx = usedforsecurity ? 1 : 0; -+ -+ /* -+ * If an error occurred during creation of the global content, the ctx_ptr -+ * will be NULL, and the error_msg will hopefully be non-NULL: -+ */ -+ if (cached_info->ctx_ptrs[idx]) { -+ /* We successfully initialized this context; copy it: */ -+ ret_obj = EVPnew(cached_info->name_obj, -+ NULL, -+ cached_info->ctx_ptrs[idx], -+ (unsigned char*)view.buf, view.len, -+ usedforsecurity); -+ } else { -+ /* Some kind of error happened initializing the global context for -+ this (digest, usedforsecurity) pair. -+ Raise an exception with the saved error message: */ -+ if (cached_info->error_msgs[idx]) { -+ PyErr_SetObject(PyExc_ValueError, cached_info->error_msgs[idx]); -+ } else { -+ PyErr_SetString(PyExc_ValueError, "Error initializing hash"); -+ } -+ } -+ -+ PyBuffer_Release(&view); -+ -+ return ret_obj; -+} -+ - /* a PyMethodDef structure for the constructor */ - #define CONSTRUCTOR_METH_DEF(NAME) \ -- {"openssl_" #NAME, (PyCFunction)EVP_new_ ## NAME, METH_VARARGS, \ -+ {"openssl_" #NAME, (PyCFunction)EVP_new_ ## NAME, \ -+ METH_VARARGS |METH_KEYWORDS, \ - PyDoc_STR("Returns a " #NAME \ - " hash object; optionally initialized with a string") \ - } - --/* used in the init function to setup a constructor: initialize OpenSSL -- constructor constants if they haven't been initialized already. */ --#define INIT_CONSTRUCTOR_CONSTANTS(NAME) do { \ -- if (CONST_ ## NAME ## _name_obj == NULL) { \ -- CONST_ ## NAME ## _name_obj = PyString_FromString(#NAME); \ -- if (EVP_get_digestbyname(#NAME)) { \ -- CONST_new_ ## NAME ## _ctx_p = &CONST_new_ ## NAME ## _ctx; \ + +From e9cd6a63ce17a0120b1d017bf08f05f3ed223bb1 Mon Sep 17 00:00:00 2001 +From: Petr Viktorin +Date: Mon, 2 Sep 2019 18:33:22 +0200 +Subject: [PATCH 08/11] Allow for errros in pre-created context creation + +--- + Modules/_hashopenssl.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c +index 74f9ab9ec150..7609e9e490f0 100644 +--- a/Modules/_hashopenssl.c ++++ b/Modules/_hashopenssl.c +@@ -813,7 +813,7 @@ generate_hash_name_list(void) + )) { \ + return NULL; \ + } \ +- if (usedforsecurity == 0) { \ ++ if (usedforsecurity == 0 || CONST_new_ ## NAME ## _ctx_p == NULL) { \ + ret_obj = EVPnew( \ + CONST_ ## NAME ## _name_obj, \ + EVP_get_digestbyname(#NAME), \ +@@ -846,7 +846,9 @@ generate_hash_name_list(void) + CONST_ ## NAME ## _name_obj = PyString_FromString(#NAME); \ + if (EVP_get_digestbyname(#NAME)) { \ + CONST_new_ ## NAME ## _ctx_p = EVP_MD_CTX_new(); \ - EVP_DigestInit(CONST_new_ ## NAME ## _ctx_p, EVP_get_digestbyname(#NAME)); \ -- } \ -- } \ -+/* -+ Macro/function pair to set up the constructors. -+ -+ Try to initialize a context for each hash twice, once with -+ EVP_MD_CTX_FLAG_NON_FIPS_ALLOW and once without. -+ -+ Any that have errors during initialization will end up wit a NULL ctx_ptrs -+ entry, and err_msgs will be set (unless we're very low on memory) -+*/ -+#define INIT_CONSTRUCTOR_CONSTANTS(NAME) do { \ -+ init_constructor_constant(&cached_info_ ## NAME, #NAME); \ ++ if (!EVP_DigestInit(CONST_new_ ## NAME ## _ctx_p, EVP_get_digestbyname(#NAME))) { \ ++ CONST_new_ ## NAME ## _ctx_p = NULL; \ ++ } \ + } \ + } \ } while (0); - -+static void -+init_constructor_constant(EVPCachedInfo *cached_info, const char *name) -+{ -+ assert(cached_info); -+ cached_info->name_obj = PyString_FromString(name); -+ if (EVP_get_digestbyname(name)) { -+ int i; -+ for (i=0; i<2; i++) { -+ mc_ctx_init(&cached_info->ctxs[i], i); -+ if (EVP_DigestInit_ex(&cached_info->ctxs[i], -+ EVP_get_digestbyname(name), NULL)) { -+ /* Success: */ -+ cached_info->ctx_ptrs[i] = &cached_info->ctxs[i]; -+ } else { -+ /* Failure: */ -+ cached_info->ctx_ptrs[i] = NULL; -+ cached_info->error_msgs[i] = error_msg_for_last_error(); -+ } -+ } -+ } -+} + +From d0465ea1c07f24067b4d6f60f73a29c82f2ad03f Mon Sep 17 00:00:00 2001 +From: David Malcolm +Date: Mon, 2 Sep 2019 18:40:08 +0200 +Subject: [PATCH 09/11] use SHA-256 rather than MD5 in + multiprocessing.connection (patch 169; rhbz#879695) + +--- + Lib/multiprocessing/connection.py | 12 ++++++++++-- + 1 file changed, 10 insertions(+), 2 deletions(-) + +diff --git a/Lib/multiprocessing/connection.py b/Lib/multiprocessing/connection.py +index 645a26f069ea..d4dc6ac19d53 100644 +--- a/Lib/multiprocessing/connection.py ++++ b/Lib/multiprocessing/connection.py +@@ -56,6 +56,10 @@ + # A very generous timeout when it comes to local connections... + CONNECTION_TIMEOUT = 20. + ++# The hmac module implicitly defaults to using MD5. ++# Support using a stronger algorithm for the challenge/response code: ++HMAC_DIGEST_NAME='sha256' + - GEN_CONSTRUCTOR(md5) - GEN_CONSTRUCTOR(sha1) - #ifdef _OPENSSL_SUPPORTS_SHA2 -@@ -794,14 +926,11 @@ - { - PyObject *m, *openssl_md_meth_names; + _mmap_counter = itertools.count() -+ SSL_load_error_strings(); -+ SSL_library_init(); - OpenSSL_add_all_digests(); - ERR_load_crypto_strings(); + default_family = 'AF_INET' +@@ -413,12 +417,16 @@ def PipeClient(address): + WELCOME = b'#WELCOME#' + FAILURE = b'#FAILURE#' -- /* TODO build EVP_functions openssl_* entries dynamically based -- * on what hashes are supported rather than listing many -- * but having some be unsupported. Only init appropriate -- * constants. */ -- - Py_TYPE(&EVPtype) = &PyType_Type; - if (PyType_Ready(&EVPtype) < 0) - return; ++def get_digestmod_for_hmac(): ++ import hashlib ++ return getattr(hashlib, HMAC_DIGEST_NAME) ++ + def deliver_challenge(connection, authkey): + import hmac + assert isinstance(authkey, bytes) + message = os.urandom(MESSAGE_LENGTH) + connection.send_bytes(CHALLENGE + message) +- digest = hmac.new(authkey, message).digest() ++ digest = hmac.new(authkey, message, get_digestmod_for_hmac()).digest() + response = connection.recv_bytes(256) # reject large message + if response == digest: + connection.send_bytes(WELCOME) +@@ -432,7 +440,7 @@ def answer_challenge(connection, authkey): + message = connection.recv_bytes(256) # reject large message + assert message[:len(CHALLENGE)] == CHALLENGE, 'message = %r' % message + message = message[len(CHALLENGE):] +- digest = hmac.new(authkey, message).digest() ++ digest = hmac.new(authkey, message, get_digestmod_for_hmac()).digest() + connection.send_bytes(digest) + response = connection.recv_bytes(256) # reject large message + if response != WELCOME: + +From 82b181a2c55be0f0766fdf1f0a3e950d22fe0602 Mon Sep 17 00:00:00 2001 +From: Petr Viktorin +Date: Mon, 19 Aug 2019 13:59:40 +0200 +Subject: [PATCH 10/11] Make uuid.uuid3 work (using libuuid via ctypes) + +--- + Lib/uuid.py | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/Lib/uuid.py b/Lib/uuid.py +index 80d33c0bd83f..bfb7477b5f58 100644 +--- a/Lib/uuid.py ++++ b/Lib/uuid.py +@@ -455,6 +455,7 @@ def _netbios_getnode(): + + # If ctypes is available, use it to find system routines for UUID generation. + _uuid_generate_time = _UuidCreate = None ++_uuid_generate_md5 = None + try: + import ctypes, ctypes.util + import sys +@@ -471,6 +472,8 @@ def _netbios_getnode(): + continue + if hasattr(lib, 'uuid_generate_time'): + _uuid_generate_time = lib.uuid_generate_time ++ # The library that has uuid_generate_time should have md5 too. ++ _uuid_generate_md5 = getattr(lib, 'uuid_generate_md5') + break + del _libnames + +@@ -595,6 +598,11 @@ def uuid1(node=None, clock_seq=None): + + def uuid3(namespace, name): + """Generate a UUID from the MD5 hash of a namespace UUID and a name.""" ++ if _uuid_generate_md5: ++ _buffer = ctypes.create_string_buffer(16) ++ _uuid_generate_md5(_buffer, namespace.bytes, name, len(name)) ++ return UUID(bytes=_buffer.raw) ++ + from hashlib import md5 + hash = md5(namespace.bytes + name).digest() + return UUID(bytes=hash[:16], version=3) + diff --git a/SOURCES/00153-fix-test_gdb-noise.patch b/SOURCES/00153-fix-test_gdb-noise.patch deleted file mode 100644 index 8884d4c..0000000 --- a/SOURCES/00153-fix-test_gdb-noise.patch +++ /dev/null @@ -1,13 +0,0 @@ ---- Lib/test/test_gdb.py.old 2012-04-11 21:04:01.367073855 -0400 -+++ Lib/test/test_gdb.py 2012-04-12 08:52:58.320288761 -0400 -@@ -211,6 +211,10 @@ - # ignore all warnings - 'warning: ', - ) -+ ignore_patterns += ('warning: Unable to open', -+ 'Missing separate debuginfo for', -+ 'Try: yum --disablerepo=', -+ 'Undefined set print command') - for line in errlines: - if not line: - continue diff --git a/SOURCES/00157-uid-gid-overflows.patch b/SOURCES/00157-uid-gid-overflows.patch deleted file mode 100644 index a31c98a..0000000 --- a/SOURCES/00157-uid-gid-overflows.patch +++ /dev/null @@ -1,49 +0,0 @@ -diff -up Python-2.7.3/Lib/test/test_os.py.uid-gid-overflows Python-2.7.3/Lib/test/test_os.py ---- Python-2.7.3/Lib/test/test_os.py.uid-gid-overflows 2012-04-09 19:07:32.000000000 -0400 -+++ Python-2.7.3/Lib/test/test_os.py 2012-06-26 14:51:36.000817929 -0400 -@@ -677,30 +677,36 @@ if sys.platform != 'win32': - def test_setuid(self): - if os.getuid() != 0: - self.assertRaises(os.error, os.setuid, 0) -+ self.assertRaises(TypeError, os.setuid, 'not an int') - self.assertRaises(OverflowError, os.setuid, 1<<32) - - @unittest.skipUnless(hasattr(os, 'setgid'), 'test needs os.setgid()') - def test_setgid(self): - if os.getuid() != 0: - self.assertRaises(os.error, os.setgid, 0) -+ self.assertRaises(TypeError, os.setgid, 'not an int') - self.assertRaises(OverflowError, os.setgid, 1<<32) - - @unittest.skipUnless(hasattr(os, 'seteuid'), 'test needs os.seteuid()') - def test_seteuid(self): - if os.getuid() != 0: - self.assertRaises(os.error, os.seteuid, 0) -+ self.assertRaises(TypeError, os.seteuid, 'not an int') - self.assertRaises(OverflowError, os.seteuid, 1<<32) - - @unittest.skipUnless(hasattr(os, 'setegid'), 'test needs os.setegid()') - def test_setegid(self): - if os.getuid() != 0: - self.assertRaises(os.error, os.setegid, 0) -+ self.assertRaises(TypeError, os.setegid, 'not an int') - self.assertRaises(OverflowError, os.setegid, 1<<32) - - @unittest.skipUnless(hasattr(os, 'setreuid'), 'test needs os.setreuid()') - def test_setreuid(self): - if os.getuid() != 0: - self.assertRaises(os.error, os.setreuid, 0, 0) -+ self.assertRaises(TypeError, os.setreuid, 'not an int', 0) -+ self.assertRaises(TypeError, os.setreuid, 0, 'not an int') - self.assertRaises(OverflowError, os.setreuid, 1<<32, 0) - self.assertRaises(OverflowError, os.setreuid, 0, 1<<32) - -@@ -715,6 +721,8 @@ if sys.platform != 'win32': - def test_setregid(self): - if os.getuid() != 0: - self.assertRaises(os.error, os.setregid, 0, 0) -+ self.assertRaises(TypeError, os.setregid, 'not an int', 0) -+ self.assertRaises(TypeError, os.setregid, 0, 'not an int') - self.assertRaises(OverflowError, os.setregid, 1<<32, 0) - self.assertRaises(OverflowError, os.setregid, 0, 1<<32) - diff --git a/SOURCES/00168-distutils-cflags.patch b/SOURCES/00168-distutils-cflags.patch deleted file mode 100644 index 0c4a8df..0000000 --- a/SOURCES/00168-distutils-cflags.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff -up Python-2.6.6/Lib/distutils/sysconfig.py.distutils-cflags Python-2.6.6/Lib/distutils/sysconfig.py ---- Python-2.6.6/Lib/distutils/sysconfig.py.distutils-cflags 2011-08-12 17:18:17.833091153 -0400 -+++ Python-2.6.6/Lib/distutils/sysconfig.py 2011-08-12 17:18:27.449106938 -0400 -@@ -187,7 +187,7 @@ def customize_compiler(compiler): - if 'LDFLAGS' in os.environ: - ldshared = ldshared + ' ' + os.environ['LDFLAGS'] - if 'CFLAGS' in os.environ: -- cflags = opt + ' ' + os.environ['CFLAGS'] -+ cflags = cflags + ' ' + os.environ['CFLAGS'] - ldshared = ldshared + ' ' + os.environ['CFLAGS'] - if 'CPPFLAGS' in os.environ: - cpp = cpp + ' ' + os.environ['CPPFLAGS'] diff --git a/SOURCES/00169-avoid-implicit-usage-of-md5-in-multiprocessing.patch b/SOURCES/00169-avoid-implicit-usage-of-md5-in-multiprocessing.patch deleted file mode 100644 index debf92f..0000000 --- a/SOURCES/00169-avoid-implicit-usage-of-md5-in-multiprocessing.patch +++ /dev/null @@ -1,41 +0,0 @@ -diff --git a/Lib/multiprocessing/connection.py b/Lib/multiprocessing/connection.py ---- a/Lib/multiprocessing/connection.py -+++ b/Lib/multiprocessing/connection.py -@@ -41,6 +41,10 @@ - # A very generous timeout when it comes to local connections... - CONNECTION_TIMEOUT = 20. - -+# The hmac module implicitly defaults to using MD5. -+# Support using a stronger algorithm for the challenge/response code: -+HMAC_DIGEST_NAME='sha256' -+ - _mmap_counter = itertools.count() - - default_family = 'AF_INET' -@@ -700,12 +704,16 @@ - WELCOME = b'#WELCOME#' - FAILURE = b'#FAILURE#' - -+def get_digestmod_for_hmac(): -+ import hashlib -+ return getattr(hashlib, HMAC_DIGEST_NAME) -+ - def deliver_challenge(connection, authkey): - import hmac - assert isinstance(authkey, bytes) - message = os.urandom(MESSAGE_LENGTH) - connection.send_bytes(CHALLENGE + message) -- digest = hmac.new(authkey, message).digest() -+ digest = hmac.new(authkey, message, get_digestmod_for_hmac()).digest() - response = connection.recv_bytes(256) # reject large message - if response == digest: - connection.send_bytes(WELCOME) -@@ -719,7 +727,7 @@ - message = connection.recv_bytes(256) # reject large message - assert message[:len(CHALLENGE)] == CHALLENGE, 'message = %r' % message - message = message[len(CHALLENGE):] -- digest = hmac.new(authkey, message).digest() -+ digest = hmac.new(authkey, message, get_digestmod_for_hmac()).digest() - connection.send_bytes(digest) - response = connection.recv_bytes(256) # reject large message - if response != WELCOME: diff --git a/SOURCES/00189-gdb-py-bt-dont-raise-exception-from-eval.patch b/SOURCES/00189-gdb-py-bt-dont-raise-exception-from-eval.patch deleted file mode 100644 index df1f4e7..0000000 --- a/SOURCES/00189-gdb-py-bt-dont-raise-exception-from-eval.patch +++ /dev/null @@ -1,14 +0,0 @@ -diff --git a/Tools/gdb/libpython.py b/Tools/gdb/libpython.py -index 9def56e..c0df208 100755 ---- a/Tools/gdb/libpython.py -+++ b/Tools/gdb/libpython.py -@@ -939,6 +939,9 @@ class PyFrameObjectPtr(PyObjectPtr): - if self.is_optimized_out(): - return '(frame information optimized out)' - -+ if self.filename() == '': -+ return '(in an eval block)' -+ - lineno = self.current_line_num() - if lineno is None: - return '(failed to get frame line number)' diff --git a/SOURCES/00189-use-rpm-wheels.patch b/SOURCES/00189-use-rpm-wheels.patch new file mode 100644 index 0000000..a9a9a7e --- /dev/null +++ b/SOURCES/00189-use-rpm-wheels.patch @@ -0,0 +1,70 @@ +diff --git a/Lib/ensurepip/__init__.py b/Lib/ensurepip/__init__.py +index 5021ebf..29a7d1b 100644 +--- a/Lib/ensurepip/__init__.py ++++ b/Lib/ensurepip/__init__.py +@@ -1,9 +1,10 @@ + #!/usr/bin/env python2 + from __future__ import print_function + ++import distutils.version ++import glob + import os + import os.path +-import pkgutil + import shutil + import sys + import tempfile +@@ -12,9 +13,19 @@ import tempfile + __all__ = ["version", "bootstrap"] + + +-_SETUPTOOLS_VERSION = "41.2.0" ++_WHEEL_DIR = "/usr/share/python{}-wheels/".format(sys.version_info[0]) + +-_PIP_VERSION = "19.2.3" ++def _get_most_recent_wheel_version(pkg): ++ prefix = os.path.join(_WHEEL_DIR, "{}-".format(pkg)) ++ suffix = "-py2.py3-none-any.whl" ++ pattern = "{}*{}".format(prefix, suffix) ++ versions = (p[len(prefix):-len(suffix)] for p in glob.glob(pattern)) ++ return str(max(versions, key=distutils.version.LooseVersion)) ++ ++ ++_SETUPTOOLS_VERSION = _get_most_recent_wheel_version("setuptools") ++ ++_PIP_VERSION = _get_most_recent_wheel_version("pip") + + _PROJECTS = [ + ("setuptools", _SETUPTOOLS_VERSION), +@@ -28,8 +39,13 @@ def _run_pip(args, additional_paths=None): + sys.path = additional_paths + sys.path + + # Install the bundled software +- import pip._internal +- return pip._internal.main(args) ++ try: ++ # pip 10 ++ from pip._internal import main ++ except ImportError: ++ # pip 9 ++ from pip import main ++ return main(args) + + + def version(): +@@ -100,12 +116,9 @@ def _bootstrap(root=None, upgrade=False, user=False, + additional_paths = [] + for project, version in _PROJECTS: + wheel_name = "{}-{}-py2.py3-none-any.whl".format(project, version) +- whl = pkgutil.get_data( +- "ensurepip", +- "_bundled/{}".format(wheel_name), +- ) +- with open(os.path.join(tmpdir, wheel_name), "wb") as fp: +- fp.write(whl) ++ with open(os.path.join(_WHEEL_DIR, wheel_name), "rb") as sfp: ++ with open(os.path.join(tmpdir, wheel_name), "wb") as fp: ++ fp.write(sfp.read()) + + additional_paths.append(os.path.join(tmpdir, wheel_name)) + diff --git a/SOURCES/00198-add-rewheel-module.patch b/SOURCES/00198-add-rewheel-module.patch deleted file mode 100644 index b39f9f3..0000000 --- a/SOURCES/00198-add-rewheel-module.patch +++ /dev/null @@ -1,249 +0,0 @@ -diff --git a/Lib/ensurepip/__init__.py b/Lib/ensurepip/__init__.py -index 5021ebf..63c763a 100644 ---- a/Lib/ensurepip/__init__.py -+++ b/Lib/ensurepip/__init__.py -@@ -7,6 +7,7 @@ import pkgutil - import shutil - import sys - import tempfile -+from ensurepip import rewheel - - - __all__ = ["version", "bootstrap"] -@@ -29,6 +30,8 @@ def _run_pip(args, additional_paths=None): - - # Install the bundled software - import pip._internal -+ if args[0] in ["install", "list", "wheel"]: -+ args.append('--pre') - return pip._internal.main(args) - - -@@ -93,21 +96,40 @@ def _bootstrap(root=None, upgrade=False, user=False, - # omit pip and easy_install - os.environ["ENSUREPIP_OPTIONS"] = "install" - -+ whls = [] -+ rewheel_dir = None -+ # try to see if we have system-wide versions of _PROJECTS -+ dep_records = rewheel.find_system_records([p[0] for p in _PROJECTS]) -+ # TODO: check if system-wide versions are the newest ones -+ # if --upgrade is used? -+ if all(dep_records): -+ # if we have all _PROJECTS installed system-wide, we'll recreate -+ # wheels from them and install those -+ rewheel_dir = tempfile.mkdtemp() -+ for dr in dep_records: -+ new_whl = rewheel.rewheel_from_record(dr, rewheel_dir) -+ whls.append(os.path.join(rewheel_dir, new_whl)) -+ else: -+ # if we don't have all the _PROJECTS installed system-wide, -+ # let's just fall back to bundled wheels -+ for project, version in _PROJECTS: -+ whl = os.path.join( -+ os.path.dirname(__file__), -+ "_bundled", -+ "{}-{}-py2.py3-none-any.whl".format(project, version) -+ ) -+ whls.append(whl) -+ - tmpdir = tempfile.mkdtemp() - try: - # Put our bundled wheels into a temporary directory and construct the - # additional paths that need added to sys.path - additional_paths = [] -- for project, version in _PROJECTS: -- wheel_name = "{}-{}-py2.py3-none-any.whl".format(project, version) -- whl = pkgutil.get_data( -- "ensurepip", -- "_bundled/{}".format(wheel_name), -- ) -- with open(os.path.join(tmpdir, wheel_name), "wb") as fp: -- fp.write(whl) -- -- additional_paths.append(os.path.join(tmpdir, wheel_name)) -+ for whl in whls: -+ shutil.copy(whl, tmpdir) -+ additional_paths.append(os.path.join(tmpdir, os.path.basename(whl))) -+ if rewheel_dir: -+ shutil.rmtree(rewheel_dir) - - # Construct the arguments to be passed to the pip command - args = ["install", "--no-index", "--find-links", tmpdir] -diff --git a/Lib/ensurepip/rewheel/__init__.py b/Lib/ensurepip/rewheel/__init__.py -new file mode 100644 -index 0000000..75c2094 ---- /dev/null -+++ b/Lib/ensurepip/rewheel/__init__.py -@@ -0,0 +1,158 @@ -+import argparse -+import codecs -+import csv -+import email.parser -+import os -+import io -+import re -+import site -+import subprocess -+import sys -+import zipfile -+ -+def run(): -+ parser = argparse.ArgumentParser(description='Recreate wheel of package with given RECORD.') -+ parser.add_argument('record_path', -+ help='Path to RECORD file') -+ parser.add_argument('-o', '--output-dir', -+ help='Dir where to place the wheel, defaults to current working dir.', -+ dest='outdir', -+ default=os.path.curdir) -+ -+ ns = parser.parse_args() -+ retcode = 0 -+ try: -+ print(rewheel_from_record(**vars(ns))) -+ except BaseException as e: -+ print('Failed: {}'.format(e)) -+ retcode = 1 -+ sys.exit(1) -+ -+def find_system_records(projects): -+ """Return list of paths to RECORD files for system-installed projects. -+ -+ If a project is not installed, the resulting list contains None instead -+ of a path to its RECORD -+ """ -+ records = [] -+ # get system site-packages dirs -+ if hasattr(sys, 'real_prefix'): -+ #we are in python2 virtualenv and sys.real_prefix is the original sys.prefix -+ _orig_prefixes = site.PREFIXES -+ setattr(site, 'PREFIXES', [sys.real_prefix]*2) -+ sys_sitepack = site.getsitepackages() -+ setattr(site, 'PREFIXES', _orig_prefixes) -+ elif hasattr(sys, 'base_prefix'): # python3 venv doesn't inject real_prefix to sys -+ # we are on python3 and base(_exec)_prefix is unchanged in venv -+ sys_sitepack = site.getsitepackages([sys.base_prefix, sys.base_exec_prefix]) -+ else: -+ # we are in python2 without virtualenv -+ sys_sitepack = site.getsitepackages() -+ -+ sys_sitepack = [sp for sp in sys_sitepack if os.path.exists(sp)] -+ # try to find all projects in all system site-packages -+ for project in projects: -+ path = None -+ for sp in sys_sitepack: -+ dist_info_re = os.path.join(sp, project) + '-[^\{0}]+\.dist-info'.format(os.sep) -+ candidates = [os.path.join(sp, p) for p in os.listdir(sp)] -+ # filter out candidate dirs based on the above regexp -+ filtered = [c for c in candidates if re.match(dist_info_re, c)] -+ # if we have 0 or 2 or more dirs, something is wrong... -+ if len(filtered) == 1: -+ path = filtered[0] -+ if path is not None: -+ records.append(os.path.join(path, 'RECORD')) -+ else: -+ records.append(None) -+ return records -+ -+def rewheel_from_record(record_path, outdir): -+ """Recreates a whee of package with given record_path and returns path -+ to the newly created wheel.""" -+ site_dir = os.path.dirname(os.path.dirname(record_path)) -+ record_relpath = record_path[len(site_dir):].strip(os.path.sep) -+ to_write, to_omit = get_records_to_pack(site_dir, record_relpath) -+ new_wheel_name = get_wheel_name(record_path) -+ new_wheel_path = os.path.join(outdir, new_wheel_name + '.whl') -+ -+ new_wheel = zipfile.ZipFile(new_wheel_path, mode='w', compression=zipfile.ZIP_DEFLATED) -+ # we need to write a new record with just the files that we will write, -+ # e.g. not binaries and *.pyc/*.pyo files -+ if sys.version_info[0] < 3: -+ new_record = io.BytesIO() -+ else: -+ new_record = io.StringIO() -+ writer = csv.writer(new_record) -+ -+ # handle files that we can write straight away -+ for f, sha_hash, size in to_write: -+ new_wheel.write(os.path.join(site_dir, f), arcname=f) -+ writer.writerow([f, sha_hash,size]) -+ -+ # rewrite the old wheel file with a new computed one -+ writer.writerow([record_relpath, '', '']) -+ new_wheel.writestr(record_relpath, new_record.getvalue()) -+ -+ new_wheel.close() -+ -+ return new_wheel.filename -+ -+def get_wheel_name(record_path): -+ """Return proper name of the wheel, without .whl.""" -+ -+ wheel_info_path = os.path.join(os.path.dirname(record_path), 'WHEEL') -+ with codecs.open(wheel_info_path, encoding='utf-8') as wheel_info_file: -+ wheel_info = email.parser.Parser().parsestr(wheel_info_file.read().encode('utf-8')) -+ -+ metadata_path = os.path.join(os.path.dirname(record_path), 'METADATA') -+ with codecs.open(metadata_path, encoding='utf-8') as metadata_file: -+ metadata = email.parser.Parser().parsestr(metadata_file.read().encode('utf-8')) -+ -+ # construct name parts according to wheel spec -+ distribution = metadata.get('Name') -+ version = metadata.get('Version') -+ build_tag = '' # nothing for now -+ lang_tag = [] -+ for t in wheel_info.get_all('Tag'): -+ lang_tag.append(t.split('-')[0]) -+ lang_tag = '.'.join(lang_tag) -+ abi_tag, plat_tag = wheel_info.get('Tag').split('-')[1:3] -+ # leave out build tag, if it is empty -+ to_join = filter(None, [distribution, version, build_tag, lang_tag, abi_tag, plat_tag]) -+ return '-'.join(list(to_join)) -+ -+def get_records_to_pack(site_dir, record_relpath): -+ """Accepts path of sitedir and path of RECORD file relative to it. -+ Returns two lists: -+ - list of files that can be written to new RECORD straight away -+ - list of files that shouldn't be written or need some processing -+ (pyc and pyo files, scripts) -+ """ -+ record_file_path = os.path.join(site_dir, record_relpath) -+ with codecs.open(record_file_path, encoding='utf-8') as record_file: -+ record_contents = record_file.read() -+ # temporary fix for https://github.com/pypa/pip/issues/1376 -+ # we need to ignore files under ".data" directory -+ data_dir = os.path.dirname(record_relpath).strip(os.path.sep) -+ data_dir = data_dir[:-len('dist-info')] + 'data' -+ -+ to_write = [] -+ to_omit = [] -+ for l in record_contents.splitlines(): -+ spl = l.split(',') -+ if len(spl) == 3: -+ # new record will omit (or write differently): -+ # - abs paths, paths with ".." (entry points), -+ # - pyc+pyo files -+ # - the old RECORD file -+ # TODO: is there any better way to recognize an entry point? -+ if os.path.isabs(spl[0]) or spl[0].startswith('..') or \ -+ spl[0].endswith('.pyc') or spl[0].endswith('.pyo') or \ -+ spl[0] == record_relpath or spl[0].startswith(data_dir): -+ to_omit.append(spl) -+ else: -+ to_write.append(spl) -+ else: -+ pass # bad RECORD or empty line -+ return to_write, to_omit -diff --git a/Makefile.pre.in b/Makefile.pre.in -index 877698c..2c43611 100644 ---- a/Makefile.pre.in -+++ b/Makefile.pre.in -@@ -1065,7 +1065,7 @@ LIBSUBDIRS= lib-tk lib-tk/test lib-tk/test/test_tkinter \ - test/tracedmodules \ - encodings compiler hotshot \ - email email/mime email/test email/test/data \ -- ensurepip ensurepip/_bundled \ -+ ensurepip ensurepip/_bundled ensurepip/rewheel\ - json json/tests \ - sqlite3 sqlite3/test \ - logging bsddb bsddb/test csv importlib wsgiref \ diff --git a/SOURCES/00320-CVE-2019-9636.patch b/SOURCES/00320-CVE-2019-9636.patch deleted file mode 100644 index 9508a4e..0000000 --- a/SOURCES/00320-CVE-2019-9636.patch +++ /dev/null @@ -1,142 +0,0 @@ -diff --git a/Doc/library/urlparse.rst b/Doc/library/urlparse.rst -index 22249da..0989c88 100644 ---- a/Doc/library/urlparse.rst -+++ b/Doc/library/urlparse.rst -@@ -119,12 +119,22 @@ The :mod:`urlparse` module defines the following functions: - See section :ref:`urlparse-result-object` for more information on the result - object. - -+ Characters in the :attr:`netloc` attribute that decompose under NFKC -+ normalization (as used by the IDNA encoding) into any of ``/``, ``?``, -+ ``#``, ``@``, or ``:`` will raise a :exc:`ValueError`. If the URL is -+ decomposed before parsing, or is not a Unicode string, no error will be -+ raised. -+ - .. versionchanged:: 2.5 - Added attributes to return value. - - .. versionchanged:: 2.7 - Added IPv6 URL parsing capabilities. - -+ .. versionchanged:: 2.7.17 -+ Characters that affect netloc parsing under NFKC normalization will -+ now raise :exc:`ValueError`. -+ - - .. function:: parse_qs(qs[, keep_blank_values[, strict_parsing[, max_num_fields]]]) - -@@ -232,11 +242,21 @@ The :mod:`urlparse` module defines the following functions: - See section :ref:`urlparse-result-object` for more information on the result - object. - -+ Characters in the :attr:`netloc` attribute that decompose under NFKC -+ normalization (as used by the IDNA encoding) into any of ``/``, ``?``, -+ ``#``, ``@``, or ``:`` will raise a :exc:`ValueError`. If the URL is -+ decomposed before parsing, or is not a Unicode string, no error will be -+ raised. -+ - .. versionadded:: 2.2 - - .. versionchanged:: 2.5 - Added attributes to return value. - -+ .. versionchanged:: 2.7.17 -+ Characters that affect netloc parsing under NFKC normalization will -+ now raise :exc:`ValueError`. -+ - - .. function:: urlunsplit(parts) - -diff --git a/Lib/test/test_urlparse.py b/Lib/test/test_urlparse.py -index 4e1ded7..857ed96 100644 ---- a/Lib/test/test_urlparse.py -+++ b/Lib/test/test_urlparse.py -@@ -1,4 +1,6 @@ - from test import test_support -+import sys -+import unicodedata - import unittest - import urlparse - -@@ -624,6 +626,36 @@ class UrlParseTestCase(unittest.TestCase): - self.assertEqual(urlparse.urlparse("http://www.python.org:80"), - ('http','www.python.org:80','','','','')) - -+ def test_urlsplit_normalization(self): -+ # Certain characters should never occur in the netloc, -+ # including under normalization. -+ # Ensure that ALL of them are detected and cause an error -+ illegal_chars = u'/:#?@' -+ hex_chars = {'{:04X}'.format(ord(c)) for c in illegal_chars} -+ denorm_chars = [ -+ c for c in map(unichr, range(128, sys.maxunicode)) -+ if (hex_chars & set(unicodedata.decomposition(c).split())) -+ and c not in illegal_chars -+ ] -+ # Sanity check that we found at least one such character -+ self.assertIn(u'\u2100', denorm_chars) -+ self.assertIn(u'\uFF03', denorm_chars) -+ -+ # bpo-36742: Verify port separators are ignored when they -+ # existed prior to decomposition -+ urlparse.urlsplit(u'http://\u30d5\u309a:80') -+ with self.assertRaises(ValueError): -+ urlparse.urlsplit(u'http://\u30d5\u309a\ufe1380') -+ -+ for scheme in [u"http", u"https", u"ftp"]: -+ for netloc in [u"netloc{}false.netloc", u"n{}user@netloc"]: -+ for c in denorm_chars: -+ url = u"{}://{}/path".format(scheme, netloc.format(c)) -+ if test_support.verbose: -+ print "Checking %r" % url -+ with self.assertRaises(ValueError): -+ urlparse.urlsplit(url) -+ - def test_main(): - test_support.run_unittest(UrlParseTestCase) - -diff --git a/Lib/urlparse.py b/Lib/urlparse.py -index f7c2b03..6834f3c 100644 ---- a/Lib/urlparse.py -+++ b/Lib/urlparse.py -@@ -165,6 +165,24 @@ def _splitnetloc(url, start=0): - delim = min(delim, wdelim) # use earliest delim position - return url[start:delim], url[delim:] # return (domain, rest) - -+def _checknetloc(netloc): -+ if not netloc or not isinstance(netloc, unicode): -+ return -+ # looking for characters like \u2100 that expand to 'a/c' -+ # IDNA uses NFKC equivalence, so normalize for this check -+ import unicodedata -+ n = netloc.replace(u'@', u'') # ignore characters already included -+ n = n.replace(u':', u'') # but not the surrounding text -+ n = n.replace(u'#', u'') -+ n = n.replace(u'?', u'') -+ netloc2 = unicodedata.normalize('NFKC', n) -+ if n == netloc2: -+ return -+ for c in '/?#@:': -+ if c in netloc2: -+ raise ValueError(u"netloc '" + netloc + u"' contains invalid " + -+ u"characters under NFKC normalization") -+ - def urlsplit(url, scheme='', allow_fragments=True): - """Parse a URL into 5 components: - :///?# -@@ -193,6 +211,7 @@ def urlsplit(url, scheme='', allow_fragments=True): - url, fragment = url.split('#', 1) - if '?' in url: - url, query = url.split('?', 1) -+ _checknetloc(netloc) - v = SplitResult(scheme, netloc, url, query, fragment) - _parse_cache[key] = v - return v -@@ -216,6 +235,7 @@ def urlsplit(url, scheme='', allow_fragments=True): - url, fragment = url.split('#', 1) - if '?' in url: - url, query = url.split('?', 1) -+ _checknetloc(netloc) - v = SplitResult(scheme, netloc, url, query, fragment) - _parse_cache[key] = v - return v diff --git a/SOURCES/00323-coverity-scan-fixes.patch b/SOURCES/00323-coverity-scan-fixes.patch deleted file mode 100644 index 4b12000..0000000 --- a/SOURCES/00323-coverity-scan-fixes.patch +++ /dev/null @@ -1,423 +0,0 @@ -diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c -index 2097342..defcde1 100644 ---- a/Modules/_ctypes/callproc.c -+++ b/Modules/_ctypes/callproc.c -@@ -1831,6 +1831,7 @@ POINTER(PyObject *self, PyObject *cls) - "s(O){}", - buf, - &PyCPointer_Type); -+ PyMem_Free(buf); - if (result == NULL) - return result; - key = PyLong_FromVoidPtr(result); -diff --git a/Modules/_ctypes/cfield.c b/Modules/_ctypes/cfield.c -index 46f041b..1b495fc 100644 ---- a/Modules/_ctypes/cfield.c -+++ b/Modules/_ctypes/cfield.c -@@ -1291,24 +1291,16 @@ U_set(void *ptr, PyObject *value, Py_ssize_t length) - static PyObject * - s_get(void *ptr, Py_ssize_t size) - { -- PyObject *result; -- size_t slen; -+ Py_ssize_t i; -+ char *p; - -- result = PyString_FromString((char *)ptr); -- if (!result) -- return NULL; -- /* chop off at the first NUL character, if any. -- * On error, result will be deallocated and set to NULL. -- */ -- slen = strlen(PyString_AS_STRING(result)); -- size = min(size, (Py_ssize_t)slen); -- if (result->ob_refcnt == 1) { -- /* shorten the result */ -- _PyString_Resize(&result, size); -- return result; -- } else -- /* cannot shorten the result */ -- return PyString_FromStringAndSize(ptr, size); -+ p = (char *)ptr; -+ for (i = 0; i < size; ++i) { -+ if (*p++ == '\0') -+ break; -+ } -+ -+ return PyBytes_FromStringAndSize((char *)ptr, (Py_ssize_t)i); - } - - static PyObject * -diff --git a/Modules/_hashopenssl.c b/Modules/_hashopenssl.c -index de69f6f..78445eb 100644 ---- a/Modules/_hashopenssl.c -+++ b/Modules/_hashopenssl.c -@@ -133,12 +133,6 @@ newEVPobject(PyObject *name) - if (retval == NULL) - return NULL; - -- retval->ctx = EVP_MD_CTX_new(); -- if (retval->ctx == NULL) { -- PyErr_NoMemory(); -- return NULL; -- } -- - /* save the name for .name to return */ - Py_INCREF(name); - retval->name = name; -@@ -146,6 +140,13 @@ newEVPobject(PyObject *name) - retval->lock = NULL; - #endif - -+ retval->ctx = EVP_MD_CTX_new(); -+ if (retval->ctx == NULL) { -+ Py_DECREF(retval); -+ PyErr_NoMemory(); -+ return NULL; -+ } -+ - return retval; - } - -@@ -205,6 +206,7 @@ EVP_copy(EVPobject *self, PyObject *unused) - return NULL; - - if (!locked_EVP_MD_CTX_copy(newobj->ctx, self)) { -+ Py_DECREF(newobj); - return _setException(PyExc_ValueError); - } - return (PyObject *)newobj; -diff --git a/Modules/_hotshot.c b/Modules/_hotshot.c -index 33cd38d..4fc5cee 100644 ---- a/Modules/_hotshot.c -+++ b/Modules/_hotshot.c -@@ -482,8 +482,11 @@ restart: - } - else if (!err) { - result = PyTuple_New(4); -- if (result == NULL) -+ if (result == NULL) { -+ Py_XDECREF(s1); -+ Py_XDECREF(s2); - return NULL; -+ } - PyTuple_SET_ITEM(result, 0, PyInt_FromLong(what)); - PyTuple_SET_ITEM(result, 2, PyInt_FromLong(fileno)); - if (s1 == NULL) -diff --git a/Modules/_io/bufferedio.c b/Modules/_io/bufferedio.c -index b8c98a4..d68f7d8 100644 ---- a/Modules/_io/bufferedio.c -+++ b/Modules/_io/bufferedio.c -@@ -1363,6 +1363,7 @@ _bufferedreader_read_all(buffered *self) - res = buffered_flush_and_rewind_unlocked(self); - if (res == NULL) { - Py_DECREF(chunks); -+ Py_XDECREF(data); - return NULL; - } - Py_CLEAR(res); -diff --git a/Modules/_json.c b/Modules/_json.c -index 3a88882..050d37d 100644 ---- a/Modules/_json.c -+++ b/Modules/_json.c -@@ -1375,8 +1375,10 @@ _match_number_str(PyScannerObject *s, PyObject *pystr, Py_ssize_t start, Py_ssiz - else { - double d = PyOS_string_to_double(PyString_AS_STRING(numstr), - NULL, NULL); -- if (d == -1.0 && PyErr_Occurred()) -+ if (d == -1.0 && PyErr_Occurred()) { -+ Py_DECREF(numstr); - return NULL; -+ } - rval = PyFloat_FromDouble(d); - } - } -diff --git a/Modules/linuxaudiodev.c b/Modules/linuxaudiodev.c -index 7fe20ae..f5135d9 100644 ---- a/Modules/linuxaudiodev.c -+++ b/Modules/linuxaudiodev.c -@@ -126,10 +126,12 @@ newladobject(PyObject *arg) - } - if (imode == O_WRONLY && ioctl(fd, SNDCTL_DSP_NONBLOCK, NULL) == -1) { - PyErr_SetFromErrnoWithFilename(LinuxAudioError, basedev); -+ close(fd); - return NULL; - } - if (ioctl(fd, SNDCTL_DSP_GETFMTS, &afmts) == -1) { - PyErr_SetFromErrnoWithFilename(LinuxAudioError, basedev); -+ close(fd); - return NULL; - } - /* Create and initialize the object */ -diff --git a/Parser/myreadline.c b/Parser/myreadline.c -index 59db41a..5376214 100644 ---- a/Parser/myreadline.c -+++ b/Parser/myreadline.c -@@ -108,7 +108,7 @@ char * - PyOS_StdioReadline(FILE *sys_stdin, FILE *sys_stdout, char *prompt) - { - size_t n; -- char *p; -+ char *p, *pr; - n = 100; - if ((p = (char *)PyMem_MALLOC(n)) == NULL) - return NULL; -@@ -140,17 +140,29 @@ PyOS_StdioReadline(FILE *sys_stdin, FILE *sys_stdout, char *prompt) - n = strlen(p); - while (n > 0 && p[n-1] != '\n') { - size_t incr = n+2; -- p = (char *)PyMem_REALLOC(p, n + incr); -- if (p == NULL) -- return NULL; - if (incr > INT_MAX) { -+ PyMem_FREE(p); - PyErr_SetString(PyExc_OverflowError, "input line too long"); -+ return NULL; -+ } -+ pr = (char *)PyMem_REALLOC(p, n + incr); -+ if (pr == NULL) { -+ PyMem_FREE(p); -+ PyErr_NoMemory(); -+ return NULL; - } -+ p = pr; - if (my_fgets(p+n, (int)incr, sys_stdin) != 0) - break; - n += strlen(p+n); - } -- return (char *)PyMem_REALLOC(p, n+1); -+ pr = (char *)PyMem_REALLOC(p, n+1); -+ if (pr == NULL) { -+ PyMem_FREE(p); -+ PyErr_NoMemory(); -+ return NULL; -+ } -+ return pr; - } - - -diff --git a/Parser/tokenizer.c b/Parser/tokenizer.c -index c6e61df..8966661 100644 ---- a/Parser/tokenizer.c -+++ b/Parser/tokenizer.c -@@ -656,9 +656,14 @@ translate_newlines(const char *s, int exec_input, struct tok_state *tok) { - } - *current = '\0'; - final_length = current - buf + 1; -- if (final_length < needed_length && final_length) -+ if (final_length < needed_length && final_length) { - /* should never fail */ -- buf = PyMem_REALLOC(buf, final_length); -+ char* result = PyMem_REALLOC(buf, final_length); -+ if (result == NULL) { -+ PyMem_FREE(buf); -+ } -+ buf = result; -+ } - return buf; - } - -diff --git a/Python/dtoa.c b/Python/dtoa.c -index 73e23af..25eb9a7 100644 ---- a/Python/dtoa.c -+++ b/Python/dtoa.c -@@ -1514,8 +1514,9 @@ _Py_dg_strtod(const char *s00, char **se) - ULong y, z, abs_exp; - Long L; - BCinfo bc; -- Bigint *bb, *bb1, *bd, *bd0, *bs, *delta; -+ Bigint *bb = NULL, *bd = NULL, *bd0 = NULL, *bs = NULL, *delta = NULL; - size_t ndigits, fraclen; -+ double result; - - dval(&rv) = 0.; - -@@ -1707,7 +1708,6 @@ _Py_dg_strtod(const char *s00, char **se) - if (k > 9) { - dval(&rv) = tens[k - 9] * dval(&rv) + z; - } -- bd0 = 0; - if (nd <= DBL_DIG - && Flt_Rounds == 1 - ) { -@@ -1877,14 +1877,11 @@ _Py_dg_strtod(const char *s00, char **se) - - bd = Balloc(bd0->k); - if (bd == NULL) { -- Bfree(bd0); - goto failed_malloc; - } - Bcopy(bd, bd0); - bb = sd2b(&rv, bc.scale, &bbe); /* srv = bb * 2^bbe */ - if (bb == NULL) { -- Bfree(bd); -- Bfree(bd0); - goto failed_malloc; - } - /* Record whether lsb of bb is odd, in case we need this -@@ -1894,9 +1891,6 @@ _Py_dg_strtod(const char *s00, char **se) - /* tdv = bd * 10**e; srv = bb * 2**bbe */ - bs = i2b(1); - if (bs == NULL) { -- Bfree(bb); -- Bfree(bd); -- Bfree(bd0); - goto failed_malloc; - } - -@@ -1945,56 +1939,39 @@ _Py_dg_strtod(const char *s00, char **se) - - /* Scale bb, bd, bs by the appropriate powers of 2 and 5. */ - if (bb5 > 0) { -+ Bigint *bb1; - bs = pow5mult(bs, bb5); - if (bs == NULL) { -- Bfree(bb); -- Bfree(bd); -- Bfree(bd0); - goto failed_malloc; - } - bb1 = mult(bs, bb); - Bfree(bb); - bb = bb1; - if (bb == NULL) { -- Bfree(bs); -- Bfree(bd); -- Bfree(bd0); - goto failed_malloc; - } - } - if (bb2 > 0) { - bb = lshift(bb, bb2); - if (bb == NULL) { -- Bfree(bs); -- Bfree(bd); -- Bfree(bd0); - goto failed_malloc; - } - } - if (bd5 > 0) { - bd = pow5mult(bd, bd5); - if (bd == NULL) { -- Bfree(bb); -- Bfree(bs); -- Bfree(bd0); - goto failed_malloc; - } - } - if (bd2 > 0) { - bd = lshift(bd, bd2); - if (bd == NULL) { -- Bfree(bb); -- Bfree(bs); -- Bfree(bd0); - goto failed_malloc; - } - } - if (bs2 > 0) { - bs = lshift(bs, bs2); - if (bs == NULL) { -- Bfree(bb); -- Bfree(bd); -- Bfree(bd0); - goto failed_malloc; - } - } -@@ -2005,10 +1982,6 @@ _Py_dg_strtod(const char *s00, char **se) - - delta = diff(bb, bd); - if (delta == NULL) { -- Bfree(bb); -- Bfree(bs); -- Bfree(bd); -- Bfree(bd0); - goto failed_malloc; - } - dsign = delta->sign; -@@ -2062,10 +2035,6 @@ _Py_dg_strtod(const char *s00, char **se) - } - delta = lshift(delta,Log2P); - if (delta == NULL) { -- Bfree(bb); -- Bfree(bs); -- Bfree(bd); -- Bfree(bd0); - goto failed_malloc; - } - if (cmp(delta, bs) > 0) -@@ -2167,11 +2136,6 @@ _Py_dg_strtod(const char *s00, char **se) - if ((word0(&rv) & Exp_mask) >= - Exp_msk1*(DBL_MAX_EXP+Bias-P)) { - if (word0(&rv0) == Big0 && word1(&rv0) == Big1) { -- Bfree(bb); -- Bfree(bd); -- Bfree(bs); -- Bfree(bd0); -- Bfree(delta); - goto ovfl; - } - word0(&rv) = Big0; -@@ -2213,16 +2177,11 @@ _Py_dg_strtod(const char *s00, char **se) - } - } - cont: -- Bfree(bb); -- Bfree(bd); -- Bfree(bs); -- Bfree(delta); -+ Bfree(bb); bb = NULL; -+ Bfree(bd); bd = NULL; -+ Bfree(bs); bs = NULL; -+ Bfree(delta); delta = NULL; - } -- Bfree(bb); -- Bfree(bd); -- Bfree(bs); -- Bfree(bd0); -- Bfree(delta); - if (bc.nd > nd) { - error = bigcomp(&rv, s0, &bc); - if (error) -@@ -2236,24 +2195,37 @@ _Py_dg_strtod(const char *s00, char **se) - } - - ret: -- return sign ? -dval(&rv) : dval(&rv); -+ result = sign ? -dval(&rv) : dval(&rv); -+ goto done; - - parse_error: -- return 0.0; -+ result = 0.0; -+ goto done; - - failed_malloc: - errno = ENOMEM; -- return -1.0; -+ result = -1.0; -+ goto done; - - undfl: -- return sign ? -0.0 : 0.0; -+ result = sign ? -0.0 : 0.0; -+ goto done; - - ovfl: - errno = ERANGE; - /* Can't trust HUGE_VAL */ - word0(&rv) = Exp_mask; - word1(&rv) = 0; -- return sign ? -dval(&rv) : dval(&rv); -+ result = sign ? -dval(&rv) : dval(&rv); -+ goto done; -+ -+ done: -+ Bfree(bb); -+ Bfree(bd); -+ Bfree(bs); -+ Bfree(bd0); -+ Bfree(delta); -+ return result; - - } - diff --git a/SOURCES/00324-disallow-control-chars-in-http-urls.patch b/SOURCES/00324-disallow-control-chars-in-http-urls.patch deleted file mode 100644 index 84e788f..0000000 --- a/SOURCES/00324-disallow-control-chars-in-http-urls.patch +++ /dev/null @@ -1,172 +0,0 @@ -diff --git a/Lib/httplib.py b/Lib/httplib.py -index 60a8fb4e355f..1b41c346e090 100644 ---- a/Lib/httplib.py -+++ b/Lib/httplib.py -@@ -247,6 +247,16 @@ - _is_legal_header_name = re.compile(r'\A[^:\s][^:\r\n]*\Z').match - _is_illegal_header_value = re.compile(r'\n(?![ \t])|\r(?![ \t\n])').search - -+# These characters are not allowed within HTTP URL paths. -+# See https://tools.ietf.org/html/rfc3986#section-3.3 and the -+# https://tools.ietf.org/html/rfc3986#appendix-A pchar definition. -+# Prevents CVE-2019-9740. Includes control characters such as \r\n. -+# Restrict non-ASCII characters above \x7f (0x80-0xff). -+_contains_disallowed_url_pchar_re = re.compile('[\x00-\x20\x7f-\xff]') -+# Arguably only these _should_ allowed: -+# _is_allowed_url_pchars_re = re.compile(r"^[/!$&'()*+,;=:@%a-zA-Z0-9._~-]+$") -+# We are more lenient for assumed real world compatibility purposes. -+ - # 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'} -@@ -927,6 +937,12 @@ def putrequest(self, method, url, skip_host=0, skip_accept_encoding=0): - self._method = method - if not url: - url = '/' -+ # Prevent CVE-2019-9740. -+ match = _contains_disallowed_url_pchar_re.search(url) -+ if match: -+ raise InvalidURL("URL can't contain control characters. %r " -+ "(found at least %r)" -+ % (url, match.group())) - hdr = '%s %s %s' % (method, url, self._http_vsn_str) - - self._output(hdr) -diff --git a/Lib/test/test_urllib.py b/Lib/test/test_urllib.py -index 1ce9201c0693..d7778d4194f3 100644 ---- a/Lib/test/test_urllib.py -+++ b/Lib/test/test_urllib.py -@@ -257,6 +257,31 @@ def test_url_fragment(self): - finally: - self.unfakehttp() - -+ def test_url_with_control_char_rejected(self): -+ for char_no in range(0, 0x21) + range(0x7f, 0x100): -+ char = chr(char_no) -+ schemeless_url = "//localhost:7777/test%s/" % char -+ self.fakehttp(b"HTTP/1.1 200 OK\r\n\r\nHello.") -+ try: -+ # urllib quotes the URL so there is no injection. -+ resp = urllib.urlopen("http:" + schemeless_url) -+ self.assertNotIn(char, resp.geturl()) -+ finally: -+ self.unfakehttp() -+ -+ def test_url_with_newline_header_injection_rejected(self): -+ self.fakehttp(b"HTTP/1.1 200 OK\r\n\r\nHello.") -+ host = "localhost:7777?a=1 HTTP/1.1\r\nX-injected: header\r\nTEST: 123" -+ schemeless_url = "//" + host + ":8080/test/?test=a" -+ try: -+ # urllib quotes the URL so there is no injection. -+ resp = urllib.urlopen("http:" + schemeless_url) -+ self.assertNotIn(' ', resp.geturl()) -+ self.assertNotIn('\r', resp.geturl()) -+ self.assertNotIn('\n', resp.geturl()) -+ finally: -+ self.unfakehttp() -+ - def test_read_bogus(self): - # urlopen() should raise IOError for many error codes. - self.fakehttp('''HTTP/1.1 401 Authentication Required -diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py -index 6d24d5ddf83c..9531818e16b2 100644 ---- a/Lib/test/test_urllib2.py -+++ b/Lib/test/test_urllib2.py -@@ -15,6 +15,9 @@ - except ImportError: - ssl = None - -+from test.test_urllib import FakeHTTPMixin -+ -+ - # XXX - # Request - # CacheFTPHandler (hard to write) -@@ -1262,7 +1265,7 @@ def _test_basic_auth(self, opener, auth_handler, auth_header, - self.assertEqual(len(http_handler.requests), 1) - self.assertFalse(http_handler.requests[0].has_header(auth_header)) - --class MiscTests(unittest.TestCase): -+class MiscTests(unittest.TestCase, FakeHTTPMixin): - - def test_build_opener(self): - class MyHTTPHandler(urllib2.HTTPHandler): pass -@@ -1317,6 +1320,52 @@ def test_unsupported_algorithm(self): - "Unsupported digest authentication algorithm 'invalid'" - ) - -+ @unittest.skipUnless(ssl, "ssl module required") -+ def test_url_with_control_char_rejected(self): -+ for char_no in range(0, 0x21) + range(0x7f, 0x100): -+ char = chr(char_no) -+ schemeless_url = "//localhost:7777/test%s/" % char -+ self.fakehttp(b"HTTP/1.1 200 OK\r\n\r\nHello.") -+ try: -+ # We explicitly test urllib.request.urlopen() instead of the top -+ # level 'def urlopen()' function defined in this... (quite ugly) -+ # test suite. They use different url opening codepaths. Plain -+ # urlopen uses FancyURLOpener which goes via a codepath that -+ # calls urllib.parse.quote() on the URL which makes all of the -+ # above attempts at injection within the url _path_ safe. -+ escaped_char_repr = repr(char).replace('\\', r'\\') -+ InvalidURL = httplib.InvalidURL -+ with self.assertRaisesRegexp( -+ InvalidURL, "contain control.*" + escaped_char_repr): -+ urllib2.urlopen("http:" + schemeless_url) -+ with self.assertRaisesRegexp( -+ InvalidURL, "contain control.*" + escaped_char_repr): -+ urllib2.urlopen("https:" + schemeless_url) -+ finally: -+ self.unfakehttp() -+ -+ @unittest.skipUnless(ssl, "ssl module required") -+ def test_url_with_newline_header_injection_rejected(self): -+ self.fakehttp(b"HTTP/1.1 200 OK\r\n\r\nHello.") -+ host = "localhost:7777?a=1 HTTP/1.1\r\nX-injected: header\r\nTEST: 123" -+ schemeless_url = "//" + host + ":8080/test/?test=a" -+ try: -+ # We explicitly test urllib2.urlopen() instead of the top -+ # level 'def urlopen()' function defined in this... (quite ugly) -+ # test suite. They use different url opening codepaths. Plain -+ # urlopen uses FancyURLOpener which goes via a codepath that -+ # calls urllib.parse.quote() on the URL which makes all of the -+ # above attempts at injection within the url _path_ safe. -+ InvalidURL = httplib.InvalidURL -+ with self.assertRaisesRegexp( -+ InvalidURL, r"contain control.*\\r.*(found at least . .)"): -+ urllib2.urlopen("http:" + schemeless_url) -+ with self.assertRaisesRegexp(InvalidURL, r"contain control.*\\n"): -+ urllib2.urlopen("https:" + schemeless_url) -+ finally: -+ self.unfakehttp() -+ -+ - - class RequestTests(unittest.TestCase): - -diff --git a/Lib/test/test_xmlrpc.py b/Lib/test/test_xmlrpc.py -index 36b3be67fd6b..90ccb30716ff 100644 ---- a/Lib/test/test_xmlrpc.py -+++ b/Lib/test/test_xmlrpc.py -@@ -659,7 +659,13 @@ def test_dotted_attribute(self): - def test_partial_post(self): - # Check that a partial POST doesn't make the server loop: issue #14001. - conn = httplib.HTTPConnection(ADDR, PORT) -- conn.request('POST', '/RPC2 HTTP/1.0\r\nContent-Length: 100\r\n\r\nbye') -+ conn.send('POST /RPC2 HTTP/1.0\r\n' -+ 'Content-Length: 100\r\n\r\n' -+ 'bye HTTP/1.1\r\n' -+ 'Host: %s:%s\r\n' -+ 'Accept-Encoding: identity\r\n' -+ 'Content-Length: 0\r\n\r\n' -+ % (ADDR, PORT)) - conn.close() - - class SimpleServerEncodingTestCase(BaseServerTestCase): -diff --git a/Misc/NEWS.d/next/Security/2019-04-10-08-53-30.bpo-30458.51E-DA.rst b/Misc/NEWS.d/next/Security/2019-04-10-08-53-30.bpo-30458.51E-DA.rst -new file mode 100644 -index 000000000000..47cb899df1af ---- /dev/null -+++ b/Misc/NEWS.d/next/Security/2019-04-10-08-53-30.bpo-30458.51E-DA.rst -@@ -0,0 +1 @@ -+Address CVE-2019-9740 by disallowing URL paths with embedded whitespace or control characters through into the underlying http client request. Such potentially malicious header injection URLs now cause an httplib.InvalidURL exception to be raised. diff --git a/SOURCES/00325-CVE-2019-9948.patch b/SOURCES/00325-CVE-2019-9948.patch deleted file mode 100644 index 890bf71..0000000 --- a/SOURCES/00325-CVE-2019-9948.patch +++ /dev/null @@ -1,37 +0,0 @@ -diff --git a/Lib/test/test_urllib.py b/Lib/test/test_urllib.py -index d2da0f8..7813b9f 100644 ---- a/Lib/test/test_urllib.py -+++ b/Lib/test/test_urllib.py -@@ -872,6 +872,17 @@ class URLopener_Tests(unittest.TestCase): - "spam://c:|windows%/:=&?~#+!$,;'@()*[]|/path/"), - "//c:|windows%/:=&?~#+!$,;'@()*[]|/path/") - -+ def test_local_file_open(self): -+ # bpo-35907, CVE-2019-9948: urllib must reject local_file:// scheme -+ class DummyURLopener(urllib.URLopener): -+ def open_local_file(self, url): -+ return url -+ for url in ('local_file://example', 'local-file://example'): -+ self.assertRaises(IOError, urllib.urlopen, url) -+ self.assertRaises(IOError, urllib.URLopener().open, url) -+ self.assertRaises(IOError, urllib.URLopener().retrieve, url) -+ self.assertRaises(IOError, DummyURLopener().open, url) -+ self.assertRaises(IOError, DummyURLopener().retrieve, url) - - # Just commented them out. - # Can't really tell why keep failing in windows and sparc. -diff --git a/Lib/urllib.py b/Lib/urllib.py -index 2201e3e..71e3637 100644 ---- a/Lib/urllib.py -+++ b/Lib/urllib.py -@@ -198,7 +198,9 @@ class URLopener: - name = 'open_' + urltype - self.type = urltype - name = name.replace('-', '_') -- if not hasattr(self, name): -+ -+ # bpo-35907: disallow the file reading with the type not allowed -+ if not hasattr(self, name) or name == 'open_local_file': - if proxy: - return self.open_unknown_proxy(proxy, fullurl, data) - else: diff --git a/SPECS/python2.spec b/SPECS/python2.spec index a267ceb..00e89ee 100644 --- a/SPECS/python2.spec +++ b/SPECS/python2.spec @@ -5,8 +5,9 @@ # Note that the bcond macros are named for the CLI option they create. # "%%bcond_without" means "ENABLE by default and create a --without option" -# Ability to reuse RPM-installed pip using rewheel -%bcond_without rewheel +# Whether to use RPM build wheels from the python2-{pip,setuptools}-wheel package +# Uses upstream bundled prebuilt wheels otherwise +%bcond_without rpmwheels # Extra build for debugging the interpreter or C-API extensions # (the -debug subpackages) @@ -102,8 +103,8 @@ Summary: An interpreted, interactive, object-oriented programming language Name: %{python} # Remember to also rebase python2-docs when changing this: -Version: 2.7.16 -Release: 9%{?dist} +Version: 2.7.17 +Release: 1%{?dist} License: Python Group: Development/Languages Requires: %{python}-libs%{?_isa} = %{version}-%{release} @@ -165,19 +166,22 @@ BuildRequires: valgrind-devel BuildRequires: zlib-devel -%if %{with rewheel} -BuildRequires: python2-setuptools -Requires: python2-setuptools - -BuildRequires: python2-pip -Requires: python2-pip -%endif # rewheel +%if %{with rpmwheels} +BuildRequires: python2-setuptools-wheel +BuildRequires: python2-pip-wheel +%endif # Runtime require alternatives Requires: %{_sbindir}/alternatives Requires(post): %{_sbindir}/alternatives Requires(postun): %{_sbindir}/alternatives +# Previously, this was required for our rewheel patch to work. +# This is technically no longer needed, but we keep it recommended +# for the developer experience. +Recommends: python2-setuptools +Recommends: python2-pip + # ======================= # Source code and patches @@ -562,20 +566,15 @@ Patch144: 00144-no-gdbm.patch # 00146 # # Support OpenSSL FIPS mode (e.g. when OPENSSL_FORCE_FIPS_MODE=1 is set) -# - handle failures from OpenSSL (e.g. on attempts to use MD5 in a +# - handle some failures from OpenSSL (e.g. on attempts to use MD5 in a # FIPS-enforcing environment) # - add a new "usedforsecurity" keyword argument to the various digest # algorithms in hashlib so that you can whitelist a callsite with # "usedforsecurity=False" -# (sent upstream for python 3 as http://bugs.python.org/issue9216; this is a -# backport to python 2.7; see RHEL6 patch 119) -# - enforce usage of the _hashlib implementation: don't fall back to the _md5 -# and _sha* modules (leading to clearer error messages if fips selftests -# fail) # - don't build the _md5 and _sha* modules; rely on the _hashlib implementation # of hashlib (for example, md5.py will use _hashlib's implementation of MD5, # if permitted by the FIPS setting) -# (rhbz#563986) +# Resolves: rhbz#1734126 Patch146: 00146-hashlib-fips.patch # 00147 # @@ -584,13 +583,6 @@ Patch146: 00146-hashlib-fips.patch # Sent upstream as http://bugs.python.org/issue14785 Patch147: 00147-add-debug-malloc-stats.patch -# 00153 # -# Strip out lines of the form "warning: Unable to open ..." from gdb's stderr -# when running test_gdb.py; also cope with change to gdb in F17 onwards in -# which values are printed as "v@entry" rather than just "v": -# Not yet sent upstream -Patch153: 00153-fix-test_gdb-noise.patch - # 00155 # # Avoid allocating thunks in ctypes unless absolutely necessary, to avoid # generating SELinux denials on "import ctypes" and "import uuid" when @@ -604,22 +596,6 @@ Patch155: 00155-avoid-ctypes-thunks.patch # Not yet sent upstream Patch156: 00156-gdb-autoload-safepath.patch -# 00157 # -# Update uid/gid handling throughout the standard library: uid_t and gid_t are -# unsigned 32-bit values, but existing code often passed them through C long -# values, which are signed 32-bit values on 32-bit architectures, leading to -# negative int objects for uid/gid values >= 2^31 on 32-bit architectures. -# -# Introduce _PyObject_FromUid/Gid to convert uid_t/gid_t values to python -# objects, using int objects where the value will fit (long objects otherwise), -# and _PyArg_ParseUid/Gid to convert int/long to uid_t/gid_t, with -1 allowed -# as a special case (since this is given special meaning by the chown syscall) -# -# Update standard library to use this throughout for uid/gid values, so that -# very large uid/gid values are round-trippable, and -1 remains usable. -# (rhbz#697470) -Patch157: 00157-uid-gid-overflows.patch - # 00165 # # Backport to Python 2 from Python 3.3 of improvements to the "crypt" module # adding precanned ways of salting a password (rhbz#835021) @@ -638,26 +614,6 @@ Patch165: 00165-crypt-module-salt-backport.patch # Not yet sent upstream Patch167: 00167-disable-stack-navigation-tests-when-optimized-in-test_gdb.patch -# 00168 # -# Update distutils.sysconfig so that if CFLAGS is defined in the environment, -# when building extension modules, it is appended to the full compilation -# flags from Python's Makefile, rather than instead reducing the compilation -# flags to the subset within OPT and adding it to those. -# -# In particular, this should ensure that "-fno-strict-aliasing" is used by -# "python setup.py build" even when CFLAGS is defined in the environment. -# -# (rhbz#849994) -Patch168: 00168-distutils-cflags.patch - -# 00169 # -# Use SHA-256 rather than implicitly using MD5 within the challenge handling -# in multiprocessing.connection -# -# Sent upstream as http://bugs.python.org/issue17258 -# (rhbz#879695) -Patch169: 00169-avoid-implicit-usage-of-md5-in-multiprocessing.patch - # 00170 # # In debug builds, try to print repr() when a C-level assert fails in the # garbage collector (typically indicating a reference-counting error @@ -705,10 +661,9 @@ Patch185: 00185-urllib2-honors-noproxy-for-ftp.patch Patch187: 00187-add-RPATH-to-pyexpat.patch # 00189 # -# Fixes gdb py-bt command not to raise exception while processing -# statements from eval -# rhbz#1008154 (patch by Attila Fazekas) -Patch189: 00189-gdb-py-bt-dont-raise-exception-from-eval.patch +# Instead of bundled wheels, use our RPM packaged wheels from +# /usr/share/python2-wheels +Patch189: 00189-use-rpm-wheels.patch # 00191 # # Disabling NOOP test as it fails without internet connection @@ -721,9 +676,6 @@ Patch191: 00191-disable-NOOP.patch # Patch provided by John C. Peterson Patch193: 00193-enable-loading-sqlite-extensions.patch -# 00198 # -Patch198: 00198-add-rewheel-module.patch - # 00257 # # Python's threading library doesn't use the monotonic clock when handling wait timeouts, # so when the system clock is set backwards, the wait doesn't return after the timeout, @@ -743,44 +695,6 @@ Patch288: 00288-ambiguous-python-version-rpmbuild-warn.patch # (we handle it it in Setup.dist, see Patch0) Patch289: 00289-disable-nis-detection.patch -# 00320 # -# Security fix for CVE-2019-9636: Information Disclosure due to urlsplit improper NFKC normalization -# Fixed upstream: https://bugs.python.org/issue36216 and https://bugs.python.org/issue36742 -# Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1689327 -Patch320: 00320-CVE-2019-9636.patch - -# 00323 # -# Coverity scan fixes -# Fixed upstream: -# https://bugs.python.org/issue13096 -# https://bugs.python.org/issue36147 -# https://bugs.python.org/issue36179 -# https://bugs.python.org/issue36212 -# https://bugs.python.org/issue36289 -# https://bugs.python.org/issue36291 -# https://bugs.python.org/issue36186 -# https://bugs.python.org/issue18368 -# https://bugs.python.org/issue36367 -# https://bugs.python.org/issue36262 -# https://bugs.python.org/issue36459 -# Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1690919 -Patch323: 00323-coverity-scan-fixes.patch - -# 00324 # -# Disallow control chars in http URLs -# Security fix for CVE-2019-9740 and CVE-2019-9947 -# Fixed upstream: https://bugs.python.org/issue30458 -# Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1703539 -# and https://bugzilla.redhat.com/show_bug.cgi?id=1704367 -Patch324: 00324-disallow-control-chars-in-http-urls.patch - -# 00325 # -# Unnecessary URL scheme exists to allow local_file:// reading file in urllib -# Security fix for CVE-2019-9948 -# Fixed upstream: https://bugs.python.org/issue35907 -# Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1704176 -Patch325: 00325-CVE-2019-9948.patch - # (New patches go here ^^^) # # When adding new patches to "python2" and "python3" in Fedora, EL, etc., @@ -846,6 +760,14 @@ Requires: glibc%{?_isa} >= 2.24.90-26 Requires: gdbm%{?_isa} >= 1:1.13 %endif +%if %{with rpmwheels} +Requires: python2-setuptools-wheel +Requires: python2-pip-wheel +%else +Provides: bundled(python2-pip) = 18.1 +Provides: bundled(python2-setuptools) = 40.6.2 +%endif + %description libs This package contains files used to embed Python 2 into applications. @@ -864,6 +786,15 @@ Requires: pkgconfig Requires: python3-rpm-generators %endif +# This is not "API" (packages that need setuptools should still BuildRequire it) +# However some packages apparently can build both with and without setuptools +# producing egg-info as file or directory (depending on setuptools presence). +# Directory-to-file updates are problematic in RPM, so we ensure setuptools is +# installed when -devel is required. +# See https://bugzilla.redhat.com/show_bug.cgi?id=1623922 +# See https://fedoraproject.org/wiki/Packaging:Directory_Replacement +Requires: python2-setuptools + # https://bugzilla.redhat.com/show_bug.cgi?id=1217376 # https://bugzilla.redhat.com/show_bug.cgi?id=1496757 # https://bugzilla.redhat.com/show_bug.cgi?id=1218294 @@ -1050,36 +981,30 @@ rm -r Modules/zlib || exit 1 %if !%{with_gdbm} %patch144 -p1 %endif -#patch146 -p1 +%patch146 -p1 %patch147 -p1 -%patch153 -p0 %patch155 -p1 %patch156 -p1 -%patch157 -p1 %patch165 -p1 mv Modules/cryptmodule.c Modules/_cryptmodule.c %patch167 -p1 -%patch168 -p1 -%patch169 -p1 %patch170 -p1 %patch174 -p1 -b .fix-for-usr-move %patch180 -p1 %patch181 -p1 %patch185 -p1 %patch187 -p1 + +%if %{with rpmwheels} %patch189 -p1 +rm Lib/ensurepip/_bundled/*.whl +%endif + %patch191 -p1 %patch193 -p1 -%if %{with rewheel} -%patch198 -p1 -%endif %patch257 -p1 %patch288 -p1 %patch289 -p1 -%patch320 -p1 -%patch323 -p1 -%patch324 -p1 -%patch325 -p1 # This shouldn't be necesarry, but is right now (2.2a3) find -name "*~" |xargs rm -f @@ -1803,11 +1728,11 @@ fi %dir %{pylibdir}/ensurepip/ %{pylibdir}/ensurepip/*.py* +%if %{with rpmwheels} %exclude %{pylibdir}/ensurepip/_bundled - -%if %{with rewheel} -%dir %{pylibdir}/ensurepip/rewheel/ -%{pylibdir}/ensurepip/rewheel/*.py* +%else +%dir %{pylibdir}/ensurepip/_bundled +%{pylibdir}/ensurepip/_bundled/*.whl %endif @@ -2018,8 +1943,27 @@ fi # ====================================================== %changelog +* Wed Oct 23 2019 Charalampos Stratakis - 2.7.17-1 +- Update to 2.7.17 +Resolves: rhbz#1759944 + +* Tue Sep 03 2019 Tomas Orsava - 2.7.16-12 +- Adding FIPS compliance to Python 2 in RHEL8: + - Updated patch 146 with a new version of the FIPS patch + - Patch 169 has been removed, obsoleted by the updated patch 146 +Resolves: rhbz#1734126 + +* Tue Jul 02 2019 Miro HronĨok - 2.7.16-11 +- Use RPM built wheels of pip and setuptools in ensurepip instead of our rewheel patch +- Require python2-setuptools from python2-devel to prevent packaging errors +Resolves: rhbz#1718398 + +* Tue Jun 11 2019 Charalampos Stratakis - 2.7.16-10 +- Fix urlparse.urlsplit() error message for Unicode URL +Resolves: rhbz#1689327 + * Fri Jun 07 2019 Charalampos Stratakis - 2.7.16-9 -- Yet another fix for CVE-2019-9636 +- Security fix for CVE-2019-10160 Resolves: rhbz#1689327 * Thu May 30 2019 Charalampos Stratakis - 2.7.16-8