Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upcrypto: handle OpenSSL error queue in CipherBase #21288
Conversation
This comment has been minimized.
This comment has been minimized.
nodejs-github-bot
commented
Jun 12, 2018
|
@tniessen sadly an error occured when I tried to trigger a build :( |
nodejs-github-bot
added
C++
crypto
labels
Jun 12, 2018
tniessen
referenced this pull request
Jun 12, 2018
Closed
Calling crypto.createCipheriv with a key of invalid length can cause errors in other consumers of the OpenSSL error queue #21281
tniessen
requested review from
bnoordhuis and
ryzokuken
Jun 12, 2018
This comment has been minimized.
This comment has been minimized.
addaleax
reviewed
Jun 14, 2018
| @@ -2749,6 +2753,7 @@ static bool IsValidGCMTagLength(unsigned int tag_len) { | |||
| bool CipherBase::InitAuthenticated(const char* cipher_type, int iv_len, | |||
| unsigned int auth_tag_len) { | |||
| CHECK(IsAuthenticatedMode()); | |||
| MarkPopErrorOnReturn mark_pop_error_on_return; | |||
This comment has been minimized.
This comment has been minimized.
addaleax
Jun 14, 2018
Member
For my own understanding: MarkPopErrorOnReturn basically means that OpenSSL errors inside this scope are swallowed/ignored?
This comment has been minimized.
This comment has been minimized.
tniessen
Jun 14, 2018
•
Author
Member
Exactly, all errors that occur in this scope are dropped once the destructor is called (unless they have been handled specifically).
This comment has been minimized.
This comment has been minimized.
ryzokuken
Jul 1, 2018
Member
And now that we're handling the errors manually, Node will be throwing an error instead of just crashing, I suppose?
This comment has been minimized.
This comment has been minimized.
ryzokuken
Jul 1, 2018
Member
And because we're manually handling the errors now, it's better because we'll throw an error that you could catch and what not instead of just crashing, right?
This comment has been minimized.
This comment has been minimized.
tniessen
Jul 1, 2018
Author
Member
It won't throw more errors than before (except for ::Init), it will just make sure that if it throws, it also clears the internal error queue of OpenSSL.
This comment has been minimized.
This comment has been minimized.
|
Travis CI failure looks related:
|
This comment has been minimized.
This comment has been minimized.
|
@addaleax Yes, see above:
As far as I can tell, #15037 should have set the flag before calling |
bnoordhuis
reviewed
Jun 14, 2018
|
|
||
| ctx_.reset(EVP_CIPHER_CTX_new()); | ||
| const bool encrypt = (kind_ == kCipher); | ||
| EVP_CipherInit_ex(ctx_.get(), cipher, nullptr, nullptr, nullptr, encrypt); | ||
| CHECK(EVP_CipherInit_ex(ctx_.get(), cipher, nullptr, |
This comment has been minimized.
This comment has been minimized.
bnoordhuis
Jun 14, 2018
Member
EVP_CipherInit_ex() can fail for legitimate reasons (e.g. an engine that fails to load) so a CHECK is not appropriate.
This comment has been minimized.
This comment has been minimized.
tniessen
Jun 14, 2018
Author
Member
I assume the old behavior (= no error checking) isn't appropriate either?
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
tniessen
Jun 14, 2018
Author
Member
That would make this semver-major I guess, so I'll probably just leave the CHECKs out for now.
This comment has been minimized.
This comment has been minimized.
bnoordhuis
Jun 14, 2018
Member
Mwah, I don't think better error checking constitutes semver-major. The kind of bugs it'd flush out are programmer bugs and environmental issues.
| @@ -2893,6 +2898,7 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) { | |||
| bool CipherBase::SetAAD(const char* data, unsigned int len, int plaintext_len) { | |||
| if (!ctx_ || !IsAuthenticatedMode()) | |||
| return false; | |||
| ClearErrorOnReturn clear_error_on_return; | |||
This comment has been minimized.
This comment has been minimized.
bnoordhuis
Jun 14, 2018
Member
This should ideally be MarkPopErrorOnReturn. ClearErrorOnReturn is a blunt hammer and might swallow genuine errors.
This comment has been minimized.
This comment has been minimized.
tniessen
Jun 14, 2018
Author
Member
Point taken, I can change it. In practice, the error stack should be empty when the JS layer calls into the C++ layer though, right?
This comment has been minimized.
This comment has been minimized.
bnoordhuis
Jun 14, 2018
•
Member
Yes, except for the following:
- It can mess up C++ -> C++ calls when the caller also manipulates the error stack.
- I know of at least one add-on that leaves openssl errors on the stack for some time.
tniessen
added some commits
Jun 14, 2018
This comment has been minimized.
This comment has been minimized.
|
@bnoordhuis Please take another look. |
ryzokuken
reviewed
Jul 1, 2018
|
Unrelated, but |
|
|
||
| ctx_.reset(EVP_CIPHER_CTX_new()); | ||
| const bool encrypt = (kind_ == kCipher); | ||
| EVP_CipherInit_ex(ctx_.get(), cipher, nullptr, nullptr, nullptr, encrypt); | ||
| if (1 != EVP_CipherInit_ex(ctx_.get(), cipher, nullptr, |
This comment has been minimized.
This comment has been minimized.
ryzokuken
Jul 1, 2018
Member
Style nit, but shouldn't this be done the other way around? Eg: EVP_CipherInit_ex(...) != 1? Would love to hear why you chose this over the former.
This comment has been minimized.
This comment has been minimized.
tniessen
Jul 1, 2018
Author
Member
Didn't know we had a preference. I usually do the opposite, but I think having != 1 at the end of a multi-line expression looks weird.
Fun fact, this style was commonly used to prevent accidental assignment of variables / pointers in boolean expressions.
This comment has been minimized.
This comment has been minimized.
ryzokuken
Jul 1, 2018
Member
Because people would accidentally type = instead of == all the times? Sounds more likely in the era of butterfly keyboards, heh. If it makes sense for you to keep this, feel free to do so. I agree wholeheartedly that != 1 looks weird when in a multiline statement, just wanted to make sure we're not going against the style guide here.
This comment has been minimized.
This comment has been minimized.
tniessen
Jul 1, 2018
Author
Member
Yes, even though I personally still prefer var == constant, the only reason here is that it looks weird. If no one has a strong opinion against this, I'd prefer to keep it this way.
| reinterpret_cast<unsigned char*>(key), | ||
| reinterpret_cast<unsigned char*>(iv), | ||
| encrypt); | ||
| if (1 != EVP_CipherInit_ex(ctx_.get(), |
This comment has been minimized.
This comment has been minimized.
| @@ -2686,7 +2696,11 @@ void CipherBase::InitIv(const char* cipher_type, | |||
| EVP_CIPHER_CTX_set_flags(ctx_.get(), EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); | |||
|
|
|||
| const bool encrypt = (kind_ == kCipher); | |||
| EVP_CipherInit_ex(ctx_.get(), cipher, nullptr, nullptr, nullptr, encrypt); | |||
| if (1 != EVP_CipherInit_ex(ctx_.get(), cipher, nullptr, | |||
This comment has been minimized.
This comment has been minimized.
| reinterpret_cast<const unsigned char*>(key), | ||
| reinterpret_cast<const unsigned char*>(iv), | ||
| encrypt); | ||
| if (1 != EVP_CipherInit_ex(ctx_.get(), |
This comment has been minimized.
This comment has been minimized.
| @@ -2749,6 +2753,7 @@ static bool IsValidGCMTagLength(unsigned int tag_len) { | |||
| bool CipherBase::InitAuthenticated(const char* cipher_type, int iv_len, | |||
| unsigned int auth_tag_len) { | |||
| CHECK(IsAuthenticatedMode()); | |||
| MarkPopErrorOnReturn mark_pop_error_on_return; | |||
This comment has been minimized.
This comment has been minimized.
ryzokuken
Jul 1, 2018
Member
And now that we're handling the errors manually, Node will be throwing an error instead of just crashing, I suppose?
| reinterpret_cast<unsigned char*>(key), | ||
| reinterpret_cast<unsigned char*>(iv), | ||
| encrypt); | ||
| if (1 != EVP_CipherInit_ex(ctx_.get(), |
This comment has been minimized.
This comment has been minimized.
| @@ -2686,7 +2696,11 @@ void CipherBase::InitIv(const char* cipher_type, | |||
| EVP_CIPHER_CTX_set_flags(ctx_.get(), EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); | |||
|
|
|||
| const bool encrypt = (kind_ == kCipher); | |||
| EVP_CipherInit_ex(ctx_.get(), cipher, nullptr, nullptr, nullptr, encrypt); | |||
| if (1 != EVP_CipherInit_ex(ctx_.get(), cipher, nullptr, | |||
This comment has been minimized.
This comment has been minimized.
| reinterpret_cast<const unsigned char*>(key), | ||
| reinterpret_cast<const unsigned char*>(iv), | ||
| encrypt); | ||
| if (1 != EVP_CipherInit_ex(ctx_.get(), |
This comment has been minimized.
This comment has been minimized.
| @@ -2749,6 +2753,7 @@ static bool IsValidGCMTagLength(unsigned int tag_len) { | |||
| bool CipherBase::InitAuthenticated(const char* cipher_type, int iv_len, | |||
| unsigned int auth_tag_len) { | |||
| CHECK(IsAuthenticatedMode()); | |||
| MarkPopErrorOnReturn mark_pop_error_on_return; | |||
This comment has been minimized.
This comment has been minimized.
ryzokuken
Jul 1, 2018
Member
And because we're manually handling the errors now, it's better because we'll throw an error that you could catch and what not instead of just crashing, right?
| EVP_CTRL_GCM_SET_TAG, | ||
| auth_tag_len_, | ||
| reinterpret_cast<unsigned char*>(auth_tag_)); | ||
| CHECK(EVP_CIPHER_CTX_ctrl(ctx_.get(), |
This comment has been minimized.
This comment has been minimized.
ryzokuken
Jul 1, 2018
Member
I'm a bit intrigued.
https://www.openssl.org/docs/man1.0.2/crypto/EVP_EncryptInit.html doesn't show EVP_CIPHER_CTX_ctrl returning anything. If it's actually returning void (I hope not), the would this work?
If it doesn't return void, shouldn't we use an if statement here as well and throw something nice instead of crashing the process?
This comment has been minimized.
This comment has been minimized.
tniessen
Jul 1, 2018
Author
Member
The only reasons I can think of right now are a bug (e.g. because we permitted the call in an invalid state) or a memory allocation failure, and those are generally not handled within our APIs.
tniessen
referenced this pull request
Jul 2, 2018
Closed
Proposal: Splitting up node_crypto.cc #16524
This comment has been minimized.
This comment has been minimized.
|
CI: https://ci.nodejs.org/job/node-test-pull-request/15753/ Pinging @nodejs/crypto one last time. |
This comment has been minimized.
This comment has been minimized.
|
Landed in 85b0f16. |
tniessen commentedJun 12, 2018
This handles all errors produced by OpenSSL within the
CipherBaseclass. API functions clear the error queue on return, utility functions such asInitAuthenticated()ensure that they do not add any new errors to the queue. Previously ignored return values are now beingCHECK'd.::Finaldoes not clear the error queue as there is a custom error handler in place.Note that #21287 is required for this to work, otherwise the
CHECKin line 2597 will fail.Fixes: #21281
Checklist
make -j4 test(UNIX), orvcbuild test(Windows) passes