Commit 9c69d7c6a72 for php
commit 9c69d7c6a7254fad4c405872d75b3cfe3531a3b1
Author: Michael Voříšek <mvorisek@mvorisek.cz>
Date: Sat Oct 31 12:59:26 2026 +0000
Fix GH-25021: SQLite createFunction corrupts large integers
Fix large int corruption to/from pdo_sqlite UDF.
Close GH-25058
diff --git a/NEWS b/NEWS
index 3d05c595e6f..f9717a6c7da 100644
--- a/NEWS
+++ b/NEWS
@@ -213,6 +213,10 @@ PHP NEWS
. Fixed bug GH-25006 (getColumnMeta() reads out of bounds for an
out-of-range column index). (Ilia Alshanetsky)
+- PDO_SQLite:
+ . Fixed bug GH-25021 (SQLite createFunction corrupts large integers).
+ (mvorisek)
+
- Phar:
. Fixed bug GH-8469 (Phar::decompressFiles() and Phar::compressFiles() retain
compressed contents after changing compression). (Raphael Paez)
@@ -256,6 +260,10 @@ PHP NEWS
. Fixed bug GH-24748 (socket_set_option() crashes with declared object
properties). (Lazizbek Ergashev)
+- SQLite3:
+ . Fixed bug GH-25021 (SQLite createFunction corrupts large integers).
+ (mvorisek)
+
- Standard:
. Fixed memory leaks in checkdnsrr() and dns_check_record() on Windows.
(Ilia Alshanetsky)
diff --git a/ext/pdo_sqlite/sqlite_driver.c b/ext/pdo_sqlite/sqlite_driver.c
index e7eb710030c..c787059ff66 100644
--- a/ext/pdo_sqlite/sqlite_driver.c
+++ b/ext/pdo_sqlite/sqlite_driver.c
@@ -355,7 +355,11 @@ static int do_callback(zend_fcall_info_cache *fcc, int argc, sqlite3_value **arg
/* get the value */
switch (sqlite3_value_type(argv[i])) {
case SQLITE_INTEGER:
+#ifdef ZEND_ENABLE_ZVAL_LONG64
+ ZVAL_LONG(&zargs[i + is_agg], sqlite3_value_int64(argv[i]));
+#else
ZVAL_LONG(&zargs[i + is_agg], sqlite3_value_int(argv[i]));
+#endif
break;
case SQLITE_FLOAT:
@@ -393,7 +397,11 @@ static int do_callback(zend_fcall_info_cache *fcc, int argc, sqlite3_value **arg
if (!Z_ISUNDEF(retval)) {
switch (Z_TYPE(retval)) {
case IS_LONG:
+#ifdef ZEND_ENABLE_ZVAL_LONG64
+ sqlite3_result_int64(context, Z_LVAL(retval));
+#else
sqlite3_result_int(context, Z_LVAL(retval));
+#endif
break;
case IS_NULL:
diff --git a/ext/pdo_sqlite/sqlite_statement.c b/ext/pdo_sqlite/sqlite_statement.c
index c0e32745023..2d79a6d0fc1 100644
--- a/ext/pdo_sqlite/sqlite_statement.c
+++ b/ext/pdo_sqlite/sqlite_statement.c
@@ -115,7 +115,7 @@ static int pdo_sqlite_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_d
}
} else {
convert_to_long(parameter);
-#if ZEND_LONG_MAX > 2147483647
+#ifdef ZEND_ENABLE_ZVAL_LONG64
if (SQLITE_OK == sqlite3_bind_int64(S->stmt, param->paramno + 1, Z_LVAL_P(parameter))) {
return 1;
}
diff --git a/ext/pdo_sqlite/tests/gh25021-udf.phpt b/ext/pdo_sqlite/tests/gh25021-udf.phpt
new file mode 100644
index 00000000000..3a5e94820af
--- /dev/null
+++ b/ext/pdo_sqlite/tests/gh25021-udf.phpt
@@ -0,0 +1,28 @@
+--TEST--
+GH-25021 (UDF isn't using sqlite3_*_int64 API)
+--EXTENSIONS--
+pdo_sqlite
+--FILE--
+<?php
+
+$conn = new PDO('sqlite::memory:');
+
+$conn->sqliteCreateFunction('my_udf_pass_int', static fn(int $v): int => $v);
+
+foreach ([
+ 10,
+ PHP_INT_MIN,
+ PHP_INT_MAX,
+] as $v) {
+ $stmt = $conn->prepare('select my_udf_pass_int(:v)');
+ $stmt->bindValue(':v', $v, PDO::PARAM_INT);
+ $stmt->execute();
+ $result = $stmt->fetch();
+
+ var_dump($result[0] === $v);
+}
+?>
+--EXPECT--
+bool(true)
+bool(true)
+bool(true)
diff --git a/ext/sqlite3/sqlite3.c b/ext/sqlite3/sqlite3.c
index 93e50b97c7f..19a226c1dca 100644
--- a/ext/sqlite3/sqlite3.c
+++ b/ext/sqlite3/sqlite3.c
@@ -638,7 +638,7 @@ static void sqlite_value_to_zval(php_sqlite3_db_object *db_obj, sqlite3_stmt *st
switch (sqlite3_column_type(stmt, column)) {
case SQLITE_INTEGER:
val = sqlite3_column_int64(stmt, column);
-#if LONG_MAX <= 2147483647
+#ifndef ZEND_ENABLE_ZVAL_LONG64
if (val > ZEND_LONG_MAX || val < ZEND_LONG_MIN) {
const char *text = (const char *) sqlite3_column_text(stmt, column);
if (UNEXPECTED(text == NULL)) {
@@ -650,7 +650,7 @@ static void sqlite_value_to_zval(php_sqlite3_db_object *db_obj, sqlite3_stmt *st
} else {
#endif
ZVAL_LONG(data, (zend_long) val);
-#if LONG_MAX <= 2147483647
+#ifndef ZEND_ENABLE_ZVAL_LONG64
}
#endif
break;
@@ -811,7 +811,7 @@ static int sqlite3_do_callback(zend_fcall_info_cache *fcc, uint32_t argc, sqlite
for (i = 0; i < argc; i++) {
switch (sqlite3_value_type(argv[i])) {
case SQLITE_INTEGER:
-#if ZEND_LONG_MAX > 2147483647
+#ifdef ZEND_ENABLE_ZVAL_LONG64
ZVAL_LONG(&zargs[i + is_agg], sqlite3_value_int64(argv[i]));
#else
ZVAL_LONG(&zargs[i + is_agg], sqlite3_value_int(argv[i]));
@@ -862,7 +862,7 @@ static int sqlite3_do_callback(zend_fcall_info_cache *fcc, uint32_t argc, sqlite
if (!Z_ISUNDEF(retval)) {
switch (Z_TYPE(retval)) {
case IS_LONG:
-#if ZEND_LONG_MAX > 2147483647
+#ifdef ZEND_ENABLE_ZVAL_LONG64
sqlite3_result_int64(context, Z_LVAL(retval));
#else
sqlite3_result_int(context, Z_LVAL(retval));
@@ -1590,7 +1590,7 @@ static int php_sqlite3_bind_params(php_sqlite3_stmt *stmt_obj) /* {{{ */
switch (param->type) {
case SQLITE_INTEGER:
convert_to_long(parameter);
-#if ZEND_LONG_MAX > 2147483647
+#ifdef ZEND_ENABLE_ZVAL_LONG64
return_code = sqlite3_bind_int64(stmt_obj->stmt, param->param_number, Z_LVAL_P(parameter));
#else
return_code = sqlite3_bind_int(stmt_obj->stmt, param->param_number, Z_LVAL_P(parameter));
diff --git a/ext/sqlite3/tests/gh25021-udf.phpt b/ext/sqlite3/tests/gh25021-udf.phpt
new file mode 100644
index 00000000000..5e9ad6f0ca6
--- /dev/null
+++ b/ext/sqlite3/tests/gh25021-udf.phpt
@@ -0,0 +1,27 @@
+--TEST--
+GH-25021 (UDF isn't using sqlite3_*_int64 API)
+--EXTENSIONS--
+sqlite3
+--FILE--
+<?php
+
+$conn = new SQLite3(':memory:');
+
+$conn->createFunction('my_udf_pass_int', static fn(int $v): int => $v);
+
+foreach ([
+ 10,
+ PHP_INT_MIN,
+ PHP_INT_MAX,
+] as $v) {
+ $stmt = $conn->prepare('select my_udf_pass_int(:v)');
+ $stmt->bindValue(':v', $v, SQLITE3_INTEGER);
+ $result = $stmt->execute()->fetchArray();
+
+ var_dump($result[0] === $v);
+}
+?>
+--EXPECT--
+bool(true)
+bool(true)
+bool(true)