From 4c287fabb54b9005aef1594775f52e031e9b5d90 Mon Sep 17 00:00:00 2001 From: Dmitry Volyntsev Date: Mon, 17 Nov 2025 18:55:16 -0800 Subject: [PATCH] Added missing detached array checks. --- src/njs_buffer.c | 29 +++++++++++++++++++---------- src/njs_encoding.c | 13 +++++++++++++ src/njs_json.c | 7 ++++++- src/njs_object.c | 5 +++++ src/njs_typed_array.c | 2 ++ src/test/njs_unit_test.c | 5 +++++ test/buffer.t.js | 6 ++++++ test/harness/runTsuite.js | 7 +++++++ test/text_decoder.t.js | 30 ++++++++++++++++++++++++++++++ test/text_encoder.t.js | 30 ++++++++++++++++++++++++++++++ 10 files changed, 123 insertions(+), 11 deletions(-) diff --git a/src/njs_buffer.c b/src/njs_buffer.c index 93426e5c..28fef5d6 100644 --- a/src/njs_buffer.c +++ b/src/njs_buffer.c @@ -568,23 +568,27 @@ njs_buffer_byte_length(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, { size_t size; njs_value_t *value; + njs_typed_array_t *array; const njs_buffer_encoding_t *encoding; value = njs_arg(args, nargs, 1); switch (value->type) { case NJS_TYPED_ARRAY: - njs_set_number(retval, njs_typed_array(value)->byte_length); + case NJS_DATA_VIEW: + array = njs_typed_array(value); + if (njs_slow_path(njs_is_detached_buffer(array->buffer))) { + njs_set_number(retval, 0); + return NJS_OK; + } + + njs_set_number(retval, array->byte_length); return NJS_OK; case NJS_ARRAY_BUFFER: njs_set_number(retval, njs_array_buffer(value)->size); return NJS_OK; - case NJS_DATA_VIEW: - njs_set_number(retval, njs_data_view(value)->byte_length); - return NJS_OK; - case NJS_STRING: encoding = njs_buffer_encoding(vm, njs_arg(args, nargs, 2), 1); if (njs_slow_path(encoding == NULL)) { @@ -681,6 +685,11 @@ njs_buffer_array_range(njs_vm_t *vm, njs_typed_array_t *array, num_start = 0; + if (njs_slow_path(njs_is_detached_buffer(array->buffer))) { + njs_type_error(vm, "detached buffer"); + return NJS_ERROR; + } + if (njs_is_defined(start)) { ret = njs_value_to_index(vm, njs_value_arg(start), &num_start); if (njs_slow_path(ret != NJS_OK)) { @@ -712,11 +721,6 @@ njs_buffer_array_range(njs_vm_t *vm, njs_typed_array_t *array, } buffer = njs_typed_array_buffer(array); - if (njs_slow_path(njs_is_detached_buffer(buffer))) { - njs_type_error(vm, "detached buffer"); - return NJS_ERROR; - } - *out_start = &buffer->u.u8[array->offset + num_start]; *out_end = &buffer->u.u8[array->offset + num_end]; @@ -1913,6 +1917,11 @@ njs_buffer_fill_typed_array(njs_vm_t *vm, const njs_value_t *value, buffer = njs_typed_array_buffer(array); arr_from = njs_typed_array(value); + if (njs_slow_path(njs_is_detached_buffer(arr_from->buffer))) { + njs_type_error(vm, "detached buffer"); + return NJS_ERROR; + } + byte_length = arr_from->byte_length; from = &njs_typed_array_buffer(arr_from)->u.u8[arr_from->offset]; diff --git a/src/njs_encoding.c b/src/njs_encoding.c index 37bdecf3..b1e1d903 100644 --- a/src/njs_encoding.c +++ b/src/njs_encoding.c @@ -199,6 +199,11 @@ njs_text_encoder_encode_into(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, end = start + str.length; array = njs_typed_array(dest); + if (njs_slow_path(njs_is_detached_buffer(array->buffer))) { + njs_type_error(vm, "detached buffer"); + return NJS_ERROR; + } + to = njs_typed_array_start(array); to_end = to + array->byte_length; @@ -512,12 +517,20 @@ njs_text_decoder_decode(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, if (njs_is_typed_array(value)) { array = njs_typed_array(value); + if (njs_slow_path(njs_is_detached_buffer(array->buffer))) { + njs_type_error(vm, "detached buffer"); + return NJS_ERROR; + } start = njs_typed_array_start(array); end = start + array->byte_length; } else if (njs_is_array_buffer(value)) { buffer = njs_array_buffer(value); + if (njs_slow_path(njs_is_detached_buffer(buffer))) { + njs_type_error(vm, "detached buffer"); + return NJS_ERROR; + } start = buffer->u.u8; end = start + buffer->size; diff --git a/src/njs_json.c b/src/njs_json.c index 1e5f0c40..166fb1b1 100644 --- a/src/njs_json.c +++ b/src/njs_json.c @@ -1790,7 +1790,12 @@ njs_dump_terminal(njs_json_stringify_t *stringify, njs_chb_t *chain, njs_chb_append_literal(chain, "["); - njs_typed_array_to_chain(stringify->vm, chain, array, NULL); + if (!njs_is_detached_buffer(array->buffer)) { + njs_typed_array_to_chain(stringify->vm, chain, array, NULL); + + } else { + njs_chb_append_literal(chain, ""); + } njs_chb_append_literal(chain, "]"); diff --git a/src/njs_object.c b/src/njs_object.c index 97f17d58..a97c4878 100644 --- a/src/njs_object.c +++ b/src/njs_object.c @@ -661,6 +661,11 @@ njs_object_enumerate_typed_array(njs_vm_t *vm, const njs_typed_array_t *array, njs_value_t *item; njs_array_t *entry; + if (njs_slow_path(njs_is_detached_buffer(array->buffer))) { + njs_type_error(vm, "detached buffer"); + return NJS_ERROR; + } + length = njs_typed_array_length(array); ret = njs_array_expand(vm, items, 0, length); diff --git a/src/njs_typed_array.c b/src/njs_typed_array.c index 39440172..f74f175f 100644 --- a/src/njs_typed_array.c +++ b/src/njs_typed_array.c @@ -2070,6 +2070,8 @@ njs_typed_array_to_chain(njs_vm_t *vm, njs_chb_t *chain, uint32_t i; njs_string_prop_t separator; + njs_assert(!njs_is_detached_buffer(array->buffer)); + if (sep != NULL && njs_is_string(sep)) { (void) njs_string_prop(vm, &separator, sep); diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index cf266dfc..e205ee20 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -6259,6 +6259,11 @@ static njs_unit_test_t njs_test[] = " try {a.set(init,Infinity)} catch (e) {return e.name == 'RangeError'};})"), njs_str("true") }, + { njs_str(NJS_INT_TYPED_ARRAY_LIST + ".map(v=>{try { var a = new v(1); $262.detachArrayBuffer(a.buffer); Object.entries(a)} " + "catch (e) {return e.name}}).every(v=>v === 'TypeError')"), + njs_str("true") }, + { njs_str("[-1,-1.00001,-Infinity]" ".every(v=>{ try {(new Uint8Array(10)).set([], v)} catch (ee) {return ee.name === 'RangeError'}})"), njs_str("true") }, diff --git a/test/buffer.t.js b/test/buffer.t.js index 37821104..ee7831d9 100644 --- a/test/buffer.t.js +++ b/test/buffer.t.js @@ -254,6 +254,10 @@ let fill_tsuite = { name: "buf.fill() tests", skip: () => (!has_buffer()), T: async (params) => { + if (params.detach_value) { + detach(params.value.buffer); + } + let r = params.buf.fill(params.value, params.offset, params.end); if (r.toString() !== params.expected) { @@ -274,6 +278,8 @@ let fill_tsuite = { exception: 'TypeError: "utf-128" encoding is not supported' }, { buf: Buffer.from('abc'), value: 'ABCD', offset: 1, expected: 'aAB' }, { buf: Buffer.from('abc'), value: Buffer.from('def'), expected: 'def' }, + { buf: Buffer.from('abc'), value: Buffer.from('def'), detach_value: true, + exception: 'TypeError: detached buffer' }, { buf: Buffer.from('def'), value: Buffer.from(new Uint8Array([0x60, 0x61, 0x62, 0x63]).buffer, 1), expected: 'abc' }, diff --git a/test/harness/runTsuite.js b/test/harness/runTsuite.js index 2a324fbe..14deebd9 100644 --- a/test/harness/runTsuite.js +++ b/test/harness/runTsuite.js @@ -95,3 +95,10 @@ function merge(to, from) { return r; } +function detach(ab) { + $262.detachArrayBuffer(ab); +} + +function is_detach_available() { + return (typeof $262 !== 'undefined' && typeof $262.detachArrayBuffer === 'function'); +} diff --git a/test/text_decoder.t.js b/test/text_decoder.t.js index afc91fc4..eaf7e191 100644 --- a/test/text_decoder.t.js +++ b/test/text_decoder.t.js @@ -133,10 +133,40 @@ let ignoreBOM_tsuite = { ], }; +let detached_tsuite = { + name: "TextDecoder() detached buffer test", + skip: () => !is_detach_available(), + + T: async (params) => { + let td = new TextDecoder('utf-8'); + let uint8 = new Uint8Array([0]); + + detach(uint8.buffer); + + try { + td.decode(uint8); + + } catch (e) { + if (e.toString().startsWith('TypeError:')) { + return 'SUCCESS'; + } else { + throw e; + } + } + + throw Error('Expected TypeError not thrown'); + }, + + tests: [ + { }, + ], +}; + run([ stream_tsuite, fatal_tsuite, ignoreBOM_tsuite, + detached_tsuite, ]) .then($DONE, $DONE); diff --git a/test/text_encoder.t.js b/test/text_encoder.t.js index 25173cd1..f9274ceb 100644 --- a/test/text_encoder.t.js +++ b/test/text_encoder.t.js @@ -67,8 +67,38 @@ let encodeinto_tsuite = { ], }; +let detached_tsuite = { + name: "TextEncoder() with detached buffer tests", + skip: () => (!is_detach_available()), + + T: async (params) => { + let td = new TextEncoder(); + let uint8 = new Uint8Array(10); + + detach(uint8.buffer); + + try { + td.encodeInto("test", uint8); + + } catch (e) { + if (e.toString().startsWith('TypeError:')) { + return 'SUCCESS'; + } else { + throw e; + } + } + + throw Error('Expected TypeError not thrown'); + }, + + tests: [ + { }, + ], +}; + run([ encode_tsuite, encodeinto_tsuite, + detached_tsuite, ]) .then($DONE, $DONE); -- 2.47.3