diff --git a/NEWS b/NEWS index 03b3ac758fd..11b9214b683 100644 --- a/NEWS +++ b/NEWS @@ -149,6 +149,10 @@ PHP NEWS - PCRE: . Fix UAF issues with PCRE after request shutdown. (nielsdos) +- PDO_MYSQL: + . Fixed GH-15949 (PDO_MySQL not properly quoting PDO_PARAM_LOB binary + data). (mbeccati, lcobucci) + - PDO_PGSQL: . Fixed GH-15986 (Double-free due to Pdo\Pgsql::setNoticeCallback()). (cmb, nielsdos) diff --git a/UPGRADING b/UPGRADING index 40117191257..d84c31f96ea 100644 --- a/UPGRADING +++ b/UPGRADING @@ -184,6 +184,9 @@ PHP 8.4 UPGRADE NOTES - PDO_MYSQL: . getAttribute, ATTR_AUTOCOMMIT, ATTR_EMULATE_PREPARES, MYSQL_ATTR_DIRECT_QUERY have been changed to get values as bool. + . Quoting a string with PARAM_LOB as type now outputs the string explicitly quoted + as binary. This also affects parameters bound as PARAM_LOB when + ATTR_EMULATE_PREPARES is enabled. - PDO_PGSQL: . The DSN's credentials, when set, are given priority over their PDO diff --git a/ext/pdo_mysql/mysql_driver.c b/ext/pdo_mysql/mysql_driver.c index f75959fce0c..3bdb335a4a9 100644 --- a/ext/pdo_mysql/mysql_driver.c +++ b/ext/pdo_mysql/mysql_driver.c @@ -309,23 +309,29 @@ static zend_string* mysql_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquo { pdo_mysql_db_handle *H = (pdo_mysql_db_handle *)dbh->driver_data; bool use_national_character_set = 0; + bool use_binary = 0; size_t quotedlen; - if (H->assume_national_character_set_strings) { - use_national_character_set = 1; - } - if ((paramtype & PDO_PARAM_STR_NATL) == PDO_PARAM_STR_NATL) { - use_national_character_set = 1; - } - if ((paramtype & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) { - use_national_character_set = 0; + if ((paramtype & PDO_PARAM_LOB) == PDO_PARAM_LOB) { + use_binary = 1; + } else { + if (H->assume_national_character_set_strings) { + use_national_character_set = 1; + } + if ((paramtype & PDO_PARAM_STR_NATL) == PDO_PARAM_STR_NATL) { + use_national_character_set = 1; + } + if ((paramtype & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) { + use_national_character_set = 0; + } } PDO_DBG_ENTER("mysql_handle_quoter"); PDO_DBG_INF_FMT("dbh=%p", dbh); PDO_DBG_INF_FMT("unquoted=%.*s", (int)ZSTR_LEN(unquoted), ZSTR_VAL(unquoted)); - zend_string *quoted_str = zend_string_safe_alloc(2, ZSTR_LEN(unquoted), 3 + (use_national_character_set ? 1 : 0), false); + zend_string *quoted_str = zend_string_safe_alloc(2, ZSTR_LEN(unquoted), + 3 + (use_national_character_set ? 1 : 0) + (use_binary ? 7 : 0), false); char *quoted = ZSTR_VAL(quoted_str); if (use_national_character_set) { @@ -334,6 +340,11 @@ static zend_string* mysql_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquo quoted[1] = '\''; ++quotedlen; /* N prefix */ + } else if (use_binary) { + quotedlen = mysql_real_escape_string_quote(H->server, quoted + 8, ZSTR_VAL(unquoted), ZSTR_LEN(unquoted), '\''); + memcpy(quoted, "_binary'", 8); + + quotedlen += 7; /* _binary prefix */ } else { quotedlen = mysql_real_escape_string_quote(H->server, quoted + 1, ZSTR_VAL(unquoted), ZSTR_LEN(unquoted), '\''); quoted[0] = '\''; diff --git a/ext/pdo_mysql/tests/pdo_mysql_prepare_emulated_binary.phpt b/ext/pdo_mysql/tests/pdo_mysql_prepare_emulated_binary.phpt index fe198376d61..9d291299843 100644 --- a/ext/pdo_mysql/tests/pdo_mysql_prepare_emulated_binary.phpt +++ b/ext/pdo_mysql/tests/pdo_mysql_prepare_emulated_binary.phpt @@ -13,6 +13,10 @@ MySQLPDOTest::skip(); $db = MySQLPDOTest::factory(); $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, true); + // Force the connection to utf8, which is enough to make the test fail + // MySQL 5.6+ would be required for utf8mb4 + $db->exec("SET NAMES 'utf8'"); + $content = '0191D886E6DC73E7AF1FEE7F99EC6235'; $statement = $db->prepare('SELECT HEX(?) as test'); diff --git a/ext/pdo_mysql/tests/pdo_mysql_quote_binary.phpt b/ext/pdo_mysql/tests/pdo_mysql_quote_binary.phpt new file mode 100644 index 00000000000..9c81f084909 --- /dev/null +++ b/ext/pdo_mysql/tests/pdo_mysql_quote_binary.phpt @@ -0,0 +1,28 @@ +--TEST-- +MySQL PDO->quote(), properly handle binary data +--EXTENSIONS-- +pdo_mysql +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_EMULATE_PREPARES, true); + + // Force the connection to utf8, which is enough to make the test fail + // MySQL 5.6+ would be required for utf8mb4 + $db->exec("SET NAMES 'utf8'"); + + $content = "\xC3\xA1\xC3"; + $quoted = $db->quote($content, PDO::PARAM_LOB); + + var_dump($quoted); + var_dump($db->query("SELECT HEX({$quoted})")->fetch(PDO::FETCH_NUM)[0]); +?> +--EXPECTF-- +string(%d) "_binary'%s'" +string(6) "C3A1C3"