From efce30bb4309ee0ae6af35b8fe6f62ccfb67622c Mon Sep 17 00:00:00 2001 From: Christian Ehrlicher Date: Fri, 3 Feb 2023 22:20:28 +0100 Subject: ODBC/SQL: Fix usage of SQLGetData SQLGetData was called with a nullptr for the output buffer to determine the buffer length which is not allowed. Fix it by passing a valid buffer with a buffer size of 0. Fixes: QTBUG-70362 Fixes: QTBUG-110803 Change-Id: I4d547383100714901a4e2ca3b4777326cfab12fe Reviewed-by: Thiago Macieira --- src/plugins/sqldrivers/odbc/qsql_odbc.cpp | 143 ++++++++++-------------------- 1 file changed, 49 insertions(+), 94 deletions(-) (limited to 'src/plugins/sqldrivers/odbc') diff --git a/src/plugins/sqldrivers/odbc/qsql_odbc.cpp b/src/plugins/sqldrivers/odbc/qsql_odbc.cpp index a90125af14..e73b573e42 100644 --- a/src/plugins/sqldrivers/odbc/qsql_odbc.cpp +++ b/src/plugins/sqldrivers/odbc/qsql_odbc.cpp @@ -370,114 +370,69 @@ static QMetaType qDecodeODBCType(SQLSMALLINT sqltype, bool isSigned = true) return QMetaType(type); } -static QVariant qGetStringData(SQLHANDLE hStmt, int column, int colSize, bool unicode) +template +static QVariant getStringDataImpl(SQLHANDLE hStmt, SQLUSMALLINT column, qsizetype colSize, SQLSMALLINT targetType) { QString fieldVal; SQLRETURN r = SQL_ERROR; SQLLEN lengthIndicator = 0; - - // NB! colSize must be a multiple of 2 for unicode enabled DBs - if (colSize <= 0) { - colSize = 256; - } else if (colSize > 65536) { // limit buffer size to 64 KB - colSize = 65536; - } else { - colSize++; // make sure there is room for more than the 0 termination - } - if (unicode) { + QVarLengthArray buf(colSize); + while (true) { r = SQLGetData(hStmt, - column+1, - SQL_C_TCHAR, - NULL, - 0, - &lengthIndicator); - if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && lengthIndicator > 0) - colSize = int(lengthIndicator / sizeof(SQLTCHAR) + 1); - QVarLengthArray buf(colSize); - while (true) { - r = SQLGetData(hStmt, - column+1, - SQL_C_TCHAR, - (SQLPOINTER)buf.data(), - colSize*sizeof(SQLTCHAR), - &lengthIndicator); - if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) { - if (lengthIndicator == SQL_NULL_DATA) { - return {}; - } - // starting with ODBC Native Client 2012, SQL_NO_TOTAL is returned - // instead of the length (which sometimes was wrong in older versions) - // see link for more info: http://msdn.microsoft.com/en-us/library/jj219209.aspx - // if length indicator equals SQL_NO_TOTAL, indicating that - // more data can be fetched, but size not known, collect data - // and fetch next block - if (lengthIndicator == SQL_NO_TOTAL) { - fieldVal += fromSQLTCHAR(buf, colSize); - continue; - } - // if SQL_SUCCESS_WITH_INFO is returned, indicating that - // more data can be fetched, the length indicator does NOT - // contain the number of bytes returned - it contains the - // total number of bytes that CAN be fetched - int rSize = (r == SQL_SUCCESS_WITH_INFO) ? colSize : int(lengthIndicator / sizeof(SQLTCHAR)); - fieldVal += fromSQLTCHAR(buf, rSize); - if (lengthIndicator < SQLLEN(colSize*sizeof(SQLTCHAR))) { - // workaround for Drivermanagers that don't return SQL_NO_DATA - break; - } - } else if (r == SQL_NO_DATA) { - break; - } else { - qWarning() << "qGetStringData: Error while fetching data (" << qWarnODBCHandle(SQL_HANDLE_STMT, hStmt) << ')'; + column + 1, + targetType, + SQLPOINTER(buf.data()), SQLINTEGER(buf.size() * sizeof(CT)), + &lengthIndicator); + if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) { + if (lengthIndicator == SQL_NULL_DATA) { return {}; } - } - } else { - r = SQLGetData(hStmt, - column+1, - SQL_C_CHAR, - NULL, - 0, - &lengthIndicator); - if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && lengthIndicator > 0) - colSize = lengthIndicator + 1; - QVarLengthArray buf(colSize); - while (true) { - r = SQLGetData(hStmt, - column+1, - SQL_C_CHAR, - (SQLPOINTER)buf.data(), - colSize, - &lengthIndicator); - if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) { - if (lengthIndicator == SQL_NULL_DATA || lengthIndicator == SQL_NO_TOTAL) { - return {}; - } - // if SQL_SUCCESS_WITH_INFO is returned, indicating that - // more data can be fetched, the length indicator does NOT - // contain the number of bytes returned - it contains the - // total number of bytes that CAN be fetched - qsizetype rSize = (r == SQL_SUCCESS_WITH_INFO) ? colSize : lengthIndicator; - // Remove any trailing \0 as some drivers misguidedly append one - int realsize = qMin(rSize, buf.size()); - if (realsize > 0 && buf[realsize - 1] == 0) - realsize--; - fieldVal += QString::fromUtf8(reinterpret_cast(buf.constData()), realsize); - if (lengthIndicator < SQLLEN(colSize)) { - // workaround for Drivermanagers that don't return SQL_NO_DATA - break; - } - } else if (r == SQL_NO_DATA) { + // starting with ODBC Native Client 2012, SQL_NO_TOTAL is returned + // instead of the length (which sometimes was wrong in older versions) + // see link for more info: http://msdn.microsoft.com/en-us/library/jj219209.aspx + // if length indicator equals SQL_NO_TOTAL, indicating that + // more data can be fetched, but size not known, collect data + // and fetch next block + if (lengthIndicator == SQL_NO_TOTAL) { + fieldVal += fromSQLTCHAR, sizeof(CT)>(buf, buf.size()); + continue; + } + // if SQL_SUCCESS_WITH_INFO is returned, indicating that + // more data can be fetched, the length indicator does NOT + // contain the number of bytes returned - it contains the + // total number of bytes that CAN be fetched + const qsizetype rSize = (r == SQL_SUCCESS_WITH_INFO) + ? buf.size() + : qsizetype(lengthIndicator / sizeof(CT)); + fieldVal += fromSQLTCHAR, sizeof(CT)>(buf, rSize); + // lengthIndicator does not contain the termination character + if (lengthIndicator < SQLLEN((buf.size() - 1) * sizeof(CT))) { + // workaround for Drivermanagers that don't return SQL_NO_DATA break; - } else { - qWarning() << "qGetStringData: Error while fetching data (" << qWarnODBCHandle(SQL_HANDLE_STMT, hStmt) << ')'; - return {}; } + } else if (r == SQL_NO_DATA) { + break; + } else { + qWarning() << "qGetStringData: Error while fetching data (" << qWarnODBCHandle(SQL_HANDLE_STMT, hStmt) << ')'; + return {}; } } return fieldVal; } +static QVariant qGetStringData(SQLHANDLE hStmt, SQLUSMALLINT column, int colSize, bool unicode) +{ + if (colSize <= 0) { + colSize = 256; // default Prealloc size of QVarLengthArray + } else if (colSize > 65536) { // limit buffer size to 64 KB + colSize = 65536; + } else { + colSize++; // make sure there is room for more than the 0 termination + } + return unicode ? getStringDataImpl(hStmt, column, colSize, SQL_C_TCHAR) + : getStringDataImpl(hStmt, column, colSize, SQL_C_CHAR); +} + static QVariant qGetBinaryData(SQLHANDLE hStmt, int column) { QByteArray fieldVal; -- cgit v1.2.3