Bug #15248 Memory allocation bug in SQLDriverConnect
Submitted: 25 Nov 2005 16:15 Modified: 8 Mar 2007 1:25
Reporter: Stéphane Donzé Email Updates:
Status: Closed Impact on me:
None 
Category:Connector / ODBC Severity:S2 (Serious)
Version:3.51.12 OS:Linux (Linux (32 and 64))
Assigned to: Bogdan Degtyariov CPU Architecture:Any

[25 Nov 2005 16:15] Stéphane Donzé
Description:
After opening and closing an ODBC connection with SQLDriverConnect several times, random errors occur in the driver, such as SEGV crashes or error messages like "Character set 'latin1' is not a compiled character set".
These errors seem to be related to a memory block free'd at some point in the code and yet used later (see the valgrind output attached to this bug report). 
The 3.51.10 version is working fine and produces a clean valgrind output when run on exactly the same program.
We are using the unixODBC-2.1.11 driver manager, but the bug was also observed with the DataDirect Connect for ODBC driver manager.
Our database server is a 4.0.24

How to repeat:
odbc.ini:
[logs]
Driver=/ng/lib/amd64-linux/libmyodbc3_r.so
Description=Site logs
Database=logs
Option=3
Server=ssss

program:
for (int i = 0; i < 4; i++) {
SQLAllocHandle(env);
SQLSetEnvAttr(SQL_ATTR_ODBC_VERSION, SQL_OV_ODBC3);
SQLDriverConnect("DSN=logs;User=xxxx;Password=yyyyy;Server=ssss;Database=logs");
SQLSetStmtAttr(SQL_ATTR_MAX_ROWS, 10);
SQLExecDirect("SELECT * FROM table");
SQLNumResultCols()
for each column: SQLDescribeCol()
for each row: SQLFetch(); SQLGetData(); ...
SQLDisconnect();
SQLFreeHandle();
SQLFreeEnv();

Suggested fix:
cf the valgrind output: the get_charset_number() function reads data from a memory block that has been freed in SQLGetPrivateProfileString
[25 Nov 2005 16:16] Stéphane Donzé
First valgrind output

Attachment: exa.3log.652 (application/octet-stream, text), 11.48 KiB.

[25 Nov 2005 16:17] Stéphane Donzé
Second valgrind output (after changing the login/password in the connection string)

Attachment: exa.3log.1431 (application/octet-stream, text), 11.67 KiB.

[25 Nov 2005 16:17] Stéphane Donzé
valgrind: http://valgrind.org/
[26 Nov 2005 8:23] Vasily Kishkin
Sorry...I was not able to reproduce the bug. Could you please build and run my test case ? My test case is attached.
[26 Nov 2005 8:23] Vasily Kishkin
Test case

Attachment: test.c (text/plain), 5.28 KiB.

[28 Nov 2005 22:39] Stéphane Donzé
I was able to reproduce the bug with your test case (slightly modified to make it compile and use an existing table on my side: cf file test2.c)
I ran the test on a Linux 64 machine (Linux version 2.4.21-32.0.1.ELsmp (bhcompile@thor.perf.redhat.com) (gcc version 3.2.3 20030502 (Red Hat Linux 3.2.3-52)) #1 SMP Tue May 17 17:46:36 EDT 2005) 
The test case is working without any error with the 3.51.10 driver.
When ran against the 3.51.12, it dumps a core at the second connection:

[Thread debugging using libthread_db enabled]
[New Thread 182906365120 (LWP 5246)]
Connect....
Test....
Fetched...
Disconnect....
Connect....

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 182906365120 (LWP 5246)]
0x0000002a95f56160 in strcasecmp () from /lib64/tls/libc.so.6
(gdb) where
#0  0x0000002a95f56160 in strcasecmp () from /lib64/tls/libc.so.6
#1  0x0000002a96574e4e in MYODBCUtilReadConnectStr ()
   from /ng/lib/amd64-linux/libmyodbc3_r.3.51.12.so
#2  0x0000002a9656a0db in my_SQLDriverConnect ()
   from /ng/lib/amd64-linux/libmyodbc3_r.3.51.12.so
#3  0x0000002a9656a504 in SQLDriverConnect ()
   from /ng/lib/amd64-linux/libmyodbc3_r.3.51.12.so
#4  0x0000002a95684996 in SQLDriverConnect (hdbc=0x2a98b02200, hwnd=0x0, 
    conn_str_in=0x7fbffff8a5 "DSN=logs;User=logsuser;Password=xxxxxxx;Server=ng5;Database=logs", len_conn_str_in=-3, 
    conn_str_out=0x5023c0 "DATABASE=logs;DESCRIPTION=Site logs;DSN=logs;OPTION=3;PWD=xxxxxxx;PORT=0;SERVER=ng5;UID=logsuser", conn_str_out_max=255, 
    ptr_conn_str_out=0x5024c0, driver_completion=0) at SQLDriverConnect.c:1155
