From 67c70b4464a2c7883a487f49dec44e003adc85f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Ara=C3=BAjo?= Date: Wed, 28 Oct 2026 15:21:43 -0300 Subject: [PATCH] sqlite: use 64-bit result APIs for UDF return values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JSValueToSQLiteResult() passed the size_t length of returned ArrayBuffers and ArrayBufferViews to sqlite3_result_blob(), which takes an int. Buffers larger than INT_MAX bytes were narrowed, yielding silently truncated blobs or bogus empty text values. Use sqlite3_result_blob64() and sqlite3_result_text64(), matching Statement::BindValue(), so oversized values are rejected with SQLITE_TOOBIG. Assisted-by: Claude Code Signed-off-by: Guilherme Araújo --- src/node_sqlite.cc | 5 ++-- test/parallel/test-sqlite-custom-functions.js | 24 ++++++++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index 36aab3f3a421..0f74618174b3 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -332,11 +332,12 @@ void JSValueToSQLiteResult(Isolate* isolate, sqlite3_result_double(ctx, value.As()->Value()); } else if (value->IsString()) { Utf8Value val(isolate, value.As()); - sqlite3_result_text(ctx, *val, val.length(), SQLITE_TRANSIENT); + sqlite3_result_text64( + ctx, *val, val.length(), SQLITE_TRANSIENT, SQLITE_UTF8); } else if (value->IsArrayBufferView() || value->IsArrayBuffer() || value->IsSharedArrayBuffer()) { ArrayBufferViewContents buf(value); - sqlite3_result_blob(ctx, buf.data(), buf.length(), SQLITE_TRANSIENT); + sqlite3_result_blob64(ctx, buf.data(), buf.length(), SQLITE_TRANSIENT); } else if (value->IsBigInt()) { bool lossless; int64_t as_int = value.As()->Int64Value(&lossless); diff --git a/test/parallel/test-sqlite-custom-functions.js b/test/parallel/test-sqlite-custom-functions.js index 20b16138813a..0a4b80b80ae2 100644 --- a/test/parallel/test-sqlite-custom-functions.js +++ b/test/parallel/test-sqlite-custom-functions.js @@ -1,5 +1,5 @@ 'use strict'; -const { skipIfSQLiteMissing, mustCall } = require('../common'); +const { skipIfSQLiteMissing, mustCall, enoughTestMem } = require('../common'); skipIfSQLiteMissing(); const assert = require('node:assert'); const { Database } = require('node:sqlite'); @@ -347,6 +347,28 @@ suite('Database.prototype.function()', () => { }); }); + test('throws if returned buffer exceeds INT_MAX bytes', { + skip: !enoughTestMem && 'insufficient memory', + }, (t) => { + const db = new Database(':memory:'); + for (const size of [2 ** 31, 2 ** 32 + 5]) { + let buf; + try { + buf = new ArrayBuffer(size); + } catch { + return t.skip('insufficient memory'); + } + db.function('retLargeBuffer', () => buf); + const stmt = db.prepare('SELECT retLargeBuffer() AS retLargeBuffer'); + assert.throws(() => { + stmt.get(); + }, { + code: 'ERR_SQLITE_ERROR', + message: /string or blob too big/, + }); + } + }); + test('throws on unsupported return types', () => { const db = new Database(':memory:'); db.function('retFunction', () => {