File php7-CVE-2025-1735.patch of Package php7.39648
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-7.4.33/ext/pdo_pgsql/pgsql_driver.c
===================================================================
--- php-7.4.33.orig/ext/pdo_pgsql/pgsql_driver.c
+++ php-7.4.33/ext/pdo_pgsql/pgsql_driver.c
@@ -323,11 +323,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);
@@ -339,7 +343,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-7.4.33/ext/pgsql/pgsql.c
===================================================================
--- php-7.4.33.orig/ext/pgsql/pgsql.c
+++ php-7.4.33/ext/pgsql/pgsql.c
@@ -4393,10 +4393,16 @@ PHP_FUNCTION(pg_escape_string)
to = zend_string_safe_alloc(ZSTR_LEN(from), 2, 0, 0);
#ifdef HAVE_PQESCAPE_CONN
if (link) {
+ int err;
if ((pgsql = (PGconn *)zend_fetch_resource2(link, "PostgreSQL link", le_link, le_plink)) == NULL) {
RETURN_FALSE;
}
- 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
#endif
{
@@ -4446,6 +4452,11 @@ PHP_FUNCTION(pg_escape_bytea)
#endif
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);
}
@@ -5529,7 +5540,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;
if (!*table_name) {
@@ -5570,7 +5581,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);
}
@@ -5578,7 +5596,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);
}
@@ -5850,7 +5875,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;
assert(pg_link != NULL);
@@ -6101,10 +6126,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;
@@ -6126,7 +6157,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;
@@ -6406,6 +6445,12 @@ PHP_PGSQL_API int php_pgsql_convert(PGco
#else
tmp = PQescapeBytea(Z_STRVAL_P(val), (unsigned char *)Z_STRLEN_P(val), &to_len);
#endif
+ 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);
@@ -6488,6 +6533,12 @@ PHP_PGSQL_API int php_pgsql_convert(PGco
zend_hash_update(Z_ARRVAL_P(result), field, &new_val);
} else {
char *escaped = PGSQLescapeIdentifier(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);
PGSQLfree(escaped);
}
@@ -6566,7 +6617,7 @@ static int do_exec(smart_str *querystr,
}
/* }}} */
-static inline void build_tablename(smart_str *querystr, PGconn *pg_link, const char *table) /* {{{ */
+static inline int build_tablename(smart_str *querystr, PGconn *pg_link, const char *table) /* {{{ */
{
size_t table_len = strlen(table);
@@ -6577,6 +6628,10 @@ static inline void build_tablename(smart
smart_str_appendl(querystr, table, len);
} else {
char *escaped = PGSQLescapeIdentifier(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);
PGSQLfree(escaped);
}
@@ -6589,11 +6644,17 @@ static inline void build_tablename(smart
smart_str_appendl(querystr, after_dot, len);
} else {
char *escaped = PGSQLescapeIdentifier(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);
PGSQLfree(escaped);
}
}
+
+ return SUCCESS;
}
/* }}} */
@@ -6615,7 +6676,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;
@@ -6631,7 +6694,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) {
@@ -6641,6 +6706,10 @@ PHP_PGSQL_API int php_pgsql_insert(PGcon
}
if (opt & PGSQL_DML_ESCAPE) {
tmp = PGSQLescapeIdentifier(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);
PGSQLfree(tmp);
} else {
@@ -6652,15 +6721,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, '\'');
@@ -6810,6 +6883,10 @@ static inline int build_assignment_strin
}
if (opt & PGSQL_DML_ESCAPE) {
char *tmp = PGSQLescapeIdentifier(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);
PGSQLfree(tmp);
} else {
@@ -6824,8 +6901,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, '\'');
@@ -6894,7 +6977,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))
@@ -6992,7 +7077,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))
@@ -7130,7 +7217,9 @@ PHP_PGSQL_API int php_pgsql_result2array
}
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))