#5  0x0000000000401413 in main (argc=-1733211584, argv=0x2a9657a60f)
    at /ng/src/donze/ng/src/tmp/test.c:123
(gdb) quit

I also attach the valgrind output corresponding to this test case.
[28 Nov 2005 22:40] Stéphane Donzé
Test case producing a SEGV in strcasecmp

Attachment: test2.c (application/octet-stream, text), 5.21 KiB.

[28 Nov 2005 22:42] Stéphane Donzé
valgrind output for the test case

Attachment: sqltest.3log.5294 (application/octet-stream, text), 5.57 KiB.

[7 Dec 2005 12:09] Vasily Kishkin
I was able to reproduce the bug on Linux Suse 9.3. Thanks for correct test case. 
I would like to notice test case works fine on Windows.
[13 Mar 2006 10:51] Bogdan Degtyariov
I was able to repeat this bug with MyODBC 3.51.06 (threadsafe library only).
Both versions of MyODBC 3.51.12 worked well on SuSE 9.2 (unixODBC 2.1.11)
[31 Mar 2006 13:27] Pierre MARC
Have the same trouble on linux RedHat entreprise 4. I cannot make the MyOdbc
driver run correctly in a multithread environment. I wrote also a little test
program. sometimes it runs, sometimes it fails with the "Character set 'latin1'
..." message, sometimes it fails with a segmentation fault.
I used MySql 5.0.19, MyOdbc 3.51.12 and UnixOdbc 2.2.11.
My ODBC DSN uses the /usr/lib/libmyodbc3_r.so.

I also tried recompiling myodbc but the compilation failed with error telling
that there are multiple definition of adler32.

Is there any workarround to that blocking situation ?

to reproduce the pb:

testodbc -s sourcename -u user -p password -h numthreads

Here is the source of my testodbc program:

#include <pthread.h>
#include <stdio.h>
#include <sql.h>        // core
#include <sqlext.h>     // extensions
#include <string>

using namespace std;

// Thread mutex
	pthread_mutex_t m_mMutex;

// End Thread flag
int EndThread = 0;
int NumErrors = 0;

// Num threads
int NumThreads = 1;

SQLCHAR	Source[255];	// Data source name
SQLCHAR	User[255];		// Data source user
SQLCHAR	Password[255];	// Data source password
SQLCHAR	Table[255];		// Data source table

void DisplaySqlError(SQLHENV henv, SQLHDBC hdbc, SQLHSTMT hstmt);
int DisplaySqlStringInfo(SQLHENV henv, SQLHDBC hdbc, SQLUSMALLINT InfoType);
int DoConnect(bool WithInfo, bool WithSearch);
void* ThreadMain(void *Value);

int main(int argc, char* argv[])
{
	if(argc <=1)
	{
		printf("usage: testodbc -s DataSource -u User [-p Password] [-t Table] [-h
NumThreads]\n");
		return 0;
	}
	Source[0] = '\0';
	User[0] = '\0';
	Password[0] = '\0';
	int i;
	for(i=1;i<argc;i++)
	{
		if(strcmp(argv[i], "-s") == 0)
		{
			if(i==(argc-1))
			{
				printf("usage: testodbc -s DataSource -u User [-p Password] [-t Table] [-h
NumThreads]\n");
				return 0;
			}
			i++;
			strcpy((char *) Source, argv[i]);
		}
		else if(strcmp(argv[i], "-u") == 0)
		{
			if(i==(argc-1))
			{
				printf("usage: testodbc -s DataSource -u User [-p Password] [-t Table] [-h
NumThreads]\n");
				return 0;
			}
			i++;
			strcpy((char *) User, argv[i]);
		}
		else if(strcmp(argv[i], "-p") == 0)
		{
			if(i==(argc-1))
			{
				printf("usage: testodbc -s DataSource -u User [-p Password] [-t Table] [-h
NumThreads]\n");
				return 0;
			}
			i++;
			strcpy((char *) Password, argv[i]);
		}
		else if(strcmp(argv[i], "-t") == 0)
		{
			if(i==(argc-1))
			{
				printf("usage: testodbc -s DataSource -u User [-p Password] [-t Table] [-h
NumThreads]\n");
				return 0;
			}
			i++;
			strcpy((char *) Table, argv[i]);
		}
		else if(strcmp(argv[i], "-h") == 0)
		{
			if(i==(argc-1))
			{
				printf("usage: testodbc -s DataSource -u User [-p Password] [-t Table] [-h
NumThreads]\n");
				return 0;
			}
			i++;
			NumThreads = atoi(argv[i]);
			if(NumThreads <= 0)
				NumThreads = 1;
			if(NumThreads > 500)
				NumThreads = 500;
		}
	}

	if(!DoConnect(true, false))
		return 0;

	pthread_mutex_init(&m_mMutex, NULL);

	EndThread = NumThreads;
	int EndThreadHere = EndThread;
	for(i=0;i<NumThreads;i++)
	{
		pthread_attr_t	attr;
		pthread_attr_init(&attr);
		pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);		// the unix thread has a
system scope for best performances
		pthread_t thid;
		pthread_create(&thid, &attr, ThreadMain, NULL);
		pthread_attr_destroy(&attr);
	}
	while(EndThreadHere)
	{
		pthread_mutex_lock(&m_mMutex);
		EndThreadHere = EndThread;
		pthread_mutex_unlock(&m_mMutex);
	}

	if(NumErrors)
		printf("\nProgram terminated with %d errors\n", NumErrors);
	else
		printf("\nProgram terminated successfully\n");

	pthread_mutex_unlock(&m_mMutex);
	pthread_mutex_destroy(&m_mMutex);

	return 0;
}

void* ThreadMain(void *Value)
{
	int RetVal = DoConnect(false, true);

	pthread_mutex_lock(&m_mMutex);
	if(!RetVal)
		NumErrors++;
	EndThread--;
	pthread_mutex_unlock(&m_mMutex);

	pthread_detach(pthread_self());
	pthread_exit(NULL);

	return (void *)(0);			// end of thread (unix)
}

int DoConnect(bool WithInfo, bool WithSearch)
{
	SQLHENV     henv;	/* environment handle */
	SQLHDBC     hdbc;	/* connection handle */
	SQLHSTMT    hstmt;	/* statement handle */
	SQLRETURN   retcode;	/* return code */
	SQLCHAR SQLStmt[255];

	henv = NULL;
	hdbc = NULL;
	hstmt = NULL;

	retcode = SQLAllocEnv(&henv);
	retcode = SQLAllocConnect(henv, &hdbc);
	retcode = SQLConnect(hdbc, Source, SQL_NTS, User, SQL_NTS, Password, SQL_NTS);

	if (retcode != SQL_SUCCESS)
	{
		printf("*** Cannot connect data source\n");
		DisplaySqlError(henv, hdbc, hstmt);
		SQLFreeConnect(hdbc);
		SQLFreeEnv(henv);
		return 0;
	}
	if(WithInfo)
	{
		printf("Database name: ");
		DisplaySqlStringInfo(henv, hdbc,SQL_DBMS_NAME);
		printf("\nDatabase version: ");
		DisplaySqlStringInfo(henv, hdbc,SQL_DBMS_VER);
		printf("\nDriver name: ");
		DisplaySqlStringInfo(henv, hdbc,SQL_DRIVER_NAME);
		printf("\nDriver version: ");
		DisplaySqlStringInfo(henv, hdbc,SQL_DRIVER_VER);
		printf("\nDriver ODBC version: ");
		DisplaySqlStringInfo(henv, hdbc,SQL_ODBC_VER);
		printf("\n");
	}
	if(WithSearch)
	{
		if(strlen((char *)Table))
		{
			retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc,&hstmt);
			if (retcode == SQL_SUCCESS)
			{
				strcpy((char *)SQLStmt, "SELECT * FROM ");
				strcat((char *)SQLStmt, (char *)Table);
				retcode = SQLExecDirect(hstmt, SQLStmt, SQL_NTS);
				// Display The Results Of The SQL Query
				if (retcode == SQL_SUCCESS)
					printf("Database search successful\n");
				else
				{
					printf("*** Error searching database\n");
					DisplaySqlError(henv, hdbc, hstmt);
					SQLDisconnect(hdbc);
					SQLFreeConnect(hdbc);
					SQLFreeEnv(henv);
					return 0;
				}
			}
			// Free The SQL Statement Handle
			if(hstmt != NULL)
				SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
		}
	}

	SQLDisconnect(hdbc);
	SQLFreeConnect(hdbc);
	SQLFreeEnv(henv);

	return 1;
}

void DisplaySqlError(SQLHENV henv, SQLHDBC hdbc, SQLHSTMT hstmt)
{
	SQLCHAR	Message[SQL_MAX_MESSAGE_LENGTH + 1];
	SQLCHAR	State[10];
	SQLINTEGER NativeError;
	SQLSMALLINT TextLength;

	Message[0] = '\0';
	SQLError(henv, hdbc, hstmt, State, &NativeError, Message,
SQL_MAX_MESSAGE_LENGTH+1, &TextLength);

	printf("SQL error: %s\n", (const char *)Message);
}

int DisplaySqlStringInfo(SQLHENV henv, SQLHDBC hdbc, SQLUSMALLINT InfoType)
{
	SQLRETURN   retcode;	/* return code */
	SQLCHAR    info[255];	/* info string for SQLGetInfo */
	SQLSMALLINT cbInfoValue;

	info[0] = '\0';
	retcode = SQLGetInfo(hdbc,InfoType, &info, sizeof(info),&cbInfoValue);
	if (retcode == SQL_SUCCESS)
		printf((const char *)info);
	else
	{
		printf("*** Cannot get info from data source\n");
		DisplaySqlError(henv, hdbc, NULL);
		return 0;
	}
	return 1;
}

makefile:

CXX = g++
CXXFLAGS = -I- -I./ -D_REENTRANT -D_GNU_SOURCE -DLINUX
LDFLAGS = -L./ -lpthread -ldl -lodbc

testodbc :: testodbc.o
	$(CXX) -o $@ $^ $(LDFLAGS)
	strip $@

testodbc.o : testodbc.cpp
	$(CXX) -c -o $@ $< $(CXXFLAGS)
[13 Apr 2006 19:29] Mark Smith
I am 95% sure this is a duplicate of bug 11892 (I have been debugging this same problem for a day now).
[11 May 2006 16:50] Paul DuBois
Not enough information is given to produce a changelog entry.
What is the version number for the fix? Is the problem reported
actually the bug that was fixed?  Thanks.
[8 Mar 2007 1:25] Jim Winstead
This is a duplicate of Bug #11892.