Skip to content

Fix SIGSEGV in SQLCopyDesc when source descriptor is empty#297

Open
fdcastel wants to merge 1 commit intoFirebirdSQL:masterfrom
fdcastel:fix/sqlcopydesc-empty-descriptor-segv
Open

Fix SIGSEGV in SQLCopyDesc when source descriptor is empty#297
fdcastel wants to merge 1 commit intoFirebirdSQL:masterfrom
fdcastel:fix/sqlcopydesc-empty-descriptor-segv

Conversation

@fdcastel
Copy link
Copy Markdown
Member

@fdcastel fdcastel commented Apr 27, 2026

Summary

SQLCopyDesc crashed with SIGSEGV (Linux) / access violation 0xC0000005 (Windows) when the source descriptor had no records — the typical case when the application has fetched an implicit ARD without binding any columns, or freshly allocated an explicit descriptor.

Reproducer:

SQLHSTMT s1, s2;
SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &s1);
SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &s2);

SQLHDESC ard1, ard2;
SQLGetStmtAttr(s1, SQL_ATTR_APP_ROW_DESC, &ard1, 0, NULL);
SQLGetStmtAttr(s2, SQL_ATTR_APP_ROW_DESC, &ard2, 0, NULL);

SQLCopyDesc(ard1, ard2);   // segfaults

Caught by odbc-crusher v3.5.0-rc1 stress-test (test_copy_desc in the Descriptor Tests category — the run hung on a corrupted DM after the SEGV, holding the runner for 47 minutes before being cancelled).

Root cause

OdbcDesc::operator= (the function SQLCopyDesc calls into) iterated sour.records[n] without first checking that sour.records was non-NULL:

for ( int n = 0 ; n <= headCount ; n++ )
{
    DescRecord *srcrec = sour.records[n];   // ← deref before null-check
    ...
    if ( srcrec ) { ... }                   // null-check is too late
}

sour.records is NULL for any descriptor that has never had records allocated — implicit ARDs/APDs of statements with no SQLBindCol/SQLBindParameter calls, or freshly-allocated explicit descriptors. For an empty source headCount == 0, the loop runs once with n = 0, dereferences NULL[0], crash.

Every other loop in OdbcDesc.cpp already guards if (records) (lines 104, 119, 148, 162, 181). This one was missing the guard — fixed by inlining the same check on the access.

Tests

The Phase 7 regression suite (tests/test_phase7_crusher_fixes.cpp) already shipped tests for this exact crash — its inline comment at lines 33-34 documents the failure mode word-for-word. The tests were GTEST_SKIP'd pending the fix.

This PR un-skips the three tests that the null-guard alone enables:

  • CopyDescCrashTest.CopyEmptyARDDoesNotCrash
  • CopyDescCrashTest.CopyEmptyToExplicitDescriptor
  • CopyDescCrashTest.CopyPopulatedThenEmpty

The four SetDescCount* tests in the same file remain skipped — they exercise a separate Phase 7 issue (SQLSetDescField(SQL_DESC_COUNT) not allocating the records array) that needs a different fix and is out of scope here.

Verification

Test plan

  • Three previously-SKIP'd regression tests now run and pass across the full CI matrix
  • All other tests still pass — no regressions
  • odbc-crusher v3.5.0-rc1 against the patched .so completes cleanly: Descriptor Tests now reports 5 passed (was 1 DRIVER CRASH); category-completion verified in odbc-crusher run 25025482604

OdbcDesc::operator= dereferenced sour.records[n] inside the copy loop
without first checking that sour.records was non-NULL. Every other
loop in OdbcDesc.cpp guards `if (records)`; this one did not.

Reproducer: allocate two statements, fetch their implicit ARDs (no
SQLBindCol calls), call SQLCopyDesc on them. Both descriptors have
records == NULL and headCount == 0, the loop runs once with n=0,
and sour.records[0] crashes with access violation.

The Phase 7 regression tests for this bug already shipped (skipped),
documenting the exact failure mode at lines 33-34. Un-skip the three
tests this fix enables: CopyEmptyARDDoesNotCrash,
CopyEmptyToExplicitDescriptor, CopyPopulatedThenEmpty. The four
SetDescCount* tests in the same file remain skipped — they exercise
a separate Phase 7 fix (SQLSetDescField(SQL_DESC_COUNT) must allocate
records) that is not part of this PR.

Reported by odbc-crusher v3.5.0-rc1 stress test.
@fdcastel
Copy link
Copy Markdown
Member Author

@irodushka Similar to #296 — backported from the v4 plan.

This isn’t urgent, but it would be great if it could also make it into -rc2.

It would enable us to run odbc-crusher on the Firebird ODBC driver.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant