From db80e6ebb8990e4ce4b6d1f1e77b8bae278d100f Mon Sep 17 00:00:00 2001 From: zhenweijin Date: Wed, 21 Feb 2024 17:37:06 +0800 Subject: [PATCH] buffer: make `buflen` in integer range --- src/string_bytes.cc | 35 +++++++++++------- test/pummel/test-buffer-large-size.js | 51 +++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 12 deletions(-) create mode 100644 test/pummel/test-buffer-large-size.js diff --git a/src/string_bytes.cc b/src/string_bytes.cc index 3e2b29005a2012..010ced54038c91 100644 --- a/src/string_bytes.cc +++ b/src/string_bytes.cc @@ -200,6 +200,13 @@ MaybeLocal ExternTwoByteString::NewSimpleFromCopy(Isolate* isolate, } // anonymous namespace +static size_t keep_buflen_in_range(size_t len) { + if (len > static_cast(std::numeric_limits::max())) { + return static_cast(std::numeric_limits::max()); + } + return len; +} + size_t StringBytes::WriteUCS2( Isolate* isolate, char* buf, size_t buflen, Local str, int flags) { uint16_t* const dst = reinterpret_cast(buf); @@ -245,7 +252,7 @@ size_t StringBytes::Write(Isolate* isolate, enum encoding encoding) { HandleScope scope(isolate); size_t nbytes; - + buflen = keep_buflen_in_range(buflen); CHECK(val->IsString() == true); Local str = val.As(); @@ -545,6 +552,7 @@ MaybeLocal StringBytes::Encode(Isolate* isolate, } case ASCII: + buflen = keep_buflen_in_range(buflen); if (simdutf::validate_ascii_with_errors(buf, buflen).error) { // The input contains non-ASCII bytes. char* out = node::UncheckedMalloc(buflen); @@ -558,23 +566,23 @@ MaybeLocal StringBytes::Encode(Isolate* isolate, return ExternOneByteString::NewFromCopy(isolate, buf, buflen, error); } - case UTF8: - { - val = String::NewFromUtf8(isolate, - buf, - v8::NewStringType::kNormal, - buflen); - Local str; - if (!val.ToLocal(&str)) { - *error = node::ERR_STRING_TOO_LONG(isolate); - } - return str; + case UTF8: { + buflen = keep_buflen_in_range(buflen); + val = + String::NewFromUtf8(isolate, buf, v8::NewStringType::kNormal, buflen); + Local str; + if (!val.ToLocal(&str)) { + *error = node::ERR_STRING_TOO_LONG(isolate); } + return str; + } case LATIN1: + buflen = keep_buflen_in_range(buflen); return ExternOneByteString::NewFromCopy(isolate, buf, buflen, error); case BASE64: { + buflen = keep_buflen_in_range(buflen); size_t dlen = simdutf::base64_length_from_binary(buflen); char* dst = node::UncheckedMalloc(dlen); if (dst == nullptr) { @@ -589,6 +597,7 @@ MaybeLocal StringBytes::Encode(Isolate* isolate, } case BASE64URL: { + buflen = keep_buflen_in_range(buflen); size_t dlen = simdutf::base64_length_from_binary(buflen, simdutf::base64_url); char* dst = node::UncheckedMalloc(dlen); @@ -605,6 +614,7 @@ MaybeLocal StringBytes::Encode(Isolate* isolate, } case HEX: { + buflen = keep_buflen_in_range(buflen); size_t dlen = buflen * 2; char* dst = node::UncheckedMalloc(dlen); if (dst == nullptr) { @@ -618,6 +628,7 @@ MaybeLocal StringBytes::Encode(Isolate* isolate, } case UCS2: { + buflen = keep_buflen_in_range(buflen); size_t str_len = buflen / 2; if constexpr (IsBigEndian()) { uint16_t* dst = node::UncheckedMalloc(str_len); diff --git a/test/pummel/test-buffer-large-size.js b/test/pummel/test-buffer-large-size.js new file mode 100644 index 00000000000000..fc89d107b4d314 --- /dev/null +++ b/test/pummel/test-buffer-large-size.js @@ -0,0 +1,51 @@ +'use strict'; +const common = require('../common'); + +// Buffer with size > INT32_MAX +common.skipIf32Bits(); + +// Test Buffer size larger than integer range +const { test } = require('node:test'); +const assert = require('assert'); +const { + SlowBuffer, +} = require('buffer'); +const kStringMaxLength = require('buffer').constants.MAX_STRING_LENGTH; + +const stringTooLongError = { + message: `Cannot create a string longer than 0x${kStringMaxLength.toString(16)}` + + ' characters', + code: 'ERR_STRING_TOO_LONG', + name: 'Error', +}; + +const size = 2 ** 31; + +// Test Buffer.toString +test('Buffer.toString with too long size', () => { + try { + assert.throws(() => SlowBuffer(size).toString('utf8'), stringTooLongError); + assert.throws(() => Buffer.alloc(size).toString('utf8'), stringTooLongError); + assert.throws(() => Buffer.allocUnsafe(size).toString('utf8'), stringTooLongError); + assert.throws(() => Buffer.allocUnsafeSlow(size).toString('utf8'), stringTooLongError); + } catch (e) { + if (e.code !== 'ERR_MEMORY_ALLOCATION_FAILED') { + throw e; + } + common.skip('insufficient space for Buffer.alloc'); + } +}); + +// Test Buffer.write +test('Buffer.write with too long size', () => { + try { + const buf = Buffer.alloc(size); + assert.strictEqual(buf.write('a', 2, kStringMaxLength), 1); + assert.strictEqual(buf.write('a', 2, size), 1); + } catch (e) { + if (e.code !== 'ERR_MEMORY_ALLOCATION_FAILED') { + throw e; + } + common.skip('insufficient space for Buffer.alloc'); + } +});