Skip to content

Commit 6a8808a

Browse files
panvaaduh95
authored andcommitted
crypto: guard WebCrypto cipher output length
Reject WebCrypto cipher operations whose computed output length would exceed INT_MAX before passing the length to OpenSSL. This avoids signed overflow in the AES and ChaCha20-Poly1305 one-shot cipher paths and turns oversized inputs into a clean operation failure. Refs: https://hackerone.com/reports/3760016 Signed-off-by: Filip Skokan <panva.ip@gmail.com> PR-URL: nodejs-private/node-private#878 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> CVE-ID: CVE-2026-48933
1 parent 179ddae commit 6a8808a

4 files changed

Lines changed: 55 additions & 5 deletions

File tree

src/crypto/crypto_aes.cc

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,17 @@ WebCryptoCipherStatus AES_Cipher(Environment* env,
120120
}
121121

122122
size_t total = 0;
123-
int buf_len = data_len + ctx.getBlockSize() + (encrypt ? tag_len : 0);
123+
const int block_size = ctx.getBlockSize();
124+
if (block_size < 0) {
125+
return WebCryptoCipherStatus::FAILED;
126+
}
127+
int buf_len;
128+
if (!TryGetIntCipherOutputLength(
129+
data_len,
130+
static_cast<size_t>(block_size) + (encrypt ? tag_len : 0),
131+
&buf_len)) {
132+
return WebCryptoCipherStatus::FAILED;
133+
}
124134
int out_len;
125135

126136
ncrypto::Buffer<const unsigned char> buffer = {
@@ -156,7 +166,7 @@ WebCryptoCipherStatus AES_Cipher(Environment* env,
156166

157167
total += out_len;
158168
CHECK_LE(out_len, buf_len);
159-
out_len = ctx.getBlockSize();
169+
out_len = block_size;
160170
if (!ctx.update({}, ptr + total, &out_len, true)) {
161171
return WebCryptoCipherStatus::FAILED;
162172
}

src/crypto/crypto_chacha20_poly1305.cc

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,17 @@ WebCryptoCipherStatus ChaCha20Poly1305CipherTraits::DoCipher(
267267
}
268268

269269
size_t total = 0;
270-
int buf_len = data_len + ctx.getBlockSize() + (encrypt ? tag_len : 0);
270+
const int block_size = ctx.getBlockSize();
271+
if (block_size < 0) {
272+
return WebCryptoCipherStatus::FAILED;
273+
}
274+
int buf_len;
275+
if (!TryGetIntCipherOutputLength(
276+
data_len,
277+
static_cast<size_t>(block_size) + (encrypt ? tag_len : 0),
278+
&buf_len)) {
279+
return WebCryptoCipherStatus::FAILED;
280+
}
271281
int out_len;
272282

273283
// Process additional authenticated data if present
@@ -297,7 +307,7 @@ WebCryptoCipherStatus ChaCha20Poly1305CipherTraits::DoCipher(
297307

298308
total += out_len;
299309
CHECK_LE(out_len, buf_len);
300-
out_len = ctx.getBlockSize();
310+
out_len = block_size;
301311
if (!ctx.update({}, ptr + total, &out_len, true)) {
302312
return WebCryptoCipherStatus::FAILED;
303313
}

src/crypto/crypto_cipher.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "memory_tracker.h"
1111
#include "v8.h"
1212

13+
#include <climits>
1314
#include <string>
1415

1516
namespace node {
@@ -124,6 +125,18 @@ enum class WebCryptoCipherStatus {
124125
FAILED
125126
};
126127

128+
inline bool TryGetIntCipherOutputLength(size_t input_len,
129+
size_t output_overhead,
130+
int* output_len) {
131+
static constexpr size_t kMaxLength = INT_MAX;
132+
if (output_overhead > kMaxLength ||
133+
input_len > kMaxLength - output_overhead) {
134+
return false;
135+
}
136+
*output_len = static_cast<int>(input_len + output_overhead);
137+
return true;
138+
}
139+
127140
// CipherJob is a base implementation class for implementations of
128141
// one-shot sync and async ciphers. It has been added primarily to
129142
// support the AES and RSA ciphers underlying the WebCrypt API.

test/cctest/test_node_crypto.cc

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22
// and setting it to a file that does not exist.
33
#define NODE_OPENSSL_SYSTEM_CERT_PATH "/missing/ca.pem"
44

5+
#include "crypto/crypto_cipher.h"
56
#include "crypto/crypto_context.h"
7+
#include "gtest/gtest.h"
68
#include "node_options.h"
79
#include "openssl/err.h"
8-
#include "gtest/gtest.h"
10+
11+
#include <climits>
912

1013
/*
1114
* This test verifies that a call to NewRootCertDir with the build time
@@ -48,3 +51,17 @@ TEST(NodeCrypto, MemoryTrackingConstants) {
4851
EXPECT_EQ(node::crypto::kSizeOf_X509, static_cast<size_t>(128));
4952
EXPECT_EQ(node::crypto::kSizeOf_EVP_MD_CTX, static_cast<size_t>(48));
5053
}
54+
55+
TEST(NodeCrypto, TryGetIntCipherOutputLength) {
56+
int output_len = 0;
57+
58+
EXPECT_TRUE(
59+
node::crypto::TryGetIntCipherOutputLength(INT_MAX - 16, 16, &output_len));
60+
EXPECT_EQ(output_len, INT_MAX);
61+
62+
EXPECT_FALSE(
63+
node::crypto::TryGetIntCipherOutputLength(INT_MAX - 15, 16, &output_len));
64+
65+
EXPECT_FALSE(node::crypto::TryGetIntCipherOutputLength(
66+
0, static_cast<size_t>(INT_MAX) + 1, &output_len));
67+
}

0 commit comments

Comments
 (0)