From: Dmitry Volyntsev Date: Thu, 11 Jun 2026 04:11:06 +0000 (-0700) Subject: Fix out-of-bounds read in Buffer.prototype.toString() X-Git-Tag: 1.0.0~19 X-Git-Url: http://git.kaiwu.me/postgresql/log/contrib/postgres_fdw/postgres_fdw.c?a=commitdiff_plain;h=deba551476a5841ff4ca48df70784e3e0b7dece0;p=njs.git Fix out-of-bounds read in Buffer.prototype.toString() Buffer.prototype.toString(encoding, start, end) on the njs engine clamped start and end independently to the buffer length but never enforced start <= end. With start > end the unsigned subtraction end - start underflowed, the zero-length guard did not fire, and the encoder read past the buffer. Reject the start >= end case before computing the range, returning an empty string to match Node.js semantics. While here, add parallel missing functionality to QuickJS for start and end arguments for Buffer.prototype.toString(). --- diff --git a/src/njs_buffer.c b/src/njs_buffer.c index 886a8657..75b7192b 100644 --- a/src/njs_buffer.c +++ b/src/njs_buffer.c @@ -1975,14 +1975,14 @@ njs_buffer_prototype_to_string(njs_vm_t *vm, njs_value_t *args, return NJS_ERROR; } - str.start = &njs_typed_array_buffer(array)->u.u8[array->offset + start]; - str.length = end - start; - - if (njs_slow_path(str.length == 0)) { + if (njs_slow_path(start >= end)) { njs_set_empty_string(vm, retval); return NJS_OK; } + str.start = &njs_typed_array_buffer(array)->u.u8[array->offset + start]; + str.length = end - start; + return encoding->encode(vm, retval, &str); } diff --git a/src/qjs_buffer.c b/src/qjs_buffer.c index b870be91..f51fc5a4 100644 --- a/src/qjs_buffer.c +++ b/src/qjs_buffer.c @@ -198,7 +198,7 @@ static const JSCFunctionListEntry qjs_buffer_proto[] = { JS_CFUNC_MAGIC_DEF("swap32", 0, qjs_buffer_prototype_swap, 4), JS_CFUNC_MAGIC_DEF("swap64", 0, qjs_buffer_prototype_swap, 8), JS_CFUNC_DEF("toJSON", 0, qjs_buffer_prototype_to_json), - JS_CFUNC_DEF("toString", 1, qjs_buffer_prototype_to_string), + JS_CFUNC_DEF("toString", 3, qjs_buffer_prototype_to_string), JS_CFUNC_DEF("write", 4, qjs_buffer_prototype_write), JS_CFUNC_MAGIC_DEF("writeInt8", 1, qjs_buffer_prototype_write_int, qjs_buffer_magic(1, 1, 1)), @@ -1419,6 +1419,7 @@ static JSValue qjs_buffer_prototype_to_string(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + uint64_t start, end; JSValue ret; njs_str_t src, data; const qjs_buffer_encoding_t *encoding; @@ -1429,6 +1430,33 @@ qjs_buffer_prototype_to_string(JSContext *ctx, JSValueConst this_val, " object"); } + start = 0; + end = src.length; + + if (!JS_IsUndefined(argv[1])) { + if (JS_ToIndex(ctx, &start, argv[1])) { + return JS_EXCEPTION; + } + + start = njs_min(start, src.length); + } + + if (!JS_IsUndefined(argv[2])) { + if (JS_ToIndex(ctx, &end, argv[2])) { + return JS_EXCEPTION; + } + + end = njs_min(end, src.length); + } + + if (start >= end) { + src.length = 0; + + } else { + src.start += start; + src.length = end - start; + } + if (JS_IsUndefined(argv[0]) || src.length == 0) { return JS_NewStringLen(ctx, (char *) src.start, src.length); } diff --git a/test/buffer.t.js b/test/buffer.t.js index f4841b05..be74083a 100644 --- a/test/buffer.t.js +++ b/test/buffer.t.js @@ -896,7 +896,8 @@ let toString_tsuite = { name: "Buffer.toString() tests", skip: () => (!has_buffer()), T: async (params) => { - let r = Buffer.from(params.value).toString(params.fmt); + let r = Buffer.from(params.value).toString(params.fmt, params.start, + params.end); if (r.length !== params.expected.length) { throw Error(`unexpected "${r}" length ${r.length} != ${params.expected.length}`); @@ -921,6 +922,11 @@ let toString_tsuite = { { value: "ABCD", fmt: "base64", expected: 'QUJDRA==' }, { value: "ABCD", fmt: "base64url", expected: 'QUJDRA' }, { value: '', fmt: "utf-128", exception: 'TypeError: "utf-128" encoding is not supported' }, + { value: "hello", fmt: "utf-8", start: 1, end: 4, expected: 'ell' }, + { value: "hello", fmt: "utf-8", start: 3, end: 3, expected: '' }, + { value: "hello", fmt: "utf-8", start: 10, end: 5, expected: '' }, + { value: "hello", fmt: "utf-8", start: 4, end: 2, expected: '' }, + { value: "hello", fmt: "hex", start: 4, end: 2, expected: '' }, ]};