Skip to content

Commit 8176c2c

Browse files
authored
buffer: fix end parameter bugs in indexOf/lastIndexOf
- Fix FastIndexOfNumber parameter order mismatch (end_i64 and is_forward were swapped vs the JS call site and slow path) - Clamp negative end values to 0 to prevent size_t overflow in IndexOfString, IndexOfBuffer, and IndexOfNumberImpl - Clamp empty needle result to search_end Signed-off-by: Robert Nagy <ronagy@icloud.com> Assisted-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> PR-URL: #62711 Fixes: #62873 Reviewed-By: Filip Skokan <panva.ip@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent fcff458 commit 8176c2c

2 files changed

Lines changed: 50 additions & 11 deletions

File tree

src/node_buffer.cc

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -983,8 +983,8 @@ void IndexOfString(const FunctionCallbackInfo<Value>& args) {
983983
if (!StringBytes::Size(isolate, needle, enc).To(&needle_length)) return;
984984

985985
// search_end is the exclusive upper bound of the search range.
986-
size_t search_end = static_cast<size_t>(
987-
std::min(end_i64, static_cast<int64_t>(haystack_length)));
986+
size_t search_end = static_cast<size_t>(std::min(
987+
std::max(end_i64, int64_t{0}), static_cast<int64_t>(haystack_length)));
988988
if (enc == UCS2) search_end &= ~static_cast<size_t>(1);
989989

990990
int64_t opt_offset = IndexOfOffset(haystack_length,
@@ -993,8 +993,10 @@ void IndexOfString(const FunctionCallbackInfo<Value>& args) {
993993
is_forward);
994994

995995
if (needle_length == 0) {
996-
// Match String#indexOf() and String#lastIndexOf() behavior.
997-
args.GetReturnValue().Set(static_cast<double>(opt_offset));
996+
// Match String#indexOf() and String#lastIndexOf() behavior,
997+
// but clamp to search_end.
998+
int64_t clamped = std::min(opt_offset, static_cast<int64_t>(search_end));
999+
args.GetReturnValue().Set(static_cast<double>(clamped));
9981000
return;
9991001
}
10001002

@@ -1108,8 +1110,8 @@ void IndexOfBuffer(const FunctionCallbackInfo<Value>& args) {
11081110
const size_t needle_length = needle_contents.length();
11091111

11101112
// search_end is the exclusive upper bound of the search range.
1111-
size_t search_end = static_cast<size_t>(
1112-
std::min(end_i64, static_cast<int64_t>(haystack_length)));
1113+
size_t search_end = static_cast<size_t>(std::min(
1114+
std::max(end_i64, int64_t{0}), static_cast<int64_t>(haystack_length)));
11131115
if (enc == UCS2) search_end &= ~static_cast<size_t>(1);
11141116

11151117
int64_t opt_offset = IndexOfOffset(haystack_length,
@@ -1118,8 +1120,10 @@ void IndexOfBuffer(const FunctionCallbackInfo<Value>& args) {
11181120
is_forward);
11191121

11201122
if (needle_length == 0) {
1121-
// Match String#indexOf() and String#lastIndexOf() behavior.
1122-
args.GetReturnValue().Set(static_cast<double>(opt_offset));
1123+
// Match String#indexOf() and String#lastIndexOf() behavior,
1124+
// but clamp to search_end.
1125+
int64_t clamped = std::min(opt_offset, static_cast<int64_t>(search_end));
1126+
args.GetReturnValue().Set(static_cast<double>(clamped));
11231127
return;
11241128
}
11251129

@@ -1184,8 +1188,8 @@ int32_t IndexOfNumberImpl(Local<Value> buffer_obj,
11841188
}
11851189
size_t offset = static_cast<size_t>(opt_offset);
11861190
// search_end is the exclusive upper bound of the search range.
1187-
size_t search_end = static_cast<size_t>(
1188-
std::min(end_i64, static_cast<int64_t>(buffer_length)));
1191+
size_t search_end = static_cast<size_t>(std::min(
1192+
std::max(end_i64, int64_t{0}), static_cast<int64_t>(buffer_length)));
11891193

11901194
const void* ptr;
11911195
if (is_forward) {
@@ -1222,8 +1226,8 @@ int32_t FastIndexOfNumber(Local<Value>,
12221226
Local<Value> buffer_obj,
12231227
uint32_t needle,
12241228
int64_t offset_i64,
1225-
int64_t end_i64,
12261229
bool is_forward,
1230+
int64_t end_i64,
12271231
// NOLINTNEXTLINE(runtime/references)
12281232
FastApiCallbackOptions& options) {
12291233
HandleScope scope(options.isolate);

test/parallel/test-buffer-indexof.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,3 +691,38 @@ assert.strictEqual(reallyLong.lastIndexOf(pattern), 0);
691691

692692
assert.strictEqual(buf.includes('c'), true);
693693
}
694+
695+
{
696+
const buf = Buffer.from('abcabc');
697+
698+
// Negative end should be treated as 0 (no match possible).
699+
assert.strictEqual(buf.indexOf('a', 0, -1), -1);
700+
assert.strictEqual(buf.indexOf('a', 0, -100), -1);
701+
assert.strictEqual(buf.indexOf(0x61, 0, -1), -1);
702+
assert.strictEqual(buf.lastIndexOf('a', 5, -1), -1);
703+
assert.strictEqual(buf.lastIndexOf(0x61, 5, -1), -1);
704+
assert.strictEqual(buf.includes('a', 0, -1), false);
705+
assert.strictEqual(buf.indexOf(Buffer.from('a'), 0, -1), -1);
706+
assert.strictEqual(buf.lastIndexOf(Buffer.from('a'), 5, -1), -1);
707+
708+
// End = 0 means empty search range.
709+
assert.strictEqual(buf.indexOf('a', 0, 0), -1);
710+
assert.strictEqual(buf.indexOf(0x61, 0, 0), -1);
711+
assert.strictEqual(buf.lastIndexOf('a', 5, 0), -1);
712+
assert.strictEqual(buf.lastIndexOf(0x61, 5, 0), -1);
713+
714+
// End greater than buffer length should be clamped.
715+
assert.strictEqual(buf.indexOf('c', 0, 100), 2);
716+
assert.strictEqual(buf.indexOf(0x63, 0, 100), 2);
717+
assert.strictEqual(buf.lastIndexOf('c', 5, 100), 5);
718+
assert.strictEqual(buf.lastIndexOf(0x63, 5, 100), 5);
719+
assert.strictEqual(buf.indexOf(Buffer.from('c'), 0, 100), 2);
720+
721+
// Empty needle with end parameter should clamp to search_end.
722+
assert.strictEqual(buf.indexOf('', 0, 3), 0);
723+
assert.strictEqual(buf.indexOf('', 5, 3), 3);
724+
assert.strictEqual(buf.indexOf(Buffer.from(''), 5, 3), 3);
725+
assert.strictEqual(buf.indexOf('', 0, 0), 0);
726+
assert.strictEqual(buf.lastIndexOf('', 5, 3), 3);
727+
assert.strictEqual(buf.lastIndexOf(Buffer.from(''), 5, 3), 3);
728+
}

0 commit comments

Comments
 (0)