postgresql-interfaces/psqlodbc GitHub issues and pull requests (mirror)  
help / color / mirror / Atom feed
From: davecramer (@davecramer) <[email protected]>
To: postgresql-interfaces/psqlodbc <[email protected]>
Subject: Re: [postgresql-interfaces/psqlodbc] PR #183: Detach freed app descriptors from statements
Date: Fri, 08 May 2026 10:16:04 +0000
Message-ID: <[email protected]> (raw)
In-Reply-To: <[email protected]>
References: <[email protected]>

This PR fixes a use-after-free vulnerability where freeing an explicitly-allocated application descriptor
  (SQLFreeHandle(SQL_HANDLE_DESC)) leaves dangling pointers in statements that had the descriptor attached via
  SQL_ATTR_APP_ROW_DESC / SQL_ATTR_APP_PARAM_DESC. Subsequent operations on those statements dereference freed memory.
  
  The Bug
  
  1. App allocates a descriptor with SQLAllocHandle(SQL_HANDLE_DESC)
  2. App attaches it to a statement via SQLSetStmtAttr(SQL_ATTR_APP_ROW_DESC, hdesc)
  3. stmt->ard now points to the allocated descriptor
  4. App frees the descriptor with SQLFreeHandle(SQL_HANDLE_DESC)
  5. stmt->ard still points to freed memory
  6. Any subsequent access via SC_get_ARDF(stmt) → heap-use-after-free
  
  The Fix
  
  A new CC_detach_descriptor() function iterates all statements on the connection and resets any ard/apd pointer
  matching the descriptor being freed back to the statement's implicit (embedded) descriptor (stmt->ardi /
  stmt->apdi).
  
  Analysis
  
  Correctness:
  
  - The detach logic is correct: stmt->ard = &(stmt->ardi) and stmt->apd = &(stmt->apdi) restore the implicit
  descriptors, which is exactly what the ODBC spec requires when an explicit descriptor is freed.
  - The function is called before DC_Destructor(desc) and free(desc), so the pointer comparison is valid.
  - The embedded flag and conn pointer are saved before DC_Destructor is called. This is important because
  DC_Destructor frees internal fields. In practice DC_Destructor doesn't clear conn_conn, but saving it upfront is
  defensive and correct.
  - The !embedded guard ensures this only runs for explicitly-allocated descriptors (not the implicit ones embedded in
  statements).
  
  Thread safety:
  
  - CONNLOCK_ACQUIRE(conn) / CONNLOCK_RELEASE(conn) protects the iteration over conn->stmts[]. This is the same lock
  used elsewhere when modifying the statement list, so it's the right synchronization primitive.
  - However, there's a subtle window: between CC_detach_descriptor() returning and DC_Destructor(desc) being called,
  another thread could theoretically re-attach the same descriptor to a statement (via SQLSetStmtAttr). This is an
  application error (using a handle after freeing it), so it's not a driver bug — but worth noting.
  
  Order of operations in PGAPI_FreeDesc:
  
  The PR reorders the existing code slightly:
  
  // Before (original):
  DC_Destructor(desc);
  if (!desc->deschd.embedded) { /* remove from conn->descs[] and free */ }
  
  // After (PR):
  const char embedded = desc->deschd.embedded;
  ConnectionClass *conn = DC_get_conn(desc);
  if (!embedded) CC_detach_descriptor(conn, desc);
  DC_Destructor(desc);
  if (!embedded) { /* remove from conn->descs[] and free */ }
  
  This is correct — embedded and conn must be read before DC_Destructor potentially invalidates internal state, and
  detachment must happen while the pointer is still valid for comparison.
  
  Issues / Suggestions
  
  1. IRD/IPD not handled: The fix only detaches ard and apd. The implementation row/parameter descriptors (ird/ipd)
  are not checked. This is correct because ODBC only allows applications to explicitly allocate and attach ARD/APD —
  IRD and IPD are always implicit. No issue here, just confirming the design is intentional. ✅
  2. No expected output file for the test: The test is added to test/tests but there's no corresponding
  test/expected/descriptors-free.out file in the diff. Looking at other tests in this project, each test binary
  typically has a matching .out file. If the test harness compares stdout against expected output, this will fail. The
  test prints:
  
     freeing attached application row descriptor
     freeing attached application parameter descriptor
  
     An expected output file should be added.
  
  3. Multiple statements with same descriptor: The fix correctly iterates all statements on the connection, not just
  one. ODBC allows the same descriptor to be attached to multiple statements, so this is necessary and correct. ✅
  4. Test coverage is good: The test exercises both ARD and APD paths, verifies the handle is no longer returned by
  SQLGetStmtAttr, and then exercises a write through the descriptor (setting array/paramset size) to confirm the
  implicit descriptor is functional. The ASan verification in the PR description confirms the UAF is caught without
  the fix.
  5. Minor: const char embedded: Using char for a boolean is fine (matches the existing deschd.embedded type), but
  BOOL or int would be more idiomatic for this codebase. Very minor style nit.

view thread (4+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: github://postgresql-interfaces/psqlodbc
  Cc: [email protected], [email protected]
  Subject: Re: [postgresql-interfaces/psqlodbc] PR #183: Detach freed app descriptors from statements
  In-Reply-To: <<[email protected]>>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox