From 2e85a38a879745b73fd7e439bda3553199b32c54 Mon Sep 17 00:00:00 2001 From: lazerg Date: Sun, 4 Oct 2026 08:57:53 +0500 Subject: [PATCH 1/2] sqlite: avoid abort on oversized SQL text Signed-off-by: lazerg Assisted-by: Claude Code --- src/node_sqlite.cc | 29 ++++++++++++++++++++------ test/parallel/test-sqlite-statement.js | 21 +++++++++++++++++++ 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index 9ebc0cb97316..5bc46cb513b2 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -3713,7 +3713,11 @@ int Database::TraceCallback(unsigned int type, char* expanded = sqlite3_expanded_sql(static_cast(p)); Local sql_string; if (expanded != nullptr) { - bool ok = String::NewFromUtf8(isolate, expanded).ToLocal(&sql_string); + bool ok = String::NewFromUtf8(isolate, + expanded, + NewStringType::kNormal, + static_cast(strlen(expanded))) + .ToLocal(&sql_string); sqlite3_free(expanded); if (!ok) { return 0; @@ -3722,7 +3726,11 @@ int Database::TraceCallback(unsigned int type, // Fallback to source SQL if expanded is unavailable const char* source = sqlite3_sql(static_cast(p)); if (source == nullptr || - !String::NewFromUtf8(isolate, source).ToLocal(&sql_string)) { + !String::NewFromUtf8(isolate, + source, + NewStringType::kNormal, + static_cast(strlen(source))) + .ToLocal(&sql_string)) { return 0; } } @@ -4004,8 +4012,16 @@ MaybeLocal Statement::ColumnNameToName(const int column) { return MaybeLocal(); } - return String::NewFromUtf8( - env()->isolate(), col_name, NewStringType::kInternalized) + const size_t len = strlen(col_name); + if (len > static_cast(String::kMaxLength)) { + env()->isolate()->ThrowException(ERR_STRING_TOO_LONG(env()->isolate())); + return MaybeLocal(); + } + + return String::NewFromUtf8(env()->isolate(), + col_name, + NewStringType::kInternalized, + static_cast(len)) .As(); } @@ -4414,7 +4430,8 @@ void Statement::SourceSQLGetter(const FunctionCallbackInfo& args) { THROW_AND_RETURN_ON_BAD_STATE( env, stmt->IsFinalized(), "statement has been finalized"); Local sql; - if (!String::NewFromUtf8(env->isolate(), sqlite3_sql(stmt->statement_.get())) + if (!Utf8StringMaybeOneByte(env->isolate(), + sqlite3_sql(stmt->statement_.get())) .ToLocal(&sql)) { return; } @@ -4434,7 +4451,7 @@ void Statement::ExpandedSQLGetter(const FunctionCallbackInfo& args) { return THROW_ERR_SQLITE_ERROR( env->isolate(), "Expanded SQL text would exceed configured limits"); } - auto maybe_expanded = String::NewFromUtf8(env->isolate(), expanded); + auto maybe_expanded = Utf8StringMaybeOneByte(env->isolate(), expanded); sqlite3_free(expanded); Local result; if (!maybe_expanded.ToLocal(&result)) { diff --git a/test/parallel/test-sqlite-statement.js b/test/parallel/test-sqlite-statement.js index d06b9018bf8e..2cd54ea2c2e5 100644 --- a/test/parallel/test-sqlite-statement.js +++ b/test/parallel/test-sqlite-statement.js @@ -4,6 +4,7 @@ const { enoughTestMem, skipIfSQLiteMissing } = require('../common'); skipIfSQLiteMissing(); const { Database, Statement } = require('node:sqlite'); const { constants } = require('node:buffer'); +const dc = require('node:diagnostics_channel'); const { suite, test } = require('node:test'); suite('Statement() constructor', () => { @@ -1469,4 +1470,24 @@ suite('values larger than the maximum string length', { skip: !enoughTestMem }, using stmt = db.prepare('SELECT count(*) AS count FROM data'); t.assert.deepStrictEqual(stmt.get(), { __proto__: null, count: 0 }); }); + + test('expandedSQL throws instead of aborting', (t) => { + using db = new Database(':memory:'); + using stmt = db.prepare('SELECT ?'); + stmt.run(Buffer.alloc(blobSize)); + t.assert.throws(() => stmt.expandedSQL, tooLong); + }); + + test('sqlite.db.query subscribers do not affect the statement', (t) => { + const handler = t.mock.fn(); + dc.subscribe('sqlite.db.query', handler); + t.after(() => dc.unsubscribe('sqlite.db.query', handler)); + using db = new Database(':memory:'); + using stmt = db.prepare('SELECT length(?) AS len'); + t.assert.deepStrictEqual( + stmt.get(Buffer.alloc(blobSize)), + { __proto__: null, len: blobSize }, + ); + t.assert.strictEqual(handler.mock.callCount(), 0); + }); }); From ad58ade4d96e01c7b0a6146f5b44c9963d20cf85 Mon Sep 17 00:00:00 2001 From: lazerg Date: Sun, 4 Oct 2026 10:08:30 +0500 Subject: [PATCH 2/2] sqlite: reuse Utf8StringMaybeOneByte for column names Signed-off-by: lazerg Assisted-by: Claude Code --- src/node_sqlite.cc | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index 5bc46cb513b2..ab96385ad049 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -71,8 +71,10 @@ using v8::TryCatch; using v8::Uint8Array; using v8::Value; -inline MaybeLocal Utf8StringMaybeOneByte(Isolate* isolate, - std::string_view input) { +inline MaybeLocal Utf8StringMaybeOneByte( + Isolate* isolate, + std::string_view input, + NewStringType type = NewStringType::kNormal) { // SQLITE_MAX_LENGTH exceeds String::kMaxLength, and V8 returns an empty // handle without throwing. Raise the error here or the value is dropped. if (input.size() > static_cast(String::kMaxLength)) [[unlikely]] { @@ -83,13 +85,9 @@ inline MaybeLocal Utf8StringMaybeOneByte(Isolate* isolate, const int len = static_cast(input.size()); if (simdutf::validate_ascii(input.data(), input.size())) { return String::NewFromOneByte( - isolate, - reinterpret_cast(input.data()), - NewStringType::kNormal, - len); + isolate, reinterpret_cast(input.data()), type, len); } - return String::NewFromUtf8( - isolate, input.data(), NewStringType::kNormal, len); + return String::NewFromUtf8(isolate, input.data(), type, len); } BindingData::BindingData(Realm* realm, Local wrap) @@ -4012,16 +4010,8 @@ MaybeLocal Statement::ColumnNameToName(const int column) { return MaybeLocal(); } - const size_t len = strlen(col_name); - if (len > static_cast(String::kMaxLength)) { - env()->isolate()->ThrowException(ERR_STRING_TOO_LONG(env()->isolate())); - return MaybeLocal(); - } - - return String::NewFromUtf8(env()->isolate(), - col_name, - NewStringType::kInternalized, - static_cast(len)) + return Utf8StringMaybeOneByte( + env()->isolate(), col_name, NewStringType::kInternalized) .As(); }