Skip to content

Commit 38b4c5e

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 <[email protected]> Backport-PR-URL: nodejs-private/node-private#879 Reviewed-By: Antoine du Hamel <[email protected]> PR-URL: nodejs-private/node-private#878 CVE-ID: CVE-2026-48933
1 parent 0a22d40 commit 38b4c5e

3 files changed

Lines changed: 41 additions & 3 deletions

File tree

src/crypto/crypto_aes.cc

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,15 @@ WebCryptoCipherStatus AES_Cipher(Environment* env,
101101
}
102102

103103
size_t total = 0;
104-
int buf_len = in.size() + ctx.getBlockSize() + tag_len;
104+
const int block_size = ctx.getBlockSize();
105+
if (block_size < 0) {
106+
return WebCryptoCipherStatus::FAILED;
107+
}
108+
int buf_len;
109+
if (!TryGetIntCipherOutputLength(
110+
in.size(), static_cast<size_t>(block_size) + tag_len, &buf_len)) {
111+
return WebCryptoCipherStatus::FAILED;
112+
}
105113
int out_len;
106114

107115
ncrypto::Buffer<const unsigned char> buffer = {
@@ -135,7 +143,7 @@ WebCryptoCipherStatus AES_Cipher(Environment* env,
135143

136144
total += out_len;
137145
CHECK_LE(out_len, buf_len);
138-
out_len = ctx.getBlockSize();
146+
out_len = block_size;
139147
if (!ctx.update({}, buf.data<unsigned char>() + total, &out_len, true)) {
140148
return WebCryptoCipherStatus::FAILED;
141149
}

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 {
@@ -134,6 +135,18 @@ enum class WebCryptoCipherStatus {
134135
FAILED
135136
};
136137

138+
inline bool TryGetIntCipherOutputLength(size_t input_len,
139+
size_t output_overhead,
140+
int* output_len) {
141+
static constexpr size_t kMaxLength = INT_MAX;
142+
if (output_overhead > kMaxLength ||
143+
input_len > kMaxLength - output_overhead) {
144+
return false;
145+
}
146+
*output_len = static_cast<int>(input_len + output_overhead);
147+
return true;
148+
}
149+
137150
// CipherJob is a base implementation class for implementations of
138151
// one-shot sync and async ciphers. It has been added primarily to
139152
// 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
@@ -21,3 +24,17 @@ TEST(NodeCrypto, NewRootCertStore) {
2124
"any errors on the OpenSSL error stack\n";
2225
X509_STORE_free(store);
2326
}
27+
28+
TEST(NodeCrypto, TryGetIntCipherOutputLength) {
29+
int output_len = 0;
30+
31+
EXPECT_TRUE(
32+
node::crypto::TryGetIntCipherOutputLength(INT_MAX - 16, 16, &output_len));
33+
EXPECT_EQ(output_len, INT_MAX);
34+
35+
EXPECT_FALSE(
36+
node::crypto::TryGetIntCipherOutputLength(INT_MAX - 15, 16, &output_len));
37+
38+
EXPECT_FALSE(node::crypto::TryGetIntCipherOutputLength(
39+
0, static_cast<size_t>(INT_MAX) + 1, &output_len));
40+
}

0 commit comments

Comments
 (0)