Add ASAN and Valgrind integration for CI test suite#289
Add ASAN and Valgrind integration for CI test suite#289fdcastel wants to merge 5 commits intoFirebirdSQL:masterfrom
Conversation
On Linux, sizeof(wchar_t) = 4 bytes but sizeof(SQLWCHAR) = 2 bytes
(unixODBC defines SQLWCHAR as unsigned short). The ConvertingString
constructor used sizeof(wchar_t) to convert a byte-count argument
into the number of narrow characters needed:
lengthString = length / sizeof(wchar_t); // = 12/4 = 3 on Linux
For SQLGetDiagRecW the state buffer is declared as State(12, sqlState),
giving lengthString=3 and Alloc() allocating 3+2=5 bytes. strcpy()
then writes the 5-character SQL state plus its NUL terminator (6 bytes)
into that 5-byte buffer, producing a 1-byte heap-buffer-overflow caught
by AddressSanitizer.
The same latent bug exists in SQLErrorW (same State(12, sqlState) pattern).
Fix: divide by sizeof(SQLWCHAR) instead of sizeof(wchar_t).
sizeof(SQLWCHAR) == 2 on all platforms (Windows: SQLWCHAR=wchar_t=2;
Linux/unixODBC: SQLWCHAR=unsigned short=2), so the formula now yields:
lengthString = 12 / sizeof(SQLWCHAR) = 6
and Alloc() allocates 6+2=8 bytes, comfortably holding the SQL state.
On Windows sizeof(wchar_t)==sizeof(SQLWCHAR)==2, so this change is
a no-op there.
Found by: AddressSanitizer (introduced in CI via PR FirebirdSQL#288/FirebirdSQL#289)
Test: DataTypeTest.SmallintRoundTrip -> ExecIgnoreError -> SQLExecDirect
-> unixODBC dispatcher -> SQLGetDiagRecW -> sqlGetDiagRec(strcpy)
Bug found and fixed by ASANThe ASAN CI job introduced by this PR immediately caught a real memory bug in the driver. What ASAN reportedThe first ASAN run failed on The full output is in the CI job log: GitHub Actions -> Build and Test -> job Root cause
ConvertingString<> State( 12, sqlState );In lengthString = length / sizeof(wchar_t); // BUGOn Windows
The bug was silently harmless on Windows and went undetected for years. FixOne-line change in // Before
lengthString = length / sizeof(wchar_t);
// After
lengthString = length / sizeof(SQLWCHAR);
CI after fixAll 4 matrix jobs now pass with no
This is a concrete demonstration that ASAN integration pays off immediately. |
|
@irodushka Enjoy! 😉 |
|
Hi @fdcastel Can you please resolve the conflicts after merging PR#286 Cheers! |
Sure! I’ll look into this in a few hours. |
Add CMake options ENABLE_ASAN and ENABLE_VALGRIND (Linux/GCC/Clang only, mutually exclusive) with corresponding compiler/linker flags and CTest memcheck configuration. CMake changes: - ENABLE_ASAN appends -fsanitize=address -fno-omit-frame-pointer to compile and link flags; suppresses -DLOGGING in Debug builds for cleaner sanitizer output - ENABLE_VALGRIND finds the valgrind binary and configures MEMORYCHECK_COMMAND for ctest -T memcheck - Mutual exclusion enforced via FATAL_ERROR CMake presets: - Add 'asan' and 'valgrind' configure/build/test presets inheriting from 'debug', with ASAN_OPTIONS env and Valgrind timeout multiplier Build script (firebird-odbc-driver.build.ps1): - Add -Sanitizer parameter (None, Asan, Valgrind) that passes the corresponding CMake option; sets ASAN_OPTIONS at runtime; skips sanitizers on Windows with a warning CI (build-and-test.yml): - Add Linux x64 ASAN matrix entry (ubuntu-22.04, Debug) - Add Linux x64 Valgrind matrix entry (ubuntu-22.04, Debug) with valgrind package installation - Sanitizer jobs do not upload release artifacts Also adds valgrind.supp suppressions file (seeded with fbclient rule). Closes FirebirdSQL#288
LSAN_OPTIONS referenced a relative 'lsan.supp' path but CTest runs from the build directory. Use an absolute path derived from the source/workspace root so the file is always found. Also adds the lsan.supp suppressions file with initial rules for libfbclient, libodbc, and libodbcinst.
ASAN correctly detected a pre-existing heap-buffer-overflow in OdbcError::sqlGetDiagRec (strcpy into an undersized ConvertingString buffer). This proves the sanitizer integration works, but the existing bug should not block PRs. Mark the ASAN matrix entry continue-on-error: true until the underlying memory bugs are fixed in a separate PR.
On Linux, sizeof(wchar_t) = 4 bytes but sizeof(SQLWCHAR) = 2 bytes
(unixODBC defines SQLWCHAR as unsigned short). The ConvertingString
constructor used sizeof(wchar_t) to convert a byte-count argument
into the number of narrow characters needed:
lengthString = length / sizeof(wchar_t); // = 12/4 = 3 on Linux
For SQLGetDiagRecW the state buffer is declared as State(12, sqlState),
giving lengthString=3 and Alloc() allocating 3+2=5 bytes. strcpy()
then writes the 5-character SQL state plus its NUL terminator (6 bytes)
into that 5-byte buffer, producing a 1-byte heap-buffer-overflow caught
by AddressSanitizer.
The same latent bug exists in SQLErrorW (same State(12, sqlState) pattern).
Fix: divide by sizeof(SQLWCHAR) instead of sizeof(wchar_t).
sizeof(SQLWCHAR) == 2 on all platforms (Windows: SQLWCHAR=wchar_t=2;
Linux/unixODBC: SQLWCHAR=unsigned short=2), so the formula now yields:
lengthString = 12 / sizeof(SQLWCHAR) = 6
and Alloc() allocates 6+2=8 bytes, comfortably holding the SQL state.
On Windows sizeof(wchar_t)==sizeof(SQLWCHAR)==2, so this change is
a no-op there.
Found by: AddressSanitizer (introduced in CI via PR FirebirdSQL#288/FirebirdSQL#289)
Test: DataTypeTest.SmallintRoundTrip -> ExecIgnoreError -> SQLExecDirect
-> unixODBC dispatcher -> SQLGetDiagRecW -> sqlGetDiagRec(strcpy)
The heap-buffer-overflow in ConvertingString (sizeof(wchar_t) vs sizeof(SQLWCHAR)) has been fixed. ASAN now passes all 375 tests cleanly on ubuntu-22.04/GCC, so the soft-fail guard is no longer needed.
|
@irodushka Rebased onto current Conflicts were in Resolution: kept all new upstream matrix entries and added the sanitizer entries on top; merged the |

Summary
Integrate AddressSanitizer (ASAN) and Valgrind into the CI test pipeline so that memory bugs (buffer overflows, use-after-free, leaks, etc.) are caught automatically on every PR.
Closes #288
Changes
CMake (
CMakeLists.txt)ENABLE_ASANoption (default OFF, Linux/GCC/Clang only): appends-fsanitize=address -fno-omit-frame-pointerto compile and link flags. Suppresses-DLOGGINGin Debug builds for cleaner sanitizer output.ENABLE_VALGRINDoption (default OFF): finds thevalgrindbinary and configuresMEMORYCHECK_COMMAND/MEMORYCHECK_COMMAND_OPTIONSforctest -T memcheck.FATAL_ERROR.if(NOT MSVC)since MSVC ASAN has different semantics (future work).CMake Presets (
CMakePresets.json)asanconfigure/build/test presets inheriting fromdebug, withASAN_OPTIONSenvironment variable.valgrindconfigure/build/test presets inheriting fromdebug, with a 600s test timeout (Valgrind is ~10-20x slower).Build script (
firebird-odbc-driver.build.ps1)-Sanitizerparameter acceptingNone,Asan,Valgrind.-DENABLE_ASAN=ONor-DENABLE_VALGRIND=ONto CMake.ASAN_OPTIONS/LSAN_OPTIONSenvironment variables at runtime for ASAN builds.CI (
build-and-test.yml)valgrindpackage installation.ASAN_OPTIONSandLSAN_OPTIONSenvironment variables.Valgrind suppressions (
valgrind.supp)libfbclient.soglobal initialization leaks.Local usage