Skip to content

Commit a36efd4

Browse files
committed
src: clamp WriteUtf8 capacity to INT_MAX in EncodeInto
In TextEncoder.encodeInto, the destination buffer's byte length is read as a size_t but then implicitly narrowed to int when passed as the capacity argument to v8::String::WriteUtf8. When the destination view is larger than INT_MAX (2,147,483,647 bytes), the narrowing conversion underflows to a negative value, V8 treats it as "no capacity", and writes 0 bytes - returning { read: 0, written: 0 } even though the buffer has plenty of room. Clamp the capacity to INT_MAX before passing it to WriteUtf8. This is sufficient because the source string in encodeInto is bounded in practice and never requires more than INT_MAX bytes to encode; only the destination view length can exceed INT_MAX. This issue is already fixed on main and v24.x as a side effect of PR #58070, which migrated to the non-deprecated WriteUtf8V2 method whose capacity parameter is size_t. WriteUtf8V2 is not available in v22.x's V8 version, so this minimal patch fixes only the EncodeInto path instead of backporting the full migration. Refs: #58070 Fixes: #62610
1 parent dbabdf7 commit a36efd4

File tree

2 files changed

+50
-1
lines changed

2 files changed

+50
-1
lines changed

src/encoding_binding.cc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "string_bytes.h"
99
#include "v8.h"
1010

11+
#include <climits>
1112
#include <cstdint>
1213

1314
namespace node {
@@ -97,12 +98,14 @@ void BindingData::EncodeInto(const FunctionCallbackInfo<Value>& args) {
9798
Local<ArrayBuffer> buf = dest->Buffer();
9899
char* write_result = static_cast<char*>(buf->Data()) + dest->ByteOffset();
99100
size_t dest_length = dest->ByteLength();
101+
int max_length =
102+
dest_length > static_cast<size_t>(INT_MAX) ? INT_MAX : dest_length;
100103

101104
int nchars;
102105
int written = source->WriteUtf8(
103106
isolate,
104107
write_result,
105-
dest_length,
108+
max_length,
106109
&nchars,
107110
String::NO_NULL_TERMINATION | String::REPLACE_INVALID_UTF8);
108111

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const os = require('os');
5+
6+
common.skipIf32Bits();
7+
if (os.totalmem() < 3 * 1024 * 1024 * 1024) {
8+
common.skip('requires at least 3 GiB of system memory');
9+
}
10+
11+
const assert = require('assert');
12+
13+
const encoder = new TextEncoder();
14+
const source = 'a\xFF\u6211\u{1D452}';
15+
const expected = encoder.encode(source);
16+
17+
const size = 2 ** 31 + expected.length;
18+
const offset = expected.length - 1;
19+
20+
try {
21+
const dest = new Uint8Array(size);
22+
23+
const large = encoder.encodeInto(source, dest.subarray(offset));
24+
assert.deepStrictEqual(large, {
25+
read: source.length,
26+
written: expected.length,
27+
});
28+
assert.deepStrictEqual(dest.slice(offset, offset + expected.length), expected);
29+
30+
const bounded = encoder.encodeInto(source,
31+
dest.subarray(offset,
32+
offset + expected.length));
33+
assert.deepStrictEqual(bounded, {
34+
read: source.length,
35+
written: expected.length,
36+
});
37+
assert.deepStrictEqual(dest.slice(offset, offset + expected.length), expected);
38+
} catch (e) {
39+
if (e.code === 'ERR_MEMORY_ALLOCATION_FAILED') {
40+
common.skip('insufficient space for Uint8Array allocation');
41+
}
42+
if (/Array buffer allocation failed/.test(e.message)) {
43+
common.skip('insufficient space for Uint8Array allocation');
44+
}
45+
throw e;
46+
}

0 commit comments

Comments
 (0)