diff options
| author | Remi Collet <remi@remirepo.net> | 2021-04-08 15:48:15 +0200 | 
|---|---|---|
| committer | Remi Collet <remi@remirepo.net> | 2021-04-08 15:48:15 +0200 | 
| commit | 2fec2005aa6472c5bca9368d3a38cb3782a87a3c (patch) | |
| tree | afe175615058ca7e6c8efa00ed6752425b922d14 | |
| parent | d24e9befae861b647e0f1e761ecd4cecd29fbbbb (diff) | |
add upstream patch for https://bugs.php.net/80783
  PDO ODBC truncates BLOB records at every 256th byte
use oracle client library version 21.1
| -rw-r--r-- | php-bug80783.patch | 185 | ||||
| -rw-r--r-- | php.spec | 30 | 
2 files changed, 197 insertions, 18 deletions
| diff --git a/php-bug80783.patch b/php-bug80783.patch new file mode 100644 index 0000000..2da9928 --- /dev/null +++ b/php-bug80783.patch @@ -0,0 +1,185 @@ +From bccca0b53aa60a62e2988c750fc73c02d109e642 Mon Sep 17 00:00:00 2001 +From: "Christoph M. Becker" <cmbecker69@gmx.de> +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-- ++<?php ++if (!extension_loaded('pdo_odbc')) die('skip pdo_odbc extension not available'); ++require 'ext/pdo/tests/pdo_test.inc'; ++PDOTest::skip(); ++?> ++--FILE-- ++<?php ++require 'ext/pdo/tests/pdo_test.inc'; ++$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt'); ++$db->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-- ++<?php ++require 'ext/pdo/tests/pdo_test.inc'; ++$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt'); ++$db->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-- ++<?php ++if (!extension_loaded('pdo_odbc')) die('skip pdo_odbc extension not available'); ++require 'ext/pdo/tests/pdo_test.inc'; ++PDOTest::skip(); ++?> ++--FILE-- ++<?php ++require 'ext/pdo/tests/pdo_test.inc'; ++$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt'); ++$db->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-- ++<?php ++require 'ext/pdo/tests/pdo_test.inc'; ++$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt'); ++$db->exec("DROP TABLE bug80783a"); ++?> ++--EXPECT-- ++bool(true) +From 25f5a1b2e15344e75d69a7140631d467e8b3f966 Mon Sep 17 00:00:00 2001 +From: Remi Collet <remi@remirepo.net> +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) */ @@ -55,22 +55,8 @@  %global mysql_sock %(mysql_config --socket  2>/dev/null || echo /var/lib/mysql/mysql.sock) -%if 0%{?rhel} == 6 -%ifarch x86_64 -%global oraclever 18.5 -%else -%global oraclever 18.3 -%endif -%global oraclelib 18.1 - -%else -%ifarch x86_64 -%global oraclever 19.9 -%else -%global oraclever 19.6 -%endif -%global oraclelib 19.1 -%endif +%global oraclever 21.1 +%global oraclelib 21.1  # Build for LiteSpeed Web Server (LSAPI)  %global with_lsws     1 @@ -141,7 +127,7 @@  Summary: PHP scripting language for creating dynamic web sites  Name:    %{?scl_prefix}php  Version: %{upver}%{?rcver:~%{rcver}} -Release: 1%{?dist} +Release: 2%{?dist}  # All files licensed under PHP version 3.01, except  # Zend is licensed under Zend  # TSRM is licensed under BSD @@ -203,8 +189,10 @@ Patch49: php-7.3.24-fpm.patch  Patch91: php-7.2.0-oci8conf.patch  # Upstream fixes (100+) -# Backported from 7.4.16 +# Backported from 7.4.16 - opcache and pcre.jit  Patch100: php-bug80682.patch +# Backported from 7.4.18 - pdo_odbc +Patch101: php-bug80783.patch  # Security fixes (200+) @@ -975,6 +963,7 @@ sed -e 's/php-devel/%{?scl_prefix}php-devel/' -i scripts/phpize.in  # upstream patches  %patch100 -p1 -b .bug80682 +%patch101 -p1 -b .bug80783  # security patches @@ -1928,6 +1917,11 @@ fi  %changelog +* Thu Apr  8 2021 Remi Collet <remi@remirepo.net> - 7.3.27-2 +- add upstream patch for https://bugs.php.net/80783 +  PDO ODBC truncates BLOB records at every 256th byte +- use oracle client library version 21.1 +  * Tue Feb  2 2021 Remi Collet <remi@remirepo.net> - 7.3.27-1  - Update to 7.3.27 - http://www.php.net/releases/7_3_27.php | 
