Bug #80460 [PATCH]: ODBC doesn't account for SQL_NO_TOTAL indicator, causing segmentation fault

From: Date: Fri, 26 Mar 2021 14:20:29 +0000
Subject: Bug #80460 [PATCH]: ODBC doesn't account for SQL_NO_TOTAL indicator, causing segmentation fault
References: 1  Groups: php.bugs 
Request: Send a blank email to php-bugs+get-233005@lists.php.net to get a copy of this message
Edit report at https://bugs.php.net/bug.php?id=80460&edit=1

 ID:                 80460
 Patch added by:     cmb@php.net
 Reported by:        mirish at ibm dot com
 Summary:            ODBC doesn't account for SQL_NO_TOTAL indicator,
                     causing segmentation fault
 Status:             Analyzed
 Type:               Bug
 Package:            ODBC related
 Operating System:   RHEL 7.9
 PHP Version:        Irrelevant
 Assigned To:        cmb
 Block user comment: N
 Private report:     N

 New Comment:

The following pull request has been associated:

Patch Name: Fix #80460: ODBC doesn't account for SQL_NO_TOTAL indicator
On GitHub:  https://github.com/php/php-src/pull/6809
Patch:      https://github.com/php/php-src/pull/6809.patch


Previous Comments:
------------------------------------------------------------------------
[2021-03-26 10:16:36] cmb@php.net

Thanks for the clarification!  Indeed, these SQL_NO_TOTAL checks
are necessary.

------------------------------------------------------------------------
[2021-03-25 20:04:34] mirish at ibm dot com

Ok, a little clarification: It looks like my particular error is generated from the StrLen_or_IndPtr
bound from SQLBindCol: https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlbindcol-function?view=sql-server-ver15

The valid values are still:
* The length of the data available to return
* SQL_NO_TOTAL
* SQL_NULL_DATA

Then, when odbc_fetch_row is called, SQLExtendedFetch gets called (in my particular test, but
SQLFetch and SQLGetData can equally return the same thing), populates that indicator bound in
SQLBindCol, and SQL_NO_TOTAL is returned to that StrLen_or_IndPtr, which is mapped in the php_odbc.c
code to result->values[field_ind].vallen.

Then, the code tries to generate a string from that length (-4), causing the seg fault.

Backtrace:

#0  0x00007f0d54ed9474 in __memcpy_ssse3_back () from /usr/lib64/libc.so.6
#1  0x0000000000591919 in zend_string_init (str=0x7f0d54491028 "",
len=18446744073709551612, persistent=0)
    at /home/mirish/php-src/Zend/zend_string.h:157
#2  0x0000000000596dc1 in zif_odbc_result (execute_data=0x7f0d54414150, return_value=0x7f0d54414120)
    at /home/mirish/php-src/ext/odbc/php_odbc.c:2224
#3  0x000000000085cbb1 in ZEND_DO_ICALL_SPEC_RETVAL_USED_HANDLER () at
/home/mirish/php-src/Zend/zend_vm_execute.h:1313
#4  0x00000000008bc92c in execute_ex (ex=0x7f0d54414020) at
/home/mirish/php-src/Zend/zend_vm_execute.h:53564
#5  0x00000000008c09fa in zend_execute (op_array=0x7f0d5447e400, return_value=0x0)
    at /home/mirish/php-src/Zend/zend_vm_execute.h:57664
#6  0x00000000007ef9fc in zend_execute_scripts (type=8, retval=0x0, file_count=3)
    at /home/mirish/php-src/Zend/zend.c:1663
#7  0x000000000075bba0 in php_execute_script (primary_file=0x7ffd3adb2620) at
/home/mirish/php-src/main/main.c:2619
#8  0x00000000008c31c6 in do_cli (argc=2, argv=0x2c11700) at
/home/mirish/php-src/sapi/cli/php_cli.c:961
#9  0x00000000008c4105 in main (argc=2, argv=0x2c11700) at
/home/mirish/php-src/sapi/cli/php_cli.c:1352


And for good measure, here is the ODBC trace:

...
[ODBC][88259][1616702310.287337][SQLExecDirect.c][240]
		Entry:
			Statement = 0x1f66a90
			SQL = [SELECT * FROM MIRISH.UTF8TEST][length = 29 (SQL_NTS)]
[ODBC][88259][1616702310.468968][SQLExecDirect.c][521]
		Exit:[SQL_SUCCESS]
[ODBC][88259][1616702310.469037][SQLNumResultCols.c][156]
		Entry:
			Statement = 0x1f66a90
			Column Count = 0x7effad458730
[ODBC][88259][1616702310.469087][SQLNumResultCols.c][251]
		Exit:[SQL_SUCCESS]
			Count = 0x7effad458730 -> 1
[ODBC][88259][1616702310.469128][SQLColAttribute.c][294]
		Entry:
			Statement = 0x1f66a90
			Column Number = 1
			Field Identifier = SQL_DESC_NAME
			Character Attr = 0x7effad45c140
			Buffer Length = 256
			String Length = 0x7fff4dbce140
			Numeric Attribute = (nil)
[ODBC][88259][1616702310.469188][SQLColAttribute.c][709]
		Exit:[SQL_SUCCESS]
[ODBC][88259][1616702310.469217][SQLColAttribute.c][294]
		Entry:
			Statement = 0x1f66a90
			Column Number = 1
			Field Identifier = SQL_DESC_CONCISE_TYPE
			Character Attr = (nil)
			Buffer Length = 0
			String Length = (nil)
			Numeric Attribute = 0x7effad45c250
[ODBC][88259][1616702310.469252][SQLColAttribute.c][709]
		Exit:[SQL_SUCCESS]
[ODBC][88259][1616702310.469280][SQLColAttribute.c][294]
		Entry:
			Statement = 0x1f66a90
			Column Number = 1
			Field Identifier = SQL_DESC_OCTET_LENGTH
			Character Attr = (nil)
			Buffer Length = 0
			String Length = (nil)
			Numeric Attribute = 0x7fff4dbce138
[ODBC][88259][1616702310.469309][SQLColAttribute.c][709]
		Exit:[SQL_SUCCESS]
[ODBC][88259][1616702310.469338][SQLBindCol.c][236]
		Entry:
			Statement = 0x1f66a90
			Column Number = 1
			Target Type = 1 SQL_CHAR
			Target Value = 0x7effad491028
			Buffer Length = 2
			StrLen Or Ind = 0x7effad45c248
[ODBC][88259][1616702310.469382][SQLBindCol.c][344]
		Exit:[SQL_SUCCESS]
[ODBC][88259][1616702310.469452][SQLExtendedFetch.c][166]
		Entry:
			Statement = 0x1f66a90
			Fetch Type = 1
			Row = 1
			PcRow = 0x7fff4dbce1a0
			Row Status = 0x7fff4dbce190
[ODBC][88259][1616702310.560382][SQLExtendedFetch.c][339]
		Exit:[SQL_SUCCESS]

------------------------------------------------------------------------
[2021-03-25 17:46:25] mirish at ibm dot com

Last comment should read:

result->values[i].vallen == SQL_NO_TOTAL

Similar to how it checks for SQL_NULL_DATA.

------------------------------------------------------------------------
[2021-03-25 17:44:23] mirish at ibm dot com

cmb@php.net noted:

"While the other bug report was indeed about SQL_NO_DATA, the fix
is supposed to cater to any result other than SQL_SUCCESS or
SQL_SUCCESS_WITH_INFO, and to return early in those cases."

I think I see the confusion now. When SQLGetData returns SQL_NO_TOTAL, it is doing so in the
StrLen_or_IndPtr parameter, not as the return code: https://docs.microsoft.com/en-us/sql/odbc/reference/syntax/sqlgetdata-function?view=sql-server-ver15

The issue you linked does indeed check the return code, but the issue here is that the valid values
returned from the StrLen_or_IndPtr are:

* The length of the data available to return
* SQL_NO_TOTAL
* SQL_NULL_DATA

The current code checks for SQL_NULL_DATA, and assigns a value of null if so. Otherwise, it just
assumes the value stored there is the length in bytes returned and tries to allocate a buffer of
that size. But that isn't the case when it returns SQL_NO_TOTAL (-4), which will try to
allocated a buffer of -4 and cause a segfault. There needs to be another check for:

result->values[i].vallen == SQL_NO_TOTAL

Similar to how it checks for SQL_NO_DATA.

I will work to generate a backtrace, might take a moment of relearning how to build PHP on my
system.

------------------------------------------------------------------------
[2021-02-08 13:30:14] cmb@php.net

> according to IBM support we have a related problem […]

This ticket is about a segmentation fault in the ODBC extension;
your issue is about truncation of strings in the PDO_ODBC
extension.  Please report that as separate ticket.

------------------------------------------------------------------------


The remainder of the comments for this report are too long. To view
the rest of the comments, please view the bug report online at

    https://bugs.php.net/bug.php?id=80460


--
Edit this bug report at https://bugs.php.net/bug.php?id=80460&edit=1


Thread (19 messages)

« previous php.bugs (#233005) next »