Commit 128f247ea4c for nodejs
commit 128f247ea4cb52da405f6cc35dec46c088be8af3
Author: Guilherme Araújo <arauujogui@gmail.com>
Date: Fri Nov 6 08:53:45 2026 -0300
sqlite: use 64-bit result APIs for UDF return values
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 <arauujogui@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/67089
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc
index 2bf67b97689..77617e011fa 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<Number>()->Value());
} else if (value->IsString()) {
Utf8Value val(isolate, value.As<String>());
- 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<uint8_t> 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<BigInt>()->Int64Value(&lossless);
diff --git a/test/parallel/test-sqlite-custom-functions.js b/test/parallel/test-sqlite-custom-functions.js
index 20b16138813..0a4b80b80ae 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', () => {