Skip to content

Commit 71282c0

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 Signed-off-by: semimikoh <ejffjeosms@gmail.com>
1 parent dbabdf7 commit 71282c0

File tree

2 files changed

+44
-1
lines changed

2 files changed

+44
-1
lines changed

src/encoding_binding.cc

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

11+
#include <algorithm>
12+
#include <climits>
1113
#include <cstdint>
1214

1315
namespace node {
@@ -102,7 +104,7 @@ void BindingData::EncodeInto(const FunctionCallbackInfo<Value>& args) {
102104
int written = source->WriteUtf8(
103105
isolate,
104106
write_result,
105-
dest_length,
107+
std::min(dest_length, static_cast<size_t>(INT_MAX)),
106108
&nchars,
107109
String::NO_NULL_TERMINATION | String::REPLACE_INVALID_UTF8);
108110

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

0 commit comments

Comments
 (0)