From 316b5efd6b02379a490ce62c31a13122729ff1dd Mon Sep 17 00:00:00 2001 From: Jinho Bang Date: Mon, 8 Jan 2018 23:37:27 +0900 Subject: [PATCH] n-api: throw RangeError napi_create_typedarray() According to the ECMA spec, we should throw a RangeError in the following cases: - `(length * elementSize) + offset` > the size of the array passed in - `offset % elementSize` != `0` In the current implementation, this check was omitted. So, the following code will cause a crash. ``` napi_create_typedarray(env, napi_uint16_array, 2 /* length */, buffer, 1 /* byte_offset */, &output_array); ``` This change fixes the problem and write some related tests. Refs: https://tc39.github.io/ecma262/#sec-typedarray-buffer-byteoffset-length PR-URL: https://github.com/nodejs/node/pull/18037 Reviewed-By: Colin Ihrig Reviewed-By: James M Snell Reviewed-By: Michael Dawson --- doc/api/errors.md | 12 +++++ lib/internal/errors.js | 3 ++ src/node_api.cc | 51 +++++++++++++++---- test/addons-napi/test_typedarray/test.js | 18 +++++++ .../test_typedarray/test_typedarray.c | 28 ++++++++-- 5 files changed, 100 insertions(+), 12 deletions(-) diff --git a/doc/api/errors.md b/doc/api/errors.md index 32281dfe2d8a1f..4bfce90bced283 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -1328,6 +1328,18 @@ While using `N-API`, `Constructor.prototype` was not an object. While calling `napi_create_dataview()`, a given `offset` was outside the bounds of the dataview or `offset + length` was larger than a length of given `buffer`. + +### ERR_NAPI_INVALID_TYPEDARRAY_ALIGNMENT + +While calling `napi_create_typedarray()`, the provided `offset` was not a +multiple of the element size. + + +### ERR_NAPI_INVALID_TYPEDARRAY_LENGTH + +While calling `napi_create_typedarray()`, `(length * size_of_element) + +byte_offset` was larger than the length of given `buffer`. + ### ERR_NO_CRYPTO diff --git a/lib/internal/errors.js b/lib/internal/errors.js index b68aff932a66b4..6e6852f380d72c 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -440,6 +440,9 @@ E('ERR_NAPI_CONS_PROTOTYPE_OBJECT', 'Constructor.prototype must be an object'); E('ERR_NAPI_INVALID_DATAVIEW_ARGS', 'byte_offset + byte_length should be less than or eqaul to the size in ' + 'bytes of the array passed in'); +E('ERR_NAPI_INVALID_TYPEDARRAY_ALIGNMENT', 'start offset of %s should be a ' + + 'multiple of %s'); +E('ERR_NAPI_INVALID_TYPEDARRAY_LENGTH', 'Invalid typed array length'); E('ERR_NO_CRYPTO', 'Node.js is not compiled with OpenSSL crypto support'); E('ERR_NO_ICU', '%s is not supported on Node.js compiled without ICU'); E('ERR_NO_LONGER_SUPPORTED', '%s is no longer supported'); diff --git a/src/node_api.cc b/src/node_api.cc index 2101fcba020d95..27ab6707de7a6f 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -142,6 +142,30 @@ struct napi_env__ { (!try_catch.HasCaught() ? napi_ok \ : napi_set_last_error((env), napi_pending_exception)) +#define THROW_RANGE_ERROR_IF_FALSE(env, condition, error, message) \ + do { \ + if (!(condition)) { \ + napi_throw_range_error((env), (error), (message)); \ + return napi_set_last_error((env), napi_generic_failure); \ + } \ + } while (0) + +#define CREATE_TYPED_ARRAY( \ + env, type, size_of_element, buffer, byte_offset, length, out) \ + do { \ + if ((size_of_element) > 1) { \ + THROW_RANGE_ERROR_IF_FALSE( \ + (env), (byte_offset) % (size_of_element) == 0, \ + "ERR_NAPI_INVALID_TYPEDARRAY_ALIGNMENT", \ + "start offset of "#type" should be a multiple of "#size_of_element); \ + } \ + THROW_RANGE_ERROR_IF_FALSE((env), (length) * (size_of_element) + \ + (byte_offset) <= buffer->ByteLength(), \ + "ERR_NAPI_INVALID_TYPEDARRAY_LENGTH", \ + "Invalid typed array length"); \ + (out) = v8::type::New((buffer), (byte_offset), (length)); \ + } while (0) + namespace { namespace v8impl { @@ -3063,31 +3087,40 @@ napi_status napi_create_typedarray(napi_env env, switch (type) { case napi_int8_array: - typedArray = v8::Int8Array::New(buffer, byte_offset, length); + CREATE_TYPED_ARRAY( + env, Int8Array, 1, buffer, byte_offset, length, typedArray); break; case napi_uint8_array: - typedArray = v8::Uint8Array::New(buffer, byte_offset, length); + CREATE_TYPED_ARRAY( + env, Uint8Array, 1, buffer, byte_offset, length, typedArray); break; case napi_uint8_clamped_array: - typedArray = v8::Uint8ClampedArray::New(buffer, byte_offset, length); + CREATE_TYPED_ARRAY( + env, Uint8ClampedArray, 1, buffer, byte_offset, length, typedArray); break; case napi_int16_array: - typedArray = v8::Int16Array::New(buffer, byte_offset, length); + CREATE_TYPED_ARRAY( + env, Int16Array, 2, buffer, byte_offset, length, typedArray); break; case napi_uint16_array: - typedArray = v8::Uint16Array::New(buffer, byte_offset, length); + CREATE_TYPED_ARRAY( + env, Uint16Array, 2, buffer, byte_offset, length, typedArray); break; case napi_int32_array: - typedArray = v8::Int32Array::New(buffer, byte_offset, length); + CREATE_TYPED_ARRAY( + env, Int32Array, 4, buffer, byte_offset, length, typedArray); break; case napi_uint32_array: - typedArray = v8::Uint32Array::New(buffer, byte_offset, length); + CREATE_TYPED_ARRAY( + env, Uint32Array, 4, buffer, byte_offset, length, typedArray); break; case napi_float32_array: - typedArray = v8::Float32Array::New(buffer, byte_offset, length); + CREATE_TYPED_ARRAY( + env, Float32Array, 4, buffer, byte_offset, length, typedArray); break; case napi_float64_array: - typedArray = v8::Float64Array::New(buffer, byte_offset, length); + CREATE_TYPED_ARRAY( + env, Float64Array, 8, buffer, byte_offset, length, typedArray); break; default: return napi_set_last_error(env, napi_invalid_arg); diff --git a/test/addons-napi/test_typedarray/test.js b/test/addons-napi/test_typedarray/test.js index cc9d6cb6e1f5a5..ed37f3c5d4da1b 100644 --- a/test/addons-napi/test_typedarray/test.js +++ b/test/addons-napi/test_typedarray/test.js @@ -55,3 +55,21 @@ arrayTypes.forEach((currentType) => { assert.notStrictEqual(theArray, template); assert.strictEqual(theArray.buffer, buffer); }); + +arrayTypes.forEach((currentType) => { + const template = Reflect.construct(currentType, buffer); + assert.throws(() => { + test_typedarray.CreateTypedArray(template, buffer, 0, 136); + }, /Invalid typed array length/); +}); + +const nonByteArrayTypes = [ Int16Array, Uint16Array, Int32Array, Uint32Array, + Float32Array, Float64Array ]; +nonByteArrayTypes.forEach((currentType) => { + const template = Reflect.construct(currentType, buffer); + assert.throws(() => { + test_typedarray.CreateTypedArray(template, buffer, + currentType.BYTES_PER_ELEMENT + 1, 1); + console.log(`start of offset ${currentType}`); + }, /start offset of/); +}); diff --git a/test/addons-napi/test_typedarray/test_typedarray.c b/test/addons-napi/test_typedarray/test_typedarray.c index 0325faedd09f8e..82c7a5bf361d30 100644 --- a/test/addons-napi/test_typedarray/test_typedarray.c +++ b/test/addons-napi/test_typedarray/test_typedarray.c @@ -97,11 +97,11 @@ napi_value External(napi_env env, napi_callback_info info) { } napi_value CreateTypedArray(napi_env env, napi_callback_info info) { - size_t argc = 2; - napi_value args[2]; + size_t argc = 4; + napi_value args[4]; NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); - NAPI_ASSERT(env, argc == 2, "Wrong number of arguments"); + NAPI_ASSERT(env, argc == 2 || argc == 4, "Wrong number of arguments"); napi_value input_array = args[0]; napi_valuetype valuetype0; @@ -136,6 +136,28 @@ napi_value CreateTypedArray(napi_env env, napi_callback_info info) { NAPI_CALL(env, napi_get_typedarray_info( env, input_array, &type, &length, NULL, &in_array_buffer, &byte_offset)); + if (argc == 4) { + napi_valuetype valuetype2; + NAPI_CALL(env, napi_typeof(env, args[2], &valuetype2)); + + NAPI_ASSERT(env, valuetype2 == napi_number, + "Wrong type of arguments. Expects a number as third argument."); + + uint32_t uint32_length; + NAPI_CALL(env, napi_get_value_uint32(env, args[2], &uint32_length)); + length = uint32_length; + + napi_valuetype valuetype3; + NAPI_CALL(env, napi_typeof(env, args[3], &valuetype3)); + + NAPI_ASSERT(env, valuetype3 == napi_number, + "Wrong type of arguments. Expects a number as third argument."); + + uint32_t uint32_byte_offset; + NAPI_CALL(env, napi_get_value_uint32(env, args[3], &uint32_byte_offset)); + byte_offset = uint32_byte_offset; + } + napi_value output_array; NAPI_CALL(env, napi_create_typedarray( env, type, length, input_buffer, byte_offset, &output_array));