-
-
Notifications
You must be signed in to change notification settings - Fork 33.2k
safely handle openssl realloc failures during resize #59198
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Review requested:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should not land as-is but it did highlight a real (albeit hopefully academic) bug in the code.
@@ -212,10 +212,25 @@ Buffer<void> DataPointer::release() { | |||
DataPointer DataPointer::resize(size_t len) { | |||
size_t actual_len = std::min(len_, len); | |||
auto buf = release(); | |||
if (actual_len == len_) return DataPointer(buf.data, actual_len); | |||
buf.data = OPENSSL_realloc(buf.data, actual_len); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This reallocation cannot realistically fail because of line 213: actual_len is min(len, len_), i.e., it always shrinks, never grows the buffer.
I'd have suggested adding a CHECK_NOT_NULL(buf.data)
sanity check but because this is code that's been moved from src/ to deps/ncrypto, you can't use that macro 🤦
deps/ncrypto/ncrypto.cc
Outdated
return DataPointer(buf); | ||
if (actual_len == len_) { | ||
// Retain the secure_ heap flag | ||
return DataPointer(buf.data, actual_len, secure_); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO this secure_
flag is problematic with resizes because secure memory comes from a different heap and should not be mixed with OPENSSL_realloc.
I believe it's a theoretical problem right now because secure buffers are never resized but it should either be a runtime error or a fatal error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you so much for the review. I agree that the heaps should not be mixed. However, the current implementation isn't explicit enough about secure_
leading to uncertainty and theoretical bugs (Avoiding some specific wordings). I have tried to improve the method to make things a bit explicit (false and explicit check).
Question: I might have overlooked compatibility with BoringSSL. I noticed that OPENSSL_secure_clear_free is really a wrapper for OPENSSL_clear_free, so the previous patch might have worked too. I would appreciate your help with testing this across crypto implementations as well, since my mac is not really setup for node development and testing.
DataPointer DataPointer::resize(size_t len) {
NCRYPTO_ASSERT_TRUE(!secure_);
size_t actual_len = std::min(len_, len);
auto buf = release();
// Handle the unexpected null pointer
if (buf.data == nullptr) {
return DataPointer(nullptr, 0, false);
}
// This scenario is unlikely
if (actual_len == len_) {
return DataPointer(buf.data, actual_len, false);
}
// This should correctly handle secure memory based on input pointer
void* new_data = OPENSSL_realloc(buf.data, actual_len);
// Handle reallocation failure. NULL will be returned on error.
// https://www.manpagez.com/man/3/OPENSSL_realloc/
if (new_data == nullptr && actual_len > 0) {
// OPENSSL_realloc doesn't free automatically. Below is a code snippet showing correct use
// https://github.com/openssl/openssl/blob/e7d5398aa1349cc575a5b80e0d6eb28e61cb4bfa/apps/engine.c#L72
OPENSSL_clear_free(buf.data, len_);
return DataPointer(nullptr, 0, false);
}
return DataPointer(new_data, actual_len, false);
}
Kindly let me know if I can update and push my branch with this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps the simplest thing to do is to add this as the first line of resize:
if (secure_) return DataPointer();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bnoordhuis updated and pushed the branch based on this review. Kindly review the new commit. I personally prefer the assert NCRYPTO_ASSERT_TRUE(!secure_);
over silently failing with return DataPointer()
. NCRYPTO_ASSERT_TRUE
is used in other places in the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bnoordhuis any thoughts?
Signed-off-by: Prabhu Subramanian <[email protected]>
Signed-off-by: Prabhu Subramanian <[email protected]>
Signed-off-by: Prabhu Subramanian <[email protected]>
f9de31b
to
6da607d
Compare
NOTE: qwen3-coder model was used to develop this patch.
While troubleshooting a recent out-of-memory issue, I arrived at the ncrypto resize logic. I realised that the method looked too simplistic, so I began looking for the correct and safe use of
OPENSSL_realloc
api.qwen3 was used to confirm the suspicion and generate this patch. I am not familiar with writing unit tests or testing this flow, so some guidance will be much appreciated.
Testing: