Description
Hello,
Thank you for this fine library.
I've been having some issues when encrypting large data (>2GiB) using the Fernet class. There seems to be several failure modes, I've seen everything from a segfault, SIGABRT to the decrypted plaintext differing from the original plaintext. Please note that I've had some issues with the RAM on my computer (so that could potentially be the source of some of the failures), but I've verified at least the SIGABRT failures on three different computers.
It seems to me that the issue seems to be an integer overflow in OpenSSL, but I'm not sure if Cryptography is at fault for passing an integer that is too large, or OpenSSL is at fault for not checking the integer, or a combination. If you think this should be fixed in OpenSSL, please let me know so I can report the issue to them.
Please see the attached script for more detailed information, as I think it speaks for itself.
- Software versions:
- Python 3.8.2/3.9.0
- OpenSSL 1.1.1h
- cryptography: 3.3
- cffi: 1.14.4
- pip: 20.3.1
- setuptools: 51.0.0
- Installed cryptography through pip
To reproduce:
import cryptography.fernet as cf
# OK
data_size = 2**31 - 1
# Fails with SIGABRT and stderr: "free(): invalid size" or "munmap_chunk(): invalid pointer"
#
# OK if _CipherContext._MAX_CHUNK_SIZE is set to 2 ** 31 - 1 - 8
data_size = 2**32 - 1
# Fails with SIGABRT or SIGSEGV and stderr: "free(): invalid size" or "munmap_chunk(): invalid pointer" or
# cryptography.exceptions.InternalError: Unknown OpenSSL error.
# This error is commonly encountered when another library is not cleaning up the OpenSSL error stack.
# If you are using cryptography with another library that uses OpenSSL try disabling it before reporting a bug.
# Otherwise please file an issue at https://github.com/pyca/cryptography/issues with information on how to reproduce this.
# ([_OpenSSLErrorWithText(code=101560482, lib=6, func=219, reason=162, reason_text=b'error:060DB0A2:digital envelope routines:evp_EncryptDecryptUpdate:partially overlapping buffers')])
#
# Fails due to plaintext != original_plaintext if _CipherContext._MAX_CHUNK_SIZE is set to 2 ** 31 - 1 - 8
#
# OK if _CipherContext._MAX_CHUNK_SIZE is set to 2 ** 31 - 1 - 16
data_size = 2**33 - 1
original_plaintext = bytes(data_size)
fernet = cf.Fernet(cf.Fernet.generate_key())
ciphertext = fernet.encrypt(original_plaintext)
plaintext = fernet.decrypt(ciphertext)
assert plaintext == original_plaintext