crypto: fix crash when calling digest after piping
When piping data into an SHA3 hash, EVP_DigestFinal_ex is called in hash._flush, bypassing safeguards in the JavaScript layer. Calling hash.digest causes EVP_DigestFinal_ex to be called again, resulting in a segmentation fault in the SHA3 implementation of OpenSSL. A relatively easy solution is to cache the result of calling EVP_DigestFinal_ex until the Hash object is garbage collected. PR-URL: https://github.com/nodejs/node/pull/28251 Fixes: https://github.com/nodejs/node/issues/28245 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
This commit is contained in:
parent
8030ca5b9e
commit
fc50e6bcc8
@ -4634,16 +4634,20 @@ void Hash::HashDigest(const FunctionCallbackInfo<Value>& args) {
|
|||||||
encoding = ParseEncoding(env->isolate(), args[0], BUFFER);
|
encoding = ParseEncoding(env->isolate(), args[0], BUFFER);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned char md_value[EVP_MAX_MD_SIZE];
|
if (hash->md_len_ == 0) {
|
||||||
unsigned int md_len;
|
// Some hash algorithms such as SHA3 do not support calling
|
||||||
|
// EVP_DigestFinal_ex more than once, however, Hash._flush
|
||||||
EVP_DigestFinal_ex(hash->mdctx_.get(), md_value, &md_len);
|
// and Hash.digest can both be used to retrieve the digest,
|
||||||
|
// so we need to cache it.
|
||||||
|
// See https://github.com/nodejs/node/issues/28245.
|
||||||
|
EVP_DigestFinal_ex(hash->mdctx_.get(), hash->md_value_, &hash->md_len_);
|
||||||
|
}
|
||||||
|
|
||||||
Local<Value> error;
|
Local<Value> error;
|
||||||
MaybeLocal<Value> rc =
|
MaybeLocal<Value> rc =
|
||||||
StringBytes::Encode(env->isolate(),
|
StringBytes::Encode(env->isolate(),
|
||||||
reinterpret_cast<const char*>(md_value),
|
reinterpret_cast<const char*>(hash->md_value_),
|
||||||
md_len,
|
hash->md_len_,
|
||||||
encoding,
|
encoding,
|
||||||
&error);
|
&error);
|
||||||
if (rc.IsEmpty()) {
|
if (rc.IsEmpty()) {
|
||||||
|
@ -595,12 +595,19 @@ class Hash : public BaseObject {
|
|||||||
|
|
||||||
Hash(Environment* env, v8::Local<v8::Object> wrap)
|
Hash(Environment* env, v8::Local<v8::Object> wrap)
|
||||||
: BaseObject(env, wrap),
|
: BaseObject(env, wrap),
|
||||||
mdctx_(nullptr) {
|
mdctx_(nullptr),
|
||||||
|
md_len_(0) {
|
||||||
MakeWeak();
|
MakeWeak();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
~Hash() override {
|
||||||
|
OPENSSL_cleanse(md_value_, md_len_);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EVPMDPointer mdctx_;
|
EVPMDPointer mdctx_;
|
||||||
|
unsigned char md_value_[EVP_MAX_MD_SIZE];
|
||||||
|
unsigned int md_len_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SignBase : public BaseObject {
|
class SignBase : public BaseObject {
|
||||||
|
@ -30,11 +30,17 @@ const crypto = require('crypto');
|
|||||||
|
|
||||||
const stream = require('stream');
|
const stream = require('stream');
|
||||||
const s = new stream.PassThrough();
|
const s = new stream.PassThrough();
|
||||||
const h = crypto.createHash('sha1');
|
const h = crypto.createHash('sha3-512');
|
||||||
const expect = '15987e60950cf22655b9323bc1e281f9c4aff47e';
|
const expect = '36a38a2a35e698974d4e5791a3f05b05' +
|
||||||
|
'198235381e864f91a0e8cd6a26b677ec' +
|
||||||
|
'dcde8e2b069bd7355fabd68abd6fc801' +
|
||||||
|
'19659f25e92f8efc961ee3a7c815c758';
|
||||||
|
|
||||||
s.pipe(h).on('data', common.mustCall(function(c) {
|
s.pipe(h).on('data', common.mustCall(function(c) {
|
||||||
assert.strictEqual(c, expect);
|
assert.strictEqual(c, expect);
|
||||||
|
// Calling digest() after piping into a stream with SHA3 should not cause
|
||||||
|
// a segmentation fault, see https://github.com/nodejs/node/issues/28245.
|
||||||
|
assert.strictEqual(h.digest('hex'), expect);
|
||||||
})).setEncoding('hex');
|
})).setEncoding('hex');
|
||||||
|
|
||||||
s.end('aoeu');
|
s.end('aoeu');
|
||||||
|
Loading…
x
Reference in New Issue
Block a user