Bug Summary

File:out/../src/crypto/crypto_hash.cc
Warning:line 101, column 12
Potential leak of memory pointed to by 'hash'

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-unknown-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name crypto_hash.cc -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -pic-is-pie -mframe-pointer=all -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fcoverage-compilation-dir=/home/maurizio/node-v18.6.0/out -resource-dir /usr/local/lib/clang/16.0.0 -D V8_DEPRECATION_WARNINGS -D V8_IMMINENT_DEPRECATION_WARNINGS -D _GLIBCXX_USE_CXX11_ABI=1 -D NODE_OPENSSL_CONF_NAME=nodejs_conf -D NODE_OPENSSL_HAS_QUIC -D __STDC_FORMAT_MACROS -D OPENSSL_NO_PINSHARED -D OPENSSL_THREADS -D NODE_ARCH="x64" -D NODE_PLATFORM="linux" -D NODE_WANT_INTERNALS=1 -D V8_DEPRECATION_WARNINGS=1 -D NODE_OPENSSL_SYSTEM_CERT_PATH="" -D NODE_USE_NODE_CODE_CACHE=1 -D HAVE_INSPECTOR=1 -D NODE_ENABLE_LARGE_CODE_PAGES=1 -D __POSIX__ -D NODE_USE_V8_PLATFORM=1 -D NODE_HAVE_I18N_SUPPORT=1 -D HAVE_OPENSSL=1 -D OPENSSL_API_COMPAT=0x10100000L -D UCONFIG_NO_SERVICE=1 -D U_ENABLE_DYLOAD=0 -D U_STATIC_IMPLEMENTATION=1 -D U_HAVE_STD_STRING=1 -D UCONFIG_NO_BREAK_ITERATION=0 -D _LARGEFILE_SOURCE -D _FILE_OFFSET_BITS=64 -D _POSIX_C_SOURCE=200112 -D NGHTTP2_STATICLIB -D NDEBUG -D OPENSSL_USE_NODELETE -D L_ENDIAN -D OPENSSL_BUILDING_OPENSSL -D AES_ASM -D BSAES_ASM -D CMLL_ASM -D ECP_NISTZ256_ASM -D GHASH_ASM -D KECCAK1600_ASM -D MD5_ASM -D OPENSSL_BN_ASM_GF2m -D OPENSSL_BN_ASM_MONT -D OPENSSL_BN_ASM_MONT5 -D OPENSSL_CPUID_OBJ -D OPENSSL_IA32_SSE2 -D PADLOCK_ASM -D POLY1305_ASM -D SHA1_ASM -D SHA256_ASM -D SHA512_ASM -D VPAES_ASM -D WHIRLPOOL_ASM -D X25519_ASM -D OPENSSL_PIC -D NGTCP2_STATICLIB -D NGHTTP3_STATICLIB -I ../src -I /home/maurizio/node-v18.6.0/out/Release/obj/gen -I /home/maurizio/node-v18.6.0/out/Release/obj/gen/include -I /home/maurizio/node-v18.6.0/out/Release/obj/gen/src -I ../deps/googletest/include -I ../deps/histogram/src -I ../deps/uvwasi/include -I ../deps/v8/include -I ../deps/icu-small/source/i18n -I ../deps/icu-small/source/common -I ../deps/zlib -I ../deps/llhttp/include -I ../deps/cares/include -I ../deps/uv/include -I ../deps/nghttp2/lib/includes -I ../deps/brotli/c/include -I ../deps/openssl/openssl/include -I ../deps/openssl/openssl/crypto/include -I ../deps/openssl/config/archs/linux-x86_64/asm/include -I ../deps/openssl/config/archs/linux-x86_64/asm -I ../deps/ngtcp2 -I ../deps/ngtcp2/ngtcp2/lib/includes -I ../deps/ngtcp2/ngtcp2/crypto/includes -I ../deps/ngtcp2/nghttp3/lib/includes -internal-isystem /usr/lib/gcc/x86_64-redhat-linux/8/../../../../include/c++/8 -internal-isystem /usr/lib/gcc/x86_64-redhat-linux/8/../../../../include/c++/8/x86_64-redhat-linux -internal-isystem /usr/lib/gcc/x86_64-redhat-linux/8/../../../../include/c++/8/backward -internal-isystem /usr/local/lib/clang/16.0.0/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-redhat-linux/8/../../../../x86_64-redhat-linux/include -internal-externc-isystem /include -internal-externc-isystem /usr/include -O3 -Wno-unused-parameter -Wno-unused-parameter -std=gnu++17 -fdeprecated-macro -fdebug-compilation-dir=/home/maurizio/node-v18.6.0/out -ferror-limit 19 -fno-rtti -fgnuc-version=4.2.1 -vectorize-loops -vectorize-slp -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /tmp/scan-build-2022-08-22-142216-507842-1 -x c++ ../src/crypto/crypto_hash.cc
1#include "crypto/crypto_hash.h"
2#include "async_wrap-inl.h"
3#include "base_object-inl.h"
4#include "env-inl.h"
5#include "memory_tracker-inl.h"
6#include "string_bytes.h"
7#include "threadpoolwork-inl.h"
8#include "v8.h"
9
10#include <cstdio>
11
12namespace node {
13
14using v8::FunctionCallbackInfo;
15using v8::FunctionTemplate;
16using v8::Just;
17using v8::Local;
18using v8::Maybe;
19using v8::MaybeLocal;
20using v8::Nothing;
21using v8::Object;
22using v8::Uint32;
23using v8::Value;
24
25namespace crypto {
26Hash::Hash(Environment* env, Local<Object> wrap) : BaseObject(env, wrap) {
27 MakeWeak();
28}
29
30void Hash::MemoryInfo(MemoryTracker* tracker) const {
31 tracker->TrackFieldWithSize("mdctx", mdctx_ ? kSizeOf_EVP_MD_CTX : 0);
32 tracker->TrackFieldWithSize("md", digest_ ? md_len_ : 0);
33}
34
35void Hash::GetHashes(const FunctionCallbackInfo<Value>& args) {
36 Environment* env = Environment::GetCurrent(args);
37 MarkPopErrorOnReturn mark_pop_error_on_return;
38 CipherPushContext ctx(env);
39 EVP_MD_do_all_sorted(
40#if OPENSSL_VERSION_MAJOR3 >= 3
41 array_push_back<EVP_MD,
42 EVP_MD_fetch,
43 EVP_MD_free,
44 EVP_get_digestbyname,
45 EVP_MD_get0_name>,
46#else
47 array_push_back<EVP_MD>,
48#endif
49 &ctx);
50 args.GetReturnValue().Set(ctx.ToJSArray());
51}
52
53void Hash::Initialize(Environment* env, Local<Object> target) {
54 Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
55
56 t->InstanceTemplate()->SetInternalFieldCount(
57 Hash::kInternalFieldCount);
58 t->Inherit(BaseObject::GetConstructorTemplate(env));
59
60 env->SetProtoMethod(t, "update", HashUpdate);
61 env->SetProtoMethod(t, "digest", HashDigest);
62
63 env->SetConstructorFunction(target, "Hash", t);
64
65 env->SetMethodNoSideEffect(target, "getHashes", GetHashes);
66
67 HashJob::Initialize(env, target);
68}
69
70void Hash::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
71 registry->Register(New);
72 registry->Register(HashUpdate);
73 registry->Register(HashDigest);
74 registry->Register(GetHashes);
75
76 HashJob::RegisterExternalReferences(registry);
77}
78
79void Hash::New(const FunctionCallbackInfo<Value>& args) {
80 Environment* env = Environment::GetCurrent(args);
81
82 const Hash* orig = nullptr;
83 const EVP_MD* md = nullptr;
84
85 if (args[0]->IsObject()) {
1
Assuming the condition is true
2
Taking true branch
86 ASSIGN_OR_RETURN_UNWRAP(&orig, args[0].As<Object>())do { *&orig = static_cast<typename std::remove_reference
<decltype(*&orig)>::type>( BaseObject::FromJSObject
(args[0].As<Object>())); if (*&orig == nullptr) return
; } while (0)
;
3
Assuming the condition is false
4
Taking false branch
5
Loop condition is false. Exiting loop
87 md = EVP_MD_CTX_md(orig->mdctx_.get());
88 } else {
89 const Utf8Value hash_type(env->isolate(), args[0]);
90 md = EVP_get_digestbyname(*hash_type);
91 }
92
93 Maybe<unsigned int> xof_md_len = Nothing<unsigned int>();
94 if (!args[1]->IsUndefined()) {
6
Taking true branch
95 CHECK(args[1]->IsUint32())do { if (__builtin_expect(!!(!(args[1]->IsUint32())), 0)) {
do { static const node::AssertionInfo args = { "../src/crypto/crypto_hash.cc"
":" "95", "args[1]->IsUint32()", __PRETTY_FUNCTION__ }; node
::Assert(args); } while (0); } } while (0)
;
7
Assuming the condition is false
8
Taking false branch
9
Loop condition is false. Exiting loop
96 xof_md_len = Just<unsigned int>(args[1].As<Uint32>()->Value());
97 }
98
99 Hash* hash = new Hash(env, args.This());
10
Memory is allocated
100 if (md == nullptr || !hash->HashInit(md, xof_md_len)) {
11
Assuming the condition is true
101 return ThrowCryptoError(env, ERR_get_error(),
12
Potential leak of memory pointed to by 'hash'
102 "Digest method not supported");
103 }
104
105 if (orig != nullptr &&
106 0 >= EVP_MD_CTX_copy(hash->mdctx_.get(), orig->mdctx_.get())) {
107 return ThrowCryptoError(env, ERR_get_error(), "Digest copy error");
108 }
109}
110
111bool Hash::HashInit(const EVP_MD* md, Maybe<unsigned int> xof_md_len) {
112 mdctx_.reset(EVP_MD_CTX_new());
113 if (!mdctx_ || EVP_DigestInit_ex(mdctx_.get(), md, nullptr) <= 0) {
114 mdctx_.reset();
115 return false;
116 }
117
118 md_len_ = EVP_MD_sizeEVP_MD_get_size(md);
119 if (xof_md_len.IsJust() && xof_md_len.FromJust() != md_len_) {
120 // This is a little hack to cause createHash to fail when an incorrect
121 // hashSize option was passed for a non-XOF hash function.
122 if ((EVP_MD_flagsEVP_MD_get_flags(md) & EVP_MD_FLAG_XOF0x0002) == 0) {
123 EVPerr(EVP_F_EVP_DIGESTFINALXOF, EVP_R_NOT_XOF_OR_INVALID_LENGTH)(ERR_new(), ERR_set_debug("../src/crypto/crypto_hash.cc",123,
"(unknown function)"), ERR_set_error)(6, (178), __null)
;
124 return false;
125 }
126 md_len_ = xof_md_len.FromJust();
127 }
128
129 return true;
130}
131
132bool Hash::HashUpdate(const char* data, size_t len) {
133 if (!mdctx_)
134 return false;
135 return EVP_DigestUpdate(mdctx_.get(), data, len) == 1;
136}
137
138void Hash::HashUpdate(const FunctionCallbackInfo<Value>& args) {
139 Decode<Hash>(args, [](Hash* hash, const FunctionCallbackInfo<Value>& args,
140 const char* data, size_t size) {
141 Environment* env = Environment::GetCurrent(args);
142 if (UNLIKELY(size > INT_MAX)__builtin_expect(!!(size > 2147483647), 0))
143 return THROW_ERR_OUT_OF_RANGE(env, "data is too long");
144 bool r = hash->HashUpdate(data, size);
145 args.GetReturnValue().Set(r);
146 });
147}
148
149void Hash::HashDigest(const FunctionCallbackInfo<Value>& args) {
150 Environment* env = Environment::GetCurrent(args);
151
152 Hash* hash;
153 ASSIGN_OR_RETURN_UNWRAP(&hash, args.Holder())do { *&hash = static_cast<typename std::remove_reference
<decltype(*&hash)>::type>( BaseObject::FromJSObject
(args.Holder())); if (*&hash == nullptr) return ; } while
(0)
;
154
155 enum encoding encoding = BUFFER;
156 if (args.Length() >= 1) {
157 encoding = ParseEncoding(env->isolate(), args[0], BUFFER);
158 }
159
160 unsigned int len = hash->md_len_;
161
162 // TODO(tniessen): SHA3_squeeze does not work for zero-length outputs on all
163 // platforms and will cause a segmentation fault if called. This workaround
164 // causes hash.digest() to correctly return an empty buffer / string.
165 // See https://github.com/openssl/openssl/issues/9431.
166
167 if (!hash->digest_ && len > 0) {
168 // Some hash algorithms such as SHA3 do not support calling
169 // EVP_DigestFinal_ex more than once, however, Hash._flush
170 // and Hash.digest can both be used to retrieve the digest,
171 // so we need to cache it.
172 // See https://github.com/nodejs/node/issues/28245.
173
174 ByteSource::Builder digest(len);
175
176 size_t default_len = EVP_MD_CTX_size(hash->mdctx_.get())EVP_MD_get_size(EVP_MD_CTX_get0_md(hash->mdctx_.get()));
177 int ret;
178 if (len == default_len) {
179 ret = EVP_DigestFinal_ex(
180 hash->mdctx_.get(), digest.data<unsigned char>(), &len);
181 // The output length should always equal hash->md_len_
182 CHECK_EQ(len, hash->md_len_)do { if (__builtin_expect(!!(!((len) == (hash->md_len_))),
0)) { do { static const node::AssertionInfo args = { "../src/crypto/crypto_hash.cc"
":" "182", "(len) == (hash->md_len_)", __PRETTY_FUNCTION__
}; node::Assert(args); } while (0); } } while (0)
;
183 } else {
184 ret = EVP_DigestFinalXOF(
185 hash->mdctx_.get(), digest.data<unsigned char>(), len);
186 }
187
188 if (ret != 1)
189 return ThrowCryptoError(env, ERR_get_error());
190
191 hash->digest_ = std::move(digest).release();
192 }
193
194 Local<Value> error;
195 MaybeLocal<Value> rc = StringBytes::Encode(
196 env->isolate(), hash->digest_.data<char>(), len, encoding, &error);
197 if (rc.IsEmpty()) {
198 CHECK(!error.IsEmpty())do { if (__builtin_expect(!!(!(!error.IsEmpty())), 0)) { do {
static const node::AssertionInfo args = { "../src/crypto/crypto_hash.cc"
":" "198", "!error.IsEmpty()", __PRETTY_FUNCTION__ }; node::
Assert(args); } while (0); } } while (0)
;
199 env->isolate()->ThrowException(error);
200 return;
201 }
202 args.GetReturnValue().Set(rc.FromMaybe(Local<Value>()));
203}
204
205HashConfig::HashConfig(HashConfig&& other) noexcept
206 : mode(other.mode),
207 in(std::move(other.in)),
208 digest(other.digest),
209 length(other.length) {}
210
211HashConfig& HashConfig::operator=(HashConfig&& other) noexcept {
212 if (&other == this) return *this;
213 this->~HashConfig();
214 return *new (this) HashConfig(std::move(other));
215}
216
217void HashConfig::MemoryInfo(MemoryTracker* tracker) const {
218 // If the Job is sync, then the HashConfig does not own the data.
219 if (mode == kCryptoJobAsync)
220 tracker->TrackFieldWithSize("in", in.size());
221}
222
223Maybe<bool> HashTraits::EncodeOutput(
224 Environment* env,
225 const HashConfig& params,
226 ByteSource* out,
227 v8::Local<v8::Value>* result) {
228 *result = out->ToArrayBuffer(env);
229 return Just(!result->IsEmpty());
230}
231
232Maybe<bool> HashTraits::AdditionalConfig(
233 CryptoJobMode mode,
234 const FunctionCallbackInfo<Value>& args,
235 unsigned int offset,
236 HashConfig* params) {
237 Environment* env = Environment::GetCurrent(args);
238
239 params->mode = mode;
240
241 CHECK(args[offset]->IsString())do { if (__builtin_expect(!!(!(args[offset]->IsString())),
0)) { do { static const node::AssertionInfo args = { "../src/crypto/crypto_hash.cc"
":" "241", "args[offset]->IsString()", __PRETTY_FUNCTION__
}; node::Assert(args); } while (0); } } while (0)
; // Hash algorithm
242 Utf8Value digest(env->isolate(), args[offset]);
243 params->digest = EVP_get_digestbyname(*digest);
244 if (UNLIKELY(params->digest == nullptr)__builtin_expect(!!(params->digest == nullptr), 0)) {
245 THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Invalid digest: %s", *digest);
246 return Nothing<bool>();
247 }
248
249 ArrayBufferOrViewContents<char> data(args[offset + 1]);
250 if (UNLIKELY(!data.CheckSizeInt32())__builtin_expect(!!(!data.CheckSizeInt32()), 0)) {
251 THROW_ERR_OUT_OF_RANGE(env, "data is too big");
252 return Nothing<bool>();
253 }
254 params->in = mode == kCryptoJobAsync
255 ? data.ToCopy()
256 : data.ToByteSource();
257
258 unsigned int expected = EVP_MD_sizeEVP_MD_get_size(params->digest);
259 params->length = expected;
260 if (UNLIKELY(args[offset + 2]->IsUint32())__builtin_expect(!!(args[offset + 2]->IsUint32()), 0)) {
261 // length is expressed in terms of bits
262 params->length =
263 static_cast<uint32_t>(args[offset + 2]
264 .As<Uint32>()->Value()) / CHAR_BIT8;
265 if (params->length != expected) {
266 if ((EVP_MD_flagsEVP_MD_get_flags(params->digest) & EVP_MD_FLAG_XOF0x0002) == 0) {
267 THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Digest method not supported");
268 return Nothing<bool>();
269 }
270 }
271 }
272
273 return Just(true);
274}
275
276bool HashTraits::DeriveBits(
277 Environment* env,
278 const HashConfig& params,
279 ByteSource* out) {
280 EVPMDPointer ctx(EVP_MD_CTX_new());
281
282 if (UNLIKELY(!ctx ||__builtin_expect(!!(!ctx || EVP_DigestInit_ex(ctx.get(), params
.digest, nullptr) <= 0 || EVP_DigestUpdate( ctx.get(), params
.in.data<char>(), params.in.size()) <= 0), 0)
283 EVP_DigestInit_ex(ctx.get(), params.digest, nullptr) <= 0 ||__builtin_expect(!!(!ctx || EVP_DigestInit_ex(ctx.get(), params
.digest, nullptr) <= 0 || EVP_DigestUpdate( ctx.get(), params
.in.data<char>(), params.in.size()) <= 0), 0)
284 EVP_DigestUpdate(__builtin_expect(!!(!ctx || EVP_DigestInit_ex(ctx.get(), params
.digest, nullptr) <= 0 || EVP_DigestUpdate( ctx.get(), params
.in.data<char>(), params.in.size()) <= 0), 0)
285 ctx.get(), params.in.data<char>(), params.in.size()) <= 0)__builtin_expect(!!(!ctx || EVP_DigestInit_ex(ctx.get(), params
.digest, nullptr) <= 0 || EVP_DigestUpdate( ctx.get(), params
.in.data<char>(), params.in.size()) <= 0), 0)
) {
286 return false;
287 }
288
289 if (LIKELY(params.length > 0)__builtin_expect(!!(params.length > 0), 1)) {
290 unsigned int length = params.length;
291 ByteSource::Builder buf(length);
292
293 size_t expected = EVP_MD_CTX_size(ctx.get())EVP_MD_get_size(EVP_MD_CTX_get0_md(ctx.get()));
294
295 int ret =
296 (length == expected)
297 ? EVP_DigestFinal_ex(ctx.get(), buf.data<unsigned char>(), &length)
298 : EVP_DigestFinalXOF(ctx.get(), buf.data<unsigned char>(), length);
299
300 if (UNLIKELY(ret != 1)__builtin_expect(!!(ret != 1), 0))
301 return false;
302
303 *out = std::move(buf).release();
304 }
305
306 return true;
307}
308
309} // namespace crypto
310} // namespace node