Skip to content

Fix SQL_C_GUID parameter binding sending corrupted data on the wire (#295)#296

Open
fdcastel wants to merge 2 commits intoFirebirdSQL:masterfrom
fdcastel:fix/issue-295-sql-c-guid-param-binding
Open

Fix SQL_C_GUID parameter binding sending corrupted data on the wire (#295)#296
fdcastel wants to merge 2 commits intoFirebirdSQL:masterfrom
fdcastel:fix/issue-295-sql-c-guid-param-binding

Conversation

@fdcastel
Copy link
Copy Markdown
Member

@fdcastel fdcastel commented Apr 27, 2026

Summary

Fixes #295. When an ODBC application called SQLBindParameter(SQL_C_GUID, SQL_GUID, ...) and provided a 16-byte UUID, the driver accepted the call but silently sent corrupt data on the wire — either ASCII bytes of the canonical UUID truncated to 16 chars (BINARY(16) target) or wide-char UTF-16 interpreted as narrow text (VARCHAR target).

The fix:

  • Adds convGuidToBinary that emits 16 bytes in canonical UUID byte order (Data1/Data2/Data3 swapped from x86 little-endian to big-endian, Data4 unchanged).
  • Reworks the SQL_C_GUID dispatch in OdbcConvert::getAdressFunction to choose between binary and text wire formats based on the IPD's sqlsubtype/sqllen rather than the application-side conciseType.
  • For text-wire targets, calls setTypeText() so SQL_VARYING is converted to SQL_TEXT, then writes the canonical UUID into the DescRecord's local buffer and redirects the wire's sqldata via setSqlData() — matching the pattern transferStringToAllowedType already uses, so we don't depend on the SQLDA-allocated buffer being large enough (an untyped ? placeholder is described by Firebird as VARCHAR(0), so its wire buffer is essentially nothing).
  • Exposes getSqlSubtype() / getSqlLen() on HeadSqlVar so the dispatch can detect CHAR(16) CHARACTER SET OCTETS / BINARY(16) targets reliably.

This is the parameter-binding piece of #287 T5-5; the column-side SQL_GUID type mapping (SQLDescribeCol returning SQL_GUID for CHAR(16) OCTETS columns) is still open and out of scope here.

Tests

Three new acceptance tests in tests/test_guid_and_binary.cpp exercise the issue's exact reproducers:

  • GuidParamBindingTest.BindGuidToCharOctets16 — SQL_C_GUID into CHAR(16) CHARACTER SET OCTETS, round-tripped via UUID_TO_CHAR.
  • GuidParamBindingTest.BindGuidToVarcharViaCharToUuid — SQL_C_GUID into VARCHAR via CHAR_TO_UUID(?) (reproducer 2 from the issue).
  • GuidParamBindingTest.BindGuidToUuidToCharRoundtrip — SQL_C_GUID into BINARY(16) via UUID_TO_CHAR(?) (reproducer 1).

All three tests pass on the FB master matrix jobs (Windows x64/x86/ARM64, Ubuntu x64/ARM64). Existing test suite (202 active tests) shows zero regressions locally.

SQLBindParameter with SQL_C_GUID was accepted, but the driver did not
convert the C-side 16-byte GUID into Firebird's wire format. The data
Firebird saw was either ASCII bytes of the canonical UUID truncated to
16 chars (BINARY(16) target), or wide-char UTF-16 interpreted as
narrow text (VARCHAR target).

Add convGuidToBinary that emits 16 bytes in canonical UUID byte order
(Data1/2/3 swapped from x86 little-endian to big-endian, Data4 as-is)
and rework the SQL_C_GUID dispatch in getAdressFunction to choose
between binary and text wire formats based on the IPD's sqlsubtype/
sqllen rather than the application-side conciseType. For text wire,
setTypeText() is now applied so SQL_VARYING is converted to SQL_TEXT
before the canonical UUID is written without a length prefix; canonical
UUIDs are pure ASCII so the same 36 narrow bytes work for UTF8 wires.

Expose getSqlSubtype()/getSqlLen() on HeadSqlVar so the dispatch can
detect CHAR(16) CHARACTER SET OCTETS / BINARY(16) targets.

Add three GuidParamBindingTest acceptance tests that exercise the
issue's reproducers: SQL_C_GUID into CHAR(16) OCTETS, into VARCHAR via
CHAR_TO_UUID(?), and into BINARY(16) via UUID_TO_CHAR(?).

Closes FirebirdSQL#295.
Untyped `?` placeholders are described by Firebird as VARCHAR(0), so the
wire-side SQLDA buffer holds at most 2 bytes (the length prefix) before
the parameter type is bound. Writing 36 bytes of canonical UUID into
that buffer overflowed on Linux/master builds (the failure surfaced as
CHAR_TO_UUID seeing an empty argument).

Switch convGuidToString in wire mode to allocate the DescRecord's local
buffer, write into it, and redirect the wire's sqldata via setSqlData().
Same pattern transferStringToAllowedType already uses for app→wire
string transfers, so checkAndRebuild copies our 36 bytes into the
freshly-sized execBuffer regardless of the original SQLDA precision.
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.

SQL_C_GUID parameter binding sends corrupted data on the wire (Phase 8 / T5-5)

1 participant