Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
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.
  • Loading branch information
tniessen committed Jun 16, 2019
commit b6d2d7923e11292722bb504c89e736d35e068494
16 changes: 10 additions & 6 deletions src/node_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4634,16 +4634,20 @@ void Hash::HashDigest(const FunctionCallbackInfo<Value>& args) {
encoding = ParseEncoding(env->isolate(), args[0], BUFFER);
}

unsigned char md_value[EVP_MAX_MD_SIZE];
unsigned int md_len;

EVP_DigestFinal_ex(hash->mdctx_.get(), md_value, &md_len);
if (hash->md_len_ == 0) {
// Some hash algorithms such as SHA3 do not support calling
// EVP_DigestFinal_ex more than once, however, Hash._flush
// 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;
MaybeLocal<Value> rc =
StringBytes::Encode(env->isolate(),
reinterpret_cast<const char*>(md_value),
md_len,
reinterpret_cast<const char*>(hash->md_value_),
hash->md_len_,
encoding,
&error);
if (rc.IsEmpty()) {
Expand Down
5 changes: 4 additions & 1 deletion src/node_crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -595,12 +595,15 @@ class Hash : public BaseObject {

Hash(Environment* env, v8::Local<v8::Object> wrap)
: BaseObject(env, wrap),
mdctx_(nullptr) {
mdctx_(nullptr),
md_len_(0) {
MakeWeak();
}

private:
EVPMDPointer mdctx_;
unsigned char md_value_[EVP_MAX_MD_SIZE];
Comment thread
tniessen marked this conversation as resolved.
unsigned int md_len_;
};

class SignBase : public BaseObject {
Expand Down
10 changes: 8 additions & 2 deletions test/parallel/test-crypto-hash-stream-pipe.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,17 @@ const crypto = require('crypto');

const stream = require('stream');
const s = new stream.PassThrough();
const h = crypto.createHash('sha1');
const expect = '15987e60950cf22655b9323bc1e281f9c4aff47e';
const h = crypto.createHash('sha3-512');
const expect = '36a38a2a35e698974d4e5791a3f05b05' +
'198235381e864f91a0e8cd6a26b677ec' +
'dcde8e2b069bd7355fabd68abd6fc801' +
'19659f25e92f8efc961ee3a7c815c758';

s.pipe(h).on('data', common.mustCall(function(c) {
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');

s.end('aoeu');