Skip to content

Commit 2ba6010

Browse files
addaleaxMylesBorins
authored andcommitted
buffer: fix crash for invalid index types
2555cb4 introduced a crash when a non-number value was passed to `ParseArrayIndex()`. We do not always have JS typechecking for that in place, though. This returns back to the previous behavior of coercing values to integers, which is certainly questionable. Refs: #22129 Fixes: #23668
1 parent 2cd68be commit 2ba6010

File tree

2 files changed

+62
-26
lines changed

2 files changed

+62
-26
lines changed

src/node_buffer.cc

+36-26
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,18 @@
4040

4141
#define THROW_AND_RETURN_IF_OOB(r) \
4242
do { \
43-
if (!(r)) return node::THROW_ERR_INDEX_OUT_OF_RANGE(env); \
44-
} while (0)
43+
if ((r).IsNothing()) return; \
44+
if (!(r).FromJust()) \
45+
return node::THROW_ERR_INDEX_OUT_OF_RANGE(env); \
46+
} while (0) \
4547

46-
#define SLICE_START_END(start_arg, end_arg, end_max) \
48+
#define SLICE_START_END(env, start_arg, end_arg, end_max) \
4749
size_t start; \
4850
size_t end; \
49-
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(start_arg, 0, &start)); \
50-
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(end_arg, end_max, &end)); \
51+
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(env, start_arg, 0, &start)); \
52+
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(env, end_arg, end_max, &end)); \
5153
if (end < start) end = start; \
52-
THROW_AND_RETURN_IF_OOB(end <= end_max); \
54+
THROW_AND_RETURN_IF_OOB(Just(end <= end_max)); \
5355
size_t length = end - start;
5456

5557
namespace node {
@@ -76,9 +78,11 @@ using v8::EscapableHandleScope;
7678
using v8::FunctionCallbackInfo;
7779
using v8::Integer;
7880
using v8::Isolate;
81+
using v8::Just;
7982
using v8::Local;
8083
using v8::Maybe;
8184
using v8::MaybeLocal;
85+
using v8::Nothing;
8286
using v8::Object;
8387
using v8::String;
8488
using v8::Uint32;
@@ -161,29 +165,32 @@ void CallbackInfo::WeakCallback(Isolate* isolate) {
161165
}
162166

163167

164-
// Parse index for external array data.
165-
inline MUST_USE_RESULT bool ParseArrayIndex(Local<Value> arg,
166-
size_t def,
167-
size_t* ret) {
168+
// Parse index for external array data. An empty Maybe indicates
169+
// a pending exception. `false` indicates that the index is out-of-bounds.
170+
inline MUST_USE_RESULT Maybe<bool> ParseArrayIndex(Environment* env,
171+
Local<Value> arg,
172+
size_t def,
173+
size_t* ret) {
168174
if (arg->IsUndefined()) {
169175
*ret = def;
170-
return true;
176+
return Just(true);
171177
}
172178

173-
CHECK(arg->IsNumber());
174-
int64_t tmp_i = arg.As<Integer>()->Value();
179+
int64_t tmp_i;
180+
if (!arg->IntegerValue(env->context()).To(&tmp_i))
181+
return Nothing<bool>();
175182

176183
if (tmp_i < 0)
177-
return false;
184+
return Just(false);
178185

179186
// Check that the result fits in a size_t.
180187
const uint64_t kSizeMax = static_cast<uint64_t>(static_cast<size_t>(-1));
181188
// coverity[pointless_expression]
182189
if (static_cast<uint64_t>(tmp_i) > kSizeMax)
183-
return false;
190+
return Just(false);
184191

185192
*ret = static_cast<size_t>(tmp_i);
186-
return true;
193+
return Just(true);
187194
}
188195

189196
} // anonymous namespace
@@ -452,7 +459,7 @@ void StringSlice(const FunctionCallbackInfo<Value>& args) {
452459
if (ts_obj_length == 0)
453460
return args.GetReturnValue().SetEmptyString();
454461

455-
SLICE_START_END(args[0], args[1], ts_obj_length)
462+
SLICE_START_END(env, args[0], args[1], ts_obj_length)
456463

457464
Local<Value> error;
458465
MaybeLocal<Value> ret =
@@ -485,9 +492,10 @@ void Copy(const FunctionCallbackInfo<Value> &args) {
485492
size_t source_start;
486493
size_t source_end;
487494

488-
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(args[2], 0, &target_start));
489-
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(args[3], 0, &source_start));
490-
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(args[4], ts_obj_length, &source_end));
495+
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(env, args[2], 0, &target_start));
496+
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(env, args[3], 0, &source_start));
497+
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(env, args[4], ts_obj_length,
498+
&source_end));
491499

492500
// Copy 0 bytes; we're done
493501
if (target_start >= target_length || source_start >= source_end)
@@ -617,13 +625,13 @@ void StringWrite(const FunctionCallbackInfo<Value>& args) {
617625
size_t offset;
618626
size_t max_length;
619627

620-
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(args[1], 0, &offset));
628+
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(env, args[1], 0, &offset));
621629
if (offset > ts_obj_length) {
622630
return node::THROW_ERR_BUFFER_OUT_OF_BOUNDS(
623631
env, "\"offset\" is outside of buffer bounds");
624632
}
625633

626-
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(args[2], ts_obj_length - offset,
634+
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(env, args[2], ts_obj_length - offset,
627635
&max_length));
628636

629637
max_length = MIN(ts_obj_length - offset, max_length);
@@ -678,10 +686,12 @@ void CompareOffset(const FunctionCallbackInfo<Value> &args) {
678686
size_t source_end;
679687
size_t target_end;
680688

681-
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(args[2], 0, &target_start));
682-
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(args[3], 0, &source_start));
683-
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(args[4], target_length, &target_end));
684-
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(args[5], ts_obj_length, &source_end));
689+
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(env, args[2], 0, &target_start));
690+
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(env, args[3], 0, &source_start));
691+
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(env, args[4], target_length,
692+
&target_end));
693+
THROW_AND_RETURN_IF_OOB(ParseArrayIndex(env, args[5], ts_obj_length,
694+
&source_end));
685695

686696
if (source_start > ts_obj_length)
687697
return node::THROW_ERR_INDEX_OUT_OF_RANGE(env);

test/parallel/test-buffer-copy.js

+26
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@ let cntr = 0;
1818
}
1919
}
2020

21+
{
22+
// Current behavior is to coerce values to integers.
23+
b.fill(++cntr);
24+
c.fill(++cntr);
25+
const copied = b.copy(c, '0', '0', '512');
26+
assert.strictEqual(copied, 512);
27+
for (let i = 0; i < c.length; i++) {
28+
assert.strictEqual(c[i], b[i]);
29+
}
30+
}
31+
2132
{
2233
// copy c into b, without specifying sourceEnd
2334
b.fill(++cntr);
@@ -144,3 +155,18 @@ assert.strictEqual(b.copy(c, 512, 0, 10), 0);
144155
assert.strictEqual(c[i], e[i]);
145156
}
146157
}
158+
159+
// https://github.com/nodejs/node/issues/23668: Do not crash for invalid input.
160+
c.fill('c');
161+
b.copy(c, 'not a valid offset');
162+
// Make sure this acted like a regular copy with `0` offset.
163+
assert.deepStrictEqual(c, b.slice(0, c.length));
164+
165+
{
166+
c.fill('C');
167+
assert.throws(() => {
168+
b.copy(c, { [Symbol.toPrimitive]() { throw new Error('foo'); } });
169+
}, /foo/);
170+
// No copying took place:
171+
assert.deepStrictEqual(c.toString(), 'C'.repeat(c.length));
172+
}

0 commit comments

Comments
 (0)