From 990feafcb60d2516cfeb36449759087517b8ff8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= Date: Sun, 16 Jun 2019 11:26:03 +0200 Subject: [PATCH] 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 Reviewed-By: Ben Noordhuis --- src/node_crypto.cc | 16 ++++++++++------ src/node_crypto.h | 9 ++++++++- test/parallel/test-crypto-hash-stream-pipe.js | 10 ++++++++-- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 813e1fc485c86c..590c6d1c374c08 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -4634,16 +4634,20 @@ void Hash::HashDigest(const FunctionCallbackInfo& 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 error; MaybeLocal rc = StringBytes::Encode(env->isolate(), - reinterpret_cast(md_value), - md_len, + reinterpret_cast(hash->md_value_), + hash->md_len_, encoding, &error); if (rc.IsEmpty()) { diff --git a/src/node_crypto.h b/src/node_crypto.h index aa29585533c62a..3e337eaddbe490 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -595,12 +595,19 @@ class Hash : public BaseObject { Hash(Environment* env, v8::Local wrap) : BaseObject(env, wrap), - mdctx_(nullptr) { + mdctx_(nullptr), + md_len_(0) { MakeWeak(); } + ~Hash() override { + OPENSSL_cleanse(md_value_, md_len_); + } + private: EVPMDPointer mdctx_; + unsigned char md_value_[EVP_MAX_MD_SIZE]; + unsigned int md_len_; }; class SignBase : public BaseObject { diff --git a/test/parallel/test-crypto-hash-stream-pipe.js b/test/parallel/test-crypto-hash-stream-pipe.js index 0a240a2abbc7ef..d22281abbd5c3c 100644 --- a/test/parallel/test-crypto-hash-stream-pipe.js +++ b/test/parallel/test-crypto-hash-stream-pipe.js @@ -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');