Commit f9b6254375f for php.net
commit f9b6254375f4da163439daee1a564798250535ac
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date: Thu Jul 16 12:41:51 2026 -0400
ext/odbc: do not mutate the caller's DSN string
odbc_sqlconnect() stripped a trailing semicolon by writing a NUL into
the Z_PARAM_STRING buffer, which is a zend_string. Shared variables,
interned literals, and const values used as the DSN were permanently
altered (strlen unchanged, last byte became NUL). Compute the base
length instead and pass it to spprintf with %.*s, matching the
non-mutating approach used when assembling UID/PWD into the connect
string.
Closes GH-22776
diff --git a/ext/odbc/php_odbc.c b/ext/odbc/php_odbc.c
index 16617078d3c..8b78308f942 100644
--- a/ext/odbc/php_odbc.c
+++ b/ext/odbc/php_odbc.c
@@ -2217,9 +2217,9 @@ bool odbc_sqlconnect(zval *zv, char *db, char *uid, char *pwd, int cur_opt, bool
/* Force UID and PWD to be set in the DSN */
if (use_uid_arg || use_pwd_arg) {
- db_end--;
- if ((unsigned char)*(db_end) == ';') {
- *db_end = '\0';
+ size_t base_len = db_len;
+ if (base_len > 0 && db[base_len - 1] == ';') {
+ base_len--;
}
char *uid_quoted = NULL, *pwd_quoted = NULL;
@@ -2235,7 +2235,7 @@ bool odbc_sqlconnect(zval *zv, char *db, char *uid, char *pwd, int cur_opt, bool
}
if (!use_pwd_arg) {
- spprintf(&ldb, 0, "%s;UID=%s;", db, uid_quoted);
+ spprintf(&ldb, 0, "%.*s;UID=%s;", (int) base_len, db, uid_quoted);
}
}
@@ -2250,12 +2250,12 @@ bool odbc_sqlconnect(zval *zv, char *db, char *uid, char *pwd, int cur_opt, bool
}
if (!use_uid_arg) {
- spprintf(&ldb, 0, "%s;PWD=%s;", db, pwd_quoted);
+ spprintf(&ldb, 0, "%.*s;PWD=%s;", (int) base_len, db, pwd_quoted);
}
}
if (use_uid_arg && use_pwd_arg) {
- spprintf(&ldb, 0, "%s;UID=%s;PWD=%s;", db, uid_quoted, pwd_quoted);
+ spprintf(&ldb, 0, "%.*s;UID=%s;PWD=%s;", (int) base_len, db, uid_quoted, pwd_quoted);
}
if (uid_quoted && should_quote_uid) {
diff --git a/ext/odbc/tests/gh_odbc_dsn_immutability.phpt b/ext/odbc/tests/gh_odbc_dsn_immutability.phpt
new file mode 100644
index 00000000000..e3023b387c7
--- /dev/null
+++ b/ext/odbc/tests/gh_odbc_dsn_immutability.phpt
@@ -0,0 +1,20 @@
+--TEST--
+odbc_connect() must not mutate the caller's DSN string
+--EXTENSIONS--
+odbc
+--FILE--
+<?php
+/* The '=' selects the connection-string form and the trailing ';' is what the
+ * UID/PWD assembly used to overwrite with a NUL, in the caller's own buffer. */
+$dsn = 'Driver=DoesNotExist;Database=x;SS001;';
+const DSN_CONST = 'Driver=DoesNotExist;Database=x;SS001_CONST;';
+
+@odbc_connect($dsn, 'u', 'p');
+@odbc_connect(DSN_CONST, 'u', 'p');
+
+var_dump($dsn);
+var_dump(DSN_CONST);
+?>
+--EXPECT--
+string(37) "Driver=DoesNotExist;Database=x;SS001;"
+string(43) "Driver=DoesNotExist;Database=x;SS001_CONST;"