CVE-2018-1000807: Use After Free (CWE-416)
value: Python Cryptographic Authority pyopenssl version prior to version 17.5.0 contains a CWE-416: Use After Free vulnerability in X509 object handling that can result in Use after free can lead to possible denial of service or remote code execution.. This attack appear to be exploitable via Depends on the calling application and if it retains a reference to the memory.. This vulnerability appears to have been fixed in 17.5.0.
CWE description: The product reuses or references memory after it has been freed. At some point afterward, the memory may be allocated again and saved in another pointer, while the original pointer references a location somewhere within the new allocation. Any operations using the original pointer are no longer valid because the memory "belongs" to the code that operates on the new pointer.
CVSS v3 base score: 8.1
Published: 2018-10-08T15:29Z
Repository: https://github.com/pyca/pyopenssl
Language: Python
Files changed: CHANGELOG.rst, setup.py, src/OpenSSL/SSL.py, src/OpenSSL/crypto.py, tests/test_ssl.py, tox.ini
Commit date: 2017-11-30 07:55:25 -0500
Commit message: fix a memory leak and a potential UAF and also #722 (#723)

* fix a memory leak and a potential UAF and also #722

* sanity check

* bump cryptography minimum version, add changelog

Diff: diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index abf3efa..3f5590f 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -9,11 +9,11 @@ The third digit is only for regressions.
 
 
 Backward-incompatible changes:
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-*none*
+* The minimum ``cryptography`` version is now 2.1.4.
 
 
 Deprecations:
 ^^^^^^^^^^^^^
 
@@ -21,12 +21,12 @@ Deprecations:
 
 
 Changes:
 ^^^^^^^^
 
-
-*none*
+- Fixed a potential use-after-free in the verify callback and resolved a memory leak when loading PKCS12 files with ``cacerts``.
+  `#723 <https://github.com/pyca/pyopenssl/pull/723>`_
 
 ----
 
 
 
diff --git a/setup.py b/setup.py
index 937d556..85252e9 100755
--- a/setup.py
+++ b/setup.py
@@ -93,11 +93,11 @@ if __name__ == "__main__":
 
         packages=find_packages(where="src"),
         package_dir={"": "src"},
         install_requires=[
             # Fix cryptographyMinimum in tox.ini when changing this!
-            "cryptography>=1.9",
+            "cryptography>=2.1.4",
             "six>=1.5.2"
         ],
         extras_require={
             "test": [
                 "flaky",
diff --git a/src/OpenSSL/SSL.py b/src/OpenSSL/SSL.py
index 32c038a..ec33814 100644
--- a/src/OpenSSL/SSL.py
+++ b/src/OpenSSL/SSL.py
@@ -307,12 +307,13 @@ class _VerifyHelper(_CallbackExceptionHelper):
     def __init__(self, callback):
         _CallbackExceptionHelper.__init__(self)
 
         @wraps(callback)
         def wrapper(ok, store_ctx):
-            cert = X509.__new__(X509)
-            cert._x509 = _lib.X509_STORE_CTX_get_current_cert(store_ctx)
+            x509 = _lib.X509_STORE_CTX_get_current_cert(store_ctx)
+            _lib.X509_up_ref(x509)
+            cert = X509._from_raw_x509_ptr(x509)
             error_number = _lib.X509_STORE_CTX_get_error(store_ctx)
             error_depth = _lib.X509_STORE_CTX_get_error_depth(store_ctx)
 
             index = _lib.SSL_get_ex_data_X509_STORE_CTX_idx()
             ssl = _lib.X509_STORE_CTX_get_ex_data(store_ctx, index)
diff --git a/src/OpenSSL/crypto.py b/src/OpenSSL/crypto.py
index ecd055e..12b4db0 100644
--- a/src/OpenSSL/crypto.py
+++ b/src/OpenSSL/crypto.py
@@ -3056,12 +3056,11 @@ def load_pkcs12(buffer, passphrase=None):
 
     if cert[0] == _ffi.NULL:
         pycert = None
         friendlyname = None
     else:
-        pycert = X509.__new__(X509)
-        pycert._x509 = _ffi.gc(cert[0], _lib.X509_free)
+        pycert = X509._from_raw_x509_ptr(cert[0])
 
         friendlyname_length = _ffi.new("int*")
         friendlyname_buffer = _lib.X509_alias_get0(
             cert[0], friendlyname_length
         )
@@ -3071,12 +3070,12 @@ def load_pkcs12(buffer, passphrase=None):
         if friendlyname_buffer == _ffi.NULL:
             friendlyname = None
 
     pycacerts = []
     for i in range(_lib.sk_X509_num(cacerts)):
-        pycacert = X509.__new__(X509)
-        pycacert._x509 = _lib.sk_X509_value(cacerts, i)
+        x509 = _lib.sk_X509_value(cacerts, i)
+        pycacert = X509._from_raw_x509_ptr(x509)
         pycacerts.append(pycacert)
     if not pycacerts:
         pycacerts = None
 
     pkcs12 = PKCS12.__new__(PKCS12)
diff --git a/tests/test_ssl.py b/tests/test_ssl.py
index 03f9abd..76d8c4d 100644
--- a/tests/test_ssl.py
+++ b/tests/test_ssl.py
@@ -1277,10 +1277,35 @@ class TestContext(object):
 
         handshake_in_memory(clientConnection, serverConnection)
 
         assert verify.connection is clientConnection
 
+    def test_x509_in_verify_works(self):
+        """
+        We had a bug where the X509 cert instantiated in the callback wrapper
+        didn't __init__ so it was missing objects needed when calling
+        get_subject. This test sets up a handshake where we call get_subject
+        on the cert provided to the verify callback.
+        """
+        serverContext = Context(TLSv1_METHOD)
+        serverContext.use_privatekey(
+            load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM))
+        serverContext.use_certificate(
+            load_certificate(FILETYPE_PEM, cleartextCertificatePEM))
+        serverConnection = Connection(serverContext, None)
+
+        def verify_cb_get_subject(conn, cert, errnum, depth, ok):
+            assert cert.get_subject()
+            return 1
+
+        clientContext = Context(TLSv1_METHOD)
+        clientContext.set_verify(VERIFY_PEER, verify_cb_get_subject)
+        clientConnection = Connection(clientContext, None)
+        clientConnection.set_connect_state()
+
+        handshake_in_memory(clientConnection, serverConnection)
+
     def test_set_verify_callback_exception(self):
         """
         If the verify callback passed to `Context.set_verify` raises an
         exception, verification fails and the exception is propagated to the
         caller of `Connection.do_handshake`.
diff --git a/tox.ini b/tox.ini
index a16e690..bcb2f48 100644
--- a/tox.ini
+++ b/tox.ini
@@ -8,11 +8,11 @@ passenv = ARCHFLAGS CFLAGS LC_ALL LDFLAGS PATH LD_LIBRARY_PATH TERM
 extras =
     test
 deps =
     coverage>=4.2
     cryptographyMaster: git+https://github.com/pyca/cryptography.git
-    cryptographyMinimum: cryptography<=1.9
+    cryptographyMinimum: cryptography==2.1.4
 setenv =
     # Do not allow the executing environment to pollute the test environment
     # with extra packages.
     PYTHONPATH=
     PIP_NO_BINARY=cryptography

Vulnerable code: *none*

*none*
            "cryptography>=1.9",
            cert = X509.__new__(X509)
            cert._x509 = _lib.X509_STORE_CTX_get_current_cert(store_ctx)
        pycert = X509.__new__(X509)
        pycert._x509 = _ffi.gc(cert[0], _lib.X509_free)
        pycacert = X509.__new__(X509)
        pycacert._x509 = _lib.sk_X509_value(cacerts, i)
    cryptographyMinimum: cryptography<=1.9
Fixed code: * The minimum ``cryptography`` version is now 2.1.4.
- Fixed a potential use-after-free in the verify callback and resolved a memory leak when loading PKCS12 files with ``cacerts``.
  `#723 <https://github.com/pyca/pyopenssl/pull/723>`_
            "cryptography>=2.1.4",
            x509 = _lib.X509_STORE_CTX_get_current_cert(store_ctx)
            _lib.X509_up_ref(x509)
            cert = X509._from_raw_x509_ptr(x509)
        pycert = X509._from_raw_x509_ptr(cert[0])
        x509 = _lib.sk_X509_value(cacerts, i)
        pycacert = X509._from_raw_x509_ptr(x509)
    def test_x509_in_verify_works(self):
        """
        We had a bug where the X509 cert instantiated in the callback wrapper
        didn't __init__ so it was missing objects needed when calling
        get_subject. This test sets up a handshake where we call get_subject
        on the cert provided to the verify callback.
        """
        serverContext = Context(TLSv1_METHOD)
        serverContext.use_privatekey(
            load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM))
        serverContext.use_certificate(
            load_certificate(FILETYPE_PEM, cleartextCertificatePEM))
        serverConnection = Connection(serverContext, None)

        def verify_cb_get_subject(conn, cert, errnum, depth, ok):
            assert cert.get_subject()
            return 1

        clientContext = Context(TLSv1_METHOD)
        clientContext.set_verify(VERIFY_PEER, verify_cb_get_subject)
        clientConnection = Connection(clientContext, None)
        clientConnection.set_connect_state()

        handshake_in_memory(clientConnection, serverConnection)

    cryptographyMinimum: cryptography==2.1.4