[ODBC-289] Crash fetching from statement after closing and re-executing Created: 2020-07-08  Updated: 2020-10-19  Resolved: 2020-10-16

Status: Closed
Project: MariaDB Connector/ODBC
Component/s: General
Affects Version/s: 3.1.9
Fix Version/s: 3.1.10

Type: Bug Priority: Major
Reporter: Tom Hughes Assignee: Lawrin Novitsky
Resolution: Fixed Votes: 0
Labels: None
Environment:

Fedora 32


Issue Links:
Duplicate
is duplicated by ODBC-291 Memory Corruption in ODBC Client 3.1.... Closed

 Description   

If SQL_ATTR_ROW_ARRAY_SIZE is set to a value greater than one and you close a cursor by calling SQLFreeStmt with SQL_CLOSE and then start a new query with SQLExecute then the first fetch will cause an attempt to write to the old Stmt->result buffer which was freed when the statement was closed.

Here's a summary of the valgrind report when this is triggered:

==427925== Invalid write of size 8
==427925==    at 0x1AE91262: mthd_stmt_fetch_to_bind (mariadb_stmt.c:409 in /usr/lib64/libmariadb.so.3)
==427925==    by 0x1AE92DB3: mysql_stmt_fetch (mariadb_stmt.c:1459 in /usr/lib64/libmariadb.so.3)
==427925==    by 0x1AE3D7B4: MoveNext (ma_result.c:63 in /usr/lib64/libmaodbc.so)
==427925==    by 0x1AE3A344: MADB_StmtFetch (ma_statement.c:2036 in /usr/lib64/libmaodbc.so)
==427925==    by 0x1AE32B61: MADB_StmtFetchScroll (ma_statement.c:4596 in /usr/lib64/libmaodbc.so)
==427925==    by 0x1AE21916: SQLFetch (odbc_3_api.c:1415 in /usr/lib64/libmaodbc.so)
==427925==    by 0x1A9BAE51: SQLFetch (SQLFetch.c:301 in /usr/lib64/libodbc.so.2.0.0)
==427925==  Address 0x1b441040 is 80 bytes inside a block of size 336 free'd
==427925==    at 0x483B9F5: free (vg_replace_malloc.c:538 in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==427925==    by 0x1AE3687E: MADB_StmtFree (ma_statement.c:205 in /usr/lib64/libmaodbc.so)
==427925==    by 0x1A9BC9E9: SQLFreeStmt (SQLFreeStmt.c:218 in /usr/lib64/libodbc.so.2.0.0)
==427925==  Block was alloc'd at
==427925==    at 0x483CAE9: calloc (vg_replace_malloc.c:760 in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==427925==    by 0x1AE3A3AE: MADB_StmtFetch (ma_statement.c:2003 in /usr/lib64/libmaodbc.so)
==427925==    by 0x1AE32B61: MADB_StmtFetchScroll (ma_statement.c:4596 in /usr/lib64/libmaodbc.so)
==427925==    by 0x1AE21916: SQLFetch (odbc_3_api.c:1415 in /usr/lib64/libmaodbc.so)
==427925==    by 0x1A9BAE51: SQLFetch (SQLFetch.c:301 in /usr/lib64/libodbc.so.2.0.0)

The trigger is that when doing a multi-row fetch MADB_StmtFetch tries to read the first row with MoveNext without calling mysql_stmt_bind_result to bind the result area first.

On the first execution that is fine as no bind has been done so the C API doesn't try to write the results back.

On the second execution the library still has the binds from the first time, and tries to write to them, but they point at the old result buffer that was freed when SQLFreeStmt was called to close the cursor.

If we're not doing a multi-row fetch then we're fine as the new result buffer will be bound with mysql_stmt_bind_result before the rows are fetched for real - it is only this special pre-read fetch of the first row that doesn't bind first.

It might be that this is a bug in the C connector as the close does call mysql_stmt_free_result but that doesn't currently reset the binds, and it is not clear if it is supposed to or not.



 Comments   
Comment by Laura Sabel [ 2020-08-18 ]

See also ODBC-291 – possibly the same bug.

Comment by Lawrin Novitsky [ 2020-10-12 ]

Thank you for your report.
Could you please verify it with version 3.1.9 or provide the testcase?
Because I could not repeat the crash right away based on the described steps.

Comment by Tom Hughes [ 2020-10-13 ]

Yes it's still happening in 3.1.9 and I've put together a small test case:

// gcc $(pkg-config --cflags odbc) -g -o odbc289 odbc289.c $(pkg-config --libs odbc)
 
#include <assert.h>
#include <sqlext.h>
#include <stdio.h>
 
int main(int argc, char **argv)
{
  SQLHANDLE hdbc;
  SQLHANDLE henv;
  SQLHANDLE hstmt;
  SQLRETURN rc;
 
  rc = SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv );
  assert( rc == SQL_SUCCESS );
 
  rc = SQLSetEnvAttr( henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0 );
  assert( rc == SQL_SUCCESS );
 
  rc = SQLAllocHandle( SQL_HANDLE_DBC, henv, &hdbc );
  assert( rc == SQL_SUCCESS );
 
  rc = SQLConnect( hdbc, "x", SQL_NTS, "x", SQL_NTS, "x", SQL_NTS );
  assert( rc == SQL_SUCCESS );
 
  rc = SQLAllocHandle( SQL_HANDLE_STMT, hdbc, &hstmt );
  assert( rc == SQL_SUCCESS );
 
  rc = SQLPrepare( hstmt, "SELECT LAST_NAME FROM CUST", SQL_NTS );
  assert( rc == SQL_SUCCESS );
 
  rc = SQLSetStmtAttr( hstmt, SQL_ATTR_ROW_ARRAY_SIZE, (SQLPOINTER)2, SQL_IS_INTEGER );
  assert( rc == SQL_SUCCESS );
 
  SQLCHAR value[60];
  SQLLEN length[2];
 
  rc = SQLBindCol( hstmt, 1, SQL_CHAR, value, 30, length );
  assert( rc == SQL_SUCCESS );
 
  rc = SQLExecute( hstmt );
  assert( rc == SQL_SUCCESS );
 
  rc = SQLFetch( hstmt );
  assert( rc == SQL_SUCCESS );
 
  printf( "fetch: %s\n", value );
  printf( "fetch: %s\n", value + 30 );
 
  rc = SQLFreeStmt( hstmt, SQL_CLOSE );
  assert( rc == SQL_SUCCESS );
 
  rc = SQLExecute( hstmt );
  assert( rc == SQL_SUCCESS );
 
  rc = SQLFetch( hstmt );
  assert( rc == SQL_SUCCESS );
 
  printf( "fetch: %s\n", value );
  printf( "fetch: %s\n", value + 30 );
 
  return 0;
}

Obviously you'll need to fill in a DSN and authentication details for the SQLConnect call and update the SQL statement to something appropriate for the database you're using. Run that under valgrind and you should see it complain during the second fetch.

Comment by Lawrin Novitsky [ 2020-10-14 ]

Interesting, for me your testcase doesn't look different from the one I've already pushed, and it passes
Ok, I must try with your test anyway

Comment by Tom Hughes [ 2020-10-14 ]

Possibly it depends on the version of the underlying C library - it wasn't clear to me where the bug actually was after all.

I'm using version 3.1.10 of the C connector.

Comment by Lawrin Novitsky [ 2020-10-14 ]

In our binaries we still link (Conn/C) statically(there is the old plan to link dynamically for platforms other than WIndows). Linux distributions link dynamically. 3.1.9 release linked against C/Cv3.1.9, and on github it already uses C/C 3.1.10, thus travis used that version as well.

Comment by Lawrin Novitsky [ 2020-10-14 ]

Your test does not crash for me either. The thing is that there is very similar bug report, but/and it also against 3.1.7
so, I have to try to figure out something

Comment by Tom Hughes [ 2020-10-14 ]

Just to be clear this won't necessarily crash - it may do but it will be dependent on exactly what happens with the freed memory among other things. What it will do is warn under valgrind, or likely in an ASAN build though I haven't tried that.

As it happens my version with a string field does crash for me at least, while changing it to use a numeric field like your test case stops it crashing while still giving a warning under valgrind.

Comment by Lawrin Novitsky [ 2020-10-16 ]

Commit 22f1255. The testcase has been pushed before.
The warning in valgrind has been cleared.

Comment by Laura Sabel [ 2020-10-19 ]

To be clear – you are saying that this issue is fixed by 3.1.9, or that it will be fixed in a still-to-be-released version?

Comment by Lawrin Novitsky [ 2020-10-19 ]

sabell no, the bug is present in 3.1.9, the fix will be in 3.1.10 release.
and it's gonna be published soon.

Comment by Laura Sabel [ 2020-10-19 ]

Thanks for confirming!

Generated at Thu Feb 08 03:27:36 UTC 2024 using Jira 8.20.16#820016-sha1:9d11dbea5f4be3d4cc21f03a88dd11d8c8687422.