Skip to content

Commit 9e4dfc7

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 be7e719 commit 9e4dfc7

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
@@ -118,7 +118,17 @@ WebCryptoCipherStatus AES_Cipher(Environment* env,
118118
}
119119

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

124134
ncrypto::Buffer<const unsigned char> buffer = {
@@ -154,7 +164,7 @@ WebCryptoCipherStatus AES_Cipher(Environment* env,
154164

155165
total += out_len;
156166
CHECK_LE(out_len, buf_len);
157-
out_len = ctx.getBlockSize();
167+
out_len = block_size;
158168
if (!ctx.update({}, ptr + total, &out_len, true)) {
159169
return WebCryptoCipherStatus::FAILED;
160170
}

src/crypto/crypto_chacha20_poly1305.cc

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

190190
size_t total = 0;
191-
int buf_len = data_len + ctx.getBlockSize() + (encrypt ? tag_len : 0);
191+
const int block_size = ctx.getBlockSize();
192+
if (block_size < 0) {
193+
return WebCryptoCipherStatus::FAILED;
194+
}
195+
int buf_len;
196+
if (!TryGetIntCipherOutputLength(
197+
data_len,
198+
static_cast<size_t>(block_size) + (encrypt ? tag_len : 0),
199+
&buf_len)) {
200+
return WebCryptoCipherStatus::FAILED;
201+
}
192202
int out_len;
193203

194204
// Process additional authenticated data if present
@@ -218,7 +228,7 @@ WebCryptoCipherStatus ChaCha20Poly1305CipherTraits::DoCipher(
218228

219229
total += out_len;
220230
CHECK_LE(out_len, buf_len);
221-
out_len = ctx.getBlockSize();
231+
out_len = block_size;
222232
if (!ctx.update({}, ptr + total, &out_len, true)) {
223233
return WebCryptoCipherStatus::FAILED;
224234
}

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, 128);
4952
EXPECT_EQ(node::crypto::kSizeOf_EVP_MD_CTX, 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)