diff --git a/.gitignore b/.gitignore index eb3f58e..20f5474 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/php-7.3.20.tar.xz +SOURCES/php-7.3.29.tar.xz diff --git a/.rh-php73-php.metadata b/.rh-php73-php.metadata index 82c7342..eab30b7 100644 --- a/.rh-php73-php.metadata +++ b/.rh-php73-php.metadata @@ -1 +1 @@ -d0dd05fa421e0f581960eda6cb8a256abb98b920 SOURCES/php-7.3.20.tar.xz +e694c68bf991d06ac6bba0277efce80bc8946c69 SOURCES/php-7.3.29.tar.xz diff --git a/SOURCES/php-bug80783.patch b/SOURCES/php-bug80783.patch new file mode 100644 index 0000000..2da9928 --- /dev/null +++ b/SOURCES/php-bug80783.patch @@ -0,0 +1,185 @@ +From bccca0b53aa60a62e2988c750fc73c02d109e642 Mon Sep 17 00:00:00 2001 +From: "Christoph M. Becker" +Date: Thu, 25 Feb 2021 14:38:42 +0100 +Subject: [PATCH] Fix #80783: PDO ODBC truncates BLOB records at every 256th + byte + +It is not guaranteed, that the driver inserts only a single NUL byte at +the end of the buffer. Apparently, there is no way to find out the +actual data length in the buffer after calling `SQLGetData()`, so we +adjust after the next `SQLGetData()` call. + +We also prevent PDO::ODBC_ATTR_ASSUME_UTF8 from fetching garbage, by +fetching all chunks with the same C type. + +Closes GH-6716. +--- + NEWS | 4 ++++ + ext/pdo_odbc/odbc_stmt.c | 14 +++++++++++-- + ext/pdo_odbc/tests/bug80783.phpt | 32 ++++++++++++++++++++++++++++++ + ext/pdo_odbc/tests/bug80783a.phpt | 33 +++++++++++++++++++++++++++++++ + 4 files changed, 81 insertions(+), 2 deletions(-) + create mode 100644 ext/pdo_odbc/tests/bug80783.phpt + create mode 100644 ext/pdo_odbc/tests/bug80783a.phpt + +diff --git a/ext/pdo_odbc/odbc_stmt.c b/ext/pdo_odbc/odbc_stmt.c +index 18abc475b9eb..7ce0bebdca0d 100644 +--- a/ext/pdo_odbc/odbc_stmt.c ++++ b/ext/pdo_odbc/odbc_stmt.c +@@ -652,6 +652,7 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, zend_ulong + + /* if it is a column containing "long" data, perform late binding now */ + if (C->is_long) { ++ SQLLEN orig_fetched_len = SQL_NULL_DATA; + zend_ulong used = 0; + char *buf; + RETCODE rc; +@@ -662,6 +663,7 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, zend_ulong + + rc = SQLGetData(S->stmt, colno+1, C->is_unicode ? SQL_C_BINARY : SQL_C_CHAR, C->data, + 256, &C->fetched_len); ++ orig_fetched_len = C->fetched_len; + + if (rc == SQL_SUCCESS) { + /* all the data fit into our little buffer; +@@ -673,7 +675,8 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, zend_ulong + /* this is a 'long column' + + read the column in 255 byte blocks until the end of the column is reached, reassembling those blocks +- in order into the output buffer ++ in order into the output buffer; 255 bytes are an optimistic assumption, since the driver may assert ++ more or less NUL bytes at the end; we cater to that later, if actual length information is available + + this loop has to work whether or not SQLGetData() provides the total column length. + calling SQLDescribeCol() or other, specifically to get the column length, then doing a single read +@@ -687,7 +690,14 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, zend_ulong + do { + C->fetched_len = 0; + /* read block. 256 bytes => 255 bytes are actually read, the last 1 is NULL */ +- rc = SQLGetData(S->stmt, colno+1, SQL_C_CHAR, buf2, 256, &C->fetched_len); ++ rc = SQLGetData(S->stmt, colno+1, C->is_unicode ? SQL_C_BINARY : SQL_C_CHAR, buf2, 256, &C->fetched_len); ++ ++ /* adjust `used` in case we have length info from the driver */ ++ if (orig_fetched_len >= 0 && C->fetched_len >= 0) { ++ SQLLEN fixed_used = orig_fetched_len - C->fetched_len; ++ ZEND_ASSERT(fixed_used <= used + 1); ++ used = fixed_used; ++ } + + /* resize output buffer and reassemble block */ + if (rc==SQL_SUCCESS_WITH_INFO) { +diff --git a/ext/pdo_odbc/tests/bug80783.phpt b/ext/pdo_odbc/tests/bug80783.phpt +new file mode 100644 +index 000000000000..9794c25a30ec +--- /dev/null ++++ b/ext/pdo_odbc/tests/bug80783.phpt +@@ -0,0 +1,32 @@ ++--TEST-- ++Bug #80783 (PDO ODBC truncates BLOB records at every 256th byte) ++--SKIPIF-- ++ ++--FILE-- ++exec("CREATE TABLE bug80783 (name IMAGE)"); ++ ++$string = str_repeat("0123456789", 50); ++$db->exec("INSERT INTO bug80783 VALUES('$string')"); ++ ++$stmt = $db->prepare("SELECT name FROM bug80783"); ++$stmt->bindColumn(1, $data, PDO::PARAM_LOB); ++$stmt->execute(); ++$stmt->fetch(PDO::FETCH_BOUND); ++ ++var_dump($data === bin2hex($string)); ++?> ++--CLEAN-- ++exec("DROP TABLE bug80783"); ++?> ++--EXPECT-- ++bool(true) +diff --git a/ext/pdo_odbc/tests/bug80783a.phpt b/ext/pdo_odbc/tests/bug80783a.phpt +new file mode 100644 +index 000000000000..f9e123ae5426 +--- /dev/null ++++ b/ext/pdo_odbc/tests/bug80783a.phpt +@@ -0,0 +1,33 @@ ++--TEST-- ++Bug #80783 (PDO ODBC truncates BLOB records at every 256th byte) ++--SKIPIF-- ++ ++--FILE-- ++exec("CREATE TABLE bug80783a (name NVARCHAR(MAX))"); ++ ++$string = str_repeat("0123456789", 50); ++$db->exec("INSERT INTO bug80783a VALUES('$string')"); ++ ++$stmt = $db->prepare("SELECT name FROM bug80783a"); ++$stmt->setAttribute(PDO::ODBC_ATTR_ASSUME_UTF8, true); ++$stmt->bindColumn(1, $data, PDO::PARAM_STR); ++$stmt->execute(); ++$stmt->fetch(PDO::FETCH_BOUND); ++ ++var_dump($data === $string); ++?> ++--CLEAN-- ++exec("DROP TABLE bug80783a"); ++?> ++--EXPECT-- ++bool(true) +From 25f5a1b2e15344e75d69a7140631d467e8b3f966 Mon Sep 17 00:00:00 2001 +From: Remi Collet +Date: Thu, 8 Apr 2021 11:04:33 +0200 +Subject: [PATCH] Improve fix for #80783 + +--- + ext/pdo_odbc/odbc_stmt.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/ext/pdo_odbc/odbc_stmt.c b/ext/pdo_odbc/odbc_stmt.c +index 7ce0bebdca0d..368648c36ae2 100644 +--- a/ext/pdo_odbc/odbc_stmt.c ++++ b/ext/pdo_odbc/odbc_stmt.c +@@ -665,13 +665,13 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, zend_ulong + 256, &C->fetched_len); + orig_fetched_len = C->fetched_len; + +- if (rc == SQL_SUCCESS) { ++ if (rc == SQL_SUCCESS && C->fetched_len < 256) { + /* all the data fit into our little buffer; + * jump down to the generic bound data case */ + goto in_data; + } + +- if (rc == SQL_SUCCESS_WITH_INFO) { ++ if (rc == SQL_SUCCESS_WITH_INFO || rc == SQL_SUCCESS) { + /* this is a 'long column' + + read the column in 255 byte blocks until the end of the column is reached, reassembling those blocks +@@ -700,7 +700,7 @@ static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, zend_ulong + } + + /* resize output buffer and reassemble block */ +- if (rc==SQL_SUCCESS_WITH_INFO) { ++ if (rc==SQL_SUCCESS_WITH_INFO || (rc==SQL_SUCCESS && C->fetched_len > 255)) { + /* point 5, in section "Retrieving Data with SQLGetData" in http://msdn.microsoft.com/en-us/library/windows/desktop/ms715441(v=vs.85).aspx + states that if SQL_SUCCESS_WITH_INFO, fetched_len will be > 255 (greater than buf2's size) + (if a driver fails to follow that and wrote less than 255 bytes to buf2, this will AV or read garbage into buf) */ diff --git a/SPECS/php.spec b/SPECS/php.spec index e1e1ce5..b87fe7f 100644 --- a/SPECS/php.spec +++ b/SPECS/php.spec @@ -60,7 +60,7 @@ Summary: PHP scripting language for creating dynamic web sites Name: %{?scl_prefix}php -Version: 7.3.20 +Version: 7.3.29 Release: 1%{?dist} # All files licensed under PHP version 3.01, except # Zend is licensed under Zend @@ -107,6 +107,8 @@ Patch45: php-7.2.3-ldap_r.patch Patch46: php-7.3.20-fixheader.patch # drop "Configure command" from phpinfo output Patch47: php-5.6.3-phpinfo.patch +# Backported from 7.4.18 - pdo_odbc +Patch48: php-bug80783.patch # Upstream fixes (100+) @@ -654,6 +656,7 @@ sed -e 's/php-devel/%{?scl_prefix}php-devel/' -i scripts/phpize.in %patch45 -p1 -b .ldap_r %patch46 -p1 -b .fixheader %patch47 -p1 -b .phpinfo +%patch48 -p1 -b .bug80783 # upstream patches @@ -1454,6 +1457,10 @@ fi %changelog +* Thu Jul 1 2021 Remi Collet - 7.3.29-1 +- update to 7.3.29 +- fix PDO ODBC truncates BLOB records at every 256th byte #1977764 + * Fri Jul 10 2020 Remi Collet - 7.3.20-1 - update to 7.3.20 #1853211