File php8-CVE-2025-1735.patch of Package php8.39647
From a2cdff5583ad2bfe9a27fba71e2b1fc423296dc0 Mon Sep 17 00:00:00 2001
From: Jakub Zelenka <bukka@php.net>
Date: Tue, 4 Mar 2025 17:23:01 +0100
Subject: [PATCH] Fix GHSA-hrwm-9436-5mv3: pgsql escaping no error checks
This adds error checks for escape function is pgsql and pdo_pgsql
extensions. It prevents possibility of storing not properly escaped
data which could potentially lead to some security issues.
---
ext/pdo_pgsql/pgsql_driver.c | 10 +-
ext/pdo_pgsql/tests/ghsa-hrwm-9436-5mv3.phpt | 24 ++++
ext/pgsql/pgsql.c | 126 ++++++++++++++++---
ext/pgsql/tests/ghsa-hrwm-9436-5mv3.phpt | 64 ++++++++++
4 files changed, 203 insertions(+), 21 deletions(-)
create mode 100644 ext/pdo_pgsql/tests/ghsa-hrwm-9436-5mv3.phpt
create mode 100644 ext/pgsql/tests/ghsa-hrwm-9436-5mv3.phpt
Index: php-8.0.30/ext/pdo_pgsql/pgsql_driver.c
===================================================================
--- php-8.0.30.orig/ext/pdo_pgsql/pgsql_driver.c
+++ php-8.0.30/ext/pdo_pgsql/pgsql_driver.c
@@ -354,11 +354,15 @@ static int pgsql_handle_quoter(pdo_dbh_t
unsigned char *escaped;
pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
size_t tmp_len;
+ int err;
switch (paramtype) {
case PDO_PARAM_LOB:
/* escapedlen returned by PQescapeBytea() accounts for trailing 0 */
escaped = PQescapeByteaConn(H->server, (unsigned char *)unquoted, unquotedlen, &tmp_len);
+ if (escaped == NULL) {
+ return NULL;
+ }
*quotedlen = tmp_len + 1;
*quoted = emalloc(*quotedlen + 1);
memcpy((*quoted)+1, escaped, *quotedlen-2);
@@ -370,7 +374,11 @@ static int pgsql_handle_quoter(pdo_dbh_t
default:
*quoted = safe_emalloc(2, unquotedlen, 3);
(*quoted)[0] = '\'';
- *quotedlen = PQescapeStringConn(H->server, *quoted + 1, unquoted, unquotedlen, NULL);
+ *quotedlen = PQescapeStringConn(H->server, *quoted + 1, unquoted, unquotedlen, &err);
+ if (err) {
+ efree(quoted);
+ return NULL;
+ }
(*quoted)[*quotedlen + 1] = '\'';
(*quoted)[*quotedlen + 2] = '\0';
*quotedlen += 2;
Index: php-8.0.30/ext/pgsql/pgsql.c
===================================================================
--- php-8.0.30.orig/ext/pgsql/pgsql.c
+++ php-8.0.30/ext/pgsql/pgsql.c
@@ -3298,10 +3298,17 @@ PHP_FUNCTION(pg_escape_string)
to = zend_string_safe_alloc(ZSTR_LEN(from), 2, 0, 0);
if (link) {
+ int err;
if ((pgsql = (PGconn *)zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink)) == NULL) {
RETURN_THROWS();
}
- ZSTR_LEN(to) = PQescapeStringConn(pgsql, ZSTR_VAL(to), ZSTR_VAL(from), ZSTR_LEN(from), NULL);
+ ZSTR_LEN(to) = PQescapeStringConn(pgsql, ZSTR_VAL(to), ZSTR_VAL(from), ZSTR_LEN(from), &err);
+ if (err) {
+ zend_argument_value_error(ZEND_NUM_ARGS(), "Escaping string failed");
+ zend_string_efree(to);
+ RETURN_THROWS();
+ }
+
} else
{
ZSTR_LEN(to) = PQescapeString(ZSTR_VAL(to), ZSTR_VAL(from), ZSTR_LEN(from));
@@ -3345,6 +3352,11 @@ PHP_FUNCTION(pg_escape_bytea)
} else
to = (char *)PQescapeBytea((unsigned char*)from, from_len, &to_len);
+ if (to == NULL) {
+ zend_argument_value_error(ZEND_NUM_ARGS(), "Escape failure");
+ RETURN_THROWS();
+ }
+
RETVAL_STRINGL(to, to_len-1); /* to_len includes additional '\0' */
PQfreemem(to);
}
@@ -4251,7 +4263,7 @@ PHP_PGSQL_API int php_pgsql_meta_data(PG
char *escaped;
smart_str querystr = {0};
size_t new_len;
- int i, num_rows;
+ int i, num_rows, err;
zval elem;
ZEND_ASSERT(*table_name);
@@ -4290,7 +4302,14 @@ PHP_PGSQL_API int php_pgsql_meta_data(PG
"WHERE a.attnum > 0 AND c.relname = '");
}
escaped = (char *)safe_emalloc(strlen(tmp_name2), 2, 1);
- new_len = PQescapeStringConn(pg_link, escaped, tmp_name2, strlen(tmp_name2), NULL);
+ new_len = PQescapeStringConn(pg_link, escaped, tmp_name2, strlen(tmp_name2), &err);
+ if (err) {
+ php_error_docref(NULL, E_WARNING, "Escaping table name '%s' failed", table_name);
+ efree(src);
+ efree(escaped);
+ smart_str_free(&querystr);
+ return FAILURE;
+ }
if (new_len) {
smart_str_appendl(&querystr, escaped, new_len);
}
@@ -4298,7 +4317,14 @@ PHP_PGSQL_API int php_pgsql_meta_data(PG
smart_str_appends(&querystr, "' AND n.nspname = '");
escaped = (char *)safe_emalloc(strlen(tmp_name), 2, 1);
- new_len = PQescapeStringConn(pg_link, escaped, tmp_name, strlen(tmp_name), NULL);
+ new_len = PQescapeStringConn(pg_link, escaped, tmp_name, strlen(tmp_name), &err);
+ if (err) {
+ php_error_docref(NULL, E_WARNING, "Escaping table namespace '%s' failed", table_name);
+ efree(src);
+ efree(escaped);
+ smart_str_free(&querystr);
+ return FAILURE;
+ }
if (new_len) {
smart_str_appendl(&querystr, escaped, new_len);
}
@@ -4575,7 +4601,7 @@ PHP_PGSQL_API int php_pgsql_convert(PGco
{
zend_string *field = NULL;
zval meta, *def, *type, *not_null, *has_default, *is_enum, *val, new_val;
- int err = 0, skip_field;
+ int err = 0, escape_err = 0, skip_field;
php_pgsql_data_type data_type;
ZEND_ASSERT(pg_link != NULL);
@@ -4829,10 +4855,16 @@ PHP_PGSQL_API int php_pgsql_convert(PGco
/* PostgreSQL ignores \0 */
str = zend_string_alloc(Z_STRLEN_P(val) * 2, 0);
/* better to use PGSQLescapeLiteral since PGescapeStringConn does not handle special \ */
- ZSTR_LEN(str) = PQescapeStringConn(pg_link, ZSTR_VAL(str), Z_STRVAL_P(val), Z_STRLEN_P(val), NULL);
- str = zend_string_truncate(str, ZSTR_LEN(str), 0);
- ZVAL_NEW_STR(&new_val, str);
- php_pgsql_add_quotes(&new_val, 1);
+ ZSTR_LEN(str) = PQescapeStringConn(pg_link, ZSTR_VAL(str),
+ Z_STRVAL_P(val), Z_STRLEN_P(val), &escape_err);
+ if (escape_err) {
+ err = 1;
+ } else {
+ str = zend_string_truncate(str, ZSTR_LEN(str), 0);
+ ZVAL_NEW_STR(&new_val, str);
+ php_pgsql_add_quotes(&new_val, 1);
+ }
+
}
break;
@@ -4854,7 +4886,15 @@ PHP_PGSQL_API int php_pgsql_convert(PGco
}
PGSQL_CONV_CHECK_IGNORE();
if (err) {
- php_error_docref(NULL, E_NOTICE, "Expects NULL, string, long or double value for PostgreSQL '%s' (%s)", Z_STRVAL_P(type), ZSTR_VAL(field));
+ if (escape_err) {
+ php_error_docref(NULL, E_NOTICE,
+ "String value escaping failed for PostgreSQL '%s' (%s)",
+ Z_STRVAL_P(type), ZSTR_VAL(field));
+ } else {
+ php_error_docref(NULL, E_NOTICE,
+ "Expects NULL, string, long or double value for PostgreSQL '%s' (%s)",
+ Z_STRVAL_P(type), ZSTR_VAL(field));
+ }
}
break;
@@ -5129,6 +5169,12 @@ PHP_PGSQL_API int php_pgsql_convert(PGco
size_t to_len;
smart_str s = {0};
tmp = PQescapeByteaConn(pg_link, (unsigned char *)Z_STRVAL_P(val), Z_STRLEN_P(val), &to_len);
+ if (tmp == NULL) {
+ php_error_docref(NULL, E_NOTICE, "Escaping value failed for %s field (%s)", Z_STRVAL_P(type), ZSTR_VAL(field));
+ err = 1;
+ break;
+ }
+
ZVAL_STRINGL(&new_val, (char *)tmp, to_len - 1); /* PQescapeBytea's to_len includes additional '\0' */
PQfreemem(tmp);
php_pgsql_add_quotes(&new_val, 1);
@@ -5210,6 +5256,12 @@ PHP_PGSQL_API int php_pgsql_convert(PGco
zend_hash_update(Z_ARRVAL_P(result), field, &new_val);
} else {
char *escaped = PQescapeIdentifier(pg_link, ZSTR_VAL(field), ZSTR_LEN(field));
+ if (escaped == NULL) {
+ /* This cannot fail because of invalid string but only due to failed memory allocation */
+ php_error_docref(NULL, E_NOTICE, "Escaping field '%s' failed", ZSTR_VAL(field));
+ err = 1;
+ break;
+ }
add_assoc_zval(result, escaped, &new_val);
PQfreemem(escaped);
}
@@ -5290,7 +5342,7 @@ static int do_exec(smart_str *querystr,
}
/* }}} */
-static inline void build_tablename(smart_str *querystr, PGconn *pg_link, const char *table) /* {{{ */
+static inline zend_result build_tablename(smart_str *querystr, PGconn *pg_link, const char *table) /* {{{ */
{
size_t table_len = strlen(table);
@@ -5301,6 +5353,10 @@ static inline void build_tablename(smart
smart_str_appendl(querystr, table, len);
} else {
char *escaped = PQescapeIdentifier(pg_link, table, len);
+ if (escaped == NULL) {
+ php_error_docref(NULL, E_NOTICE, "Failed to escape table name '%s'", table);
+ return FAILURE;
+ }
smart_str_appends(querystr, escaped);
PQfreemem(escaped);
}
@@ -5313,11 +5369,17 @@ static inline void build_tablename(smart
smart_str_appendl(querystr, after_dot, len);
} else {
char *escaped = PQescapeIdentifier(pg_link, after_dot, len);
+ if (escaped == NULL) {
+ php_error_docref(NULL, E_NOTICE, "Failed to escape table name '%s'", table);
+ return FAILURE;
+ }
smart_str_appendc(querystr, '.');
smart_str_appends(querystr, escaped);
PQfreemem(escaped);
}
}
+
+ return SUCCESS;
}
/* }}} */
@@ -5338,7 +5400,9 @@ PHP_PGSQL_API int php_pgsql_insert(PGcon
ZVAL_UNDEF(&converted);
if (zend_hash_num_elements(Z_ARRVAL_P(var_array)) == 0) {
smart_str_appends(&querystr, "INSERT INTO ");
- build_tablename(&querystr, pg_link, table);
+ if (build_tablename(&querystr, pg_link, table) == FAILURE) {
+ goto cleanup;
+ }
smart_str_appends(&querystr, " DEFAULT VALUES");
goto no_values;
@@ -5354,7 +5418,9 @@ PHP_PGSQL_API int php_pgsql_insert(PGcon
}
smart_str_appends(&querystr, "INSERT INTO ");
- build_tablename(&querystr, pg_link, table);
+ if (build_tablename(&querystr, pg_link, table) == FAILURE) {
+ goto cleanup;
+ }
smart_str_appends(&querystr, " (");
ZEND_HASH_FOREACH_STR_KEY(Z_ARRVAL_P(var_array), fld) {
@@ -5364,6 +5430,10 @@ PHP_PGSQL_API int php_pgsql_insert(PGcon
}
if (opt & PGSQL_DML_ESCAPE) {
tmp = PQescapeIdentifier(pg_link, ZSTR_VAL(fld), ZSTR_LEN(fld) + 1);
+ if (tmp == NULL) {
+ php_error_docref(NULL, E_NOTICE, "Failed to escape field '%s'", ZSTR_VAL(fld));
+ goto cleanup;
+ }
smart_str_appends(&querystr, tmp);
PQfreemem(tmp);
} else {
@@ -5375,15 +5445,19 @@ PHP_PGSQL_API int php_pgsql_insert(PGcon
smart_str_appends(&querystr, ") VALUES (");
/* make values string */
- ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(var_array), val) {
+ ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(var_array), fld, val) {
/* we can avoid the key_type check here, because we tested it in the other loop */
switch (Z_TYPE_P(val)) {
case IS_STRING:
if (opt & PGSQL_DML_ESCAPE) {
- size_t new_len;
- char *tmp;
- tmp = (char *)safe_emalloc(Z_STRLEN_P(val), 2, 1);
- new_len = PQescapeStringConn(pg_link, tmp, Z_STRVAL_P(val), Z_STRLEN_P(val), NULL);
+ int error;
+ char *tmp = safe_emalloc(Z_STRLEN_P(val), 2, 1);
+ size_t new_len = PQescapeStringConn(pg_link, tmp, Z_STRVAL_P(val), Z_STRLEN_P(val), &error);
+ if (error) {
+ php_error_docref(NULL, E_NOTICE, "Failed to escape field '%s' value", ZSTR_VAL(fld));
+ efree(tmp);
+ goto cleanup;
+ }
smart_str_appendc(&querystr, '\'');
smart_str_appendl(&querystr, tmp, new_len);
smart_str_appendc(&querystr, '\'');
@@ -5537,6 +5611,10 @@ static inline int build_assignment_strin
}
if (opt & PGSQL_DML_ESCAPE) {
char *tmp = PQescapeIdentifier(pg_link, ZSTR_VAL(fld), ZSTR_LEN(fld) + 1);
+ if (tmp == NULL) {
+ php_error_docref(NULL, E_NOTICE, "Failed to escape field '%s'", ZSTR_VAL(fld));
+ return -1;
+ }
smart_str_appends(querystr, tmp);
PQfreemem(tmp);
} else {
@@ -5551,8 +5629,14 @@ static inline int build_assignment_strin
switch (Z_TYPE_P(val)) {
case IS_STRING:
if (opt & PGSQL_DML_ESCAPE) {
+ int error;
char *tmp = (char *)safe_emalloc(Z_STRLEN_P(val), 2, 1);
- size_t new_len = PQescapeStringConn(pg_link, tmp, Z_STRVAL_P(val), Z_STRLEN_P(val), NULL);
+ size_t new_len = PQescapeStringConn(pg_link, tmp, Z_STRVAL_P(val), Z_STRLEN_P(val), &error);
+ if (error) {
+ php_error_docref(NULL, E_NOTICE, "Failed to escape field '%s' value", ZSTR_VAL(fld));
+ efree(tmp);
+ return -1;
+ }
smart_str_appendc(querystr, '\'');
smart_str_appendl(querystr, tmp, new_len);
smart_str_appendc(querystr, '\'');
@@ -5620,7 +5704,9 @@ PHP_PGSQL_API int php_pgsql_update(PGcon
}
smart_str_appends(&querystr, "UPDATE ");
- build_tablename(&querystr, pg_link, table);
+ if (build_tablename(&querystr, pg_link, table) == FAILURE) {
+ goto cleanup;
+ }
smart_str_appends(&querystr, " SET ");
if (build_assignment_string(pg_link, &querystr, Z_ARRVAL_P(var_array), 0, ",", 1, opt))
@@ -5722,7 +5808,9 @@ PHP_PGSQL_API int php_pgsql_delete(PGcon
}
smart_str_appends(&querystr, "DELETE FROM ");
- build_tablename(&querystr, pg_link, table);
+ if (build_tablename(&querystr, pg_link, table) == FAILURE) {
+ goto cleanup;
+ }
smart_str_appends(&querystr, " WHERE ");
if (build_assignment_string(pg_link, &querystr, Z_ARRVAL_P(ids_array), 1, " AND ", sizeof(" AND ")-1, opt))
@@ -5860,7 +5948,9 @@ PHP_PGSQL_API void php_pgsql_result2arra
}
smart_str_appends(&querystr, "SELECT * FROM ");
- build_tablename(&querystr, pg_link, table);
+ if (build_tablename(&querystr, pg_link, table) == FAILURE) {
+ goto cleanup;
+ }
smart_str_appends(&querystr, " WHERE ");
if (build_assignment_string(pg_link, &querystr, Z_ARRVAL_P(ids_array), 1, " AND ", sizeof(" AND ")-1, opt))