Potential Overflow, Memory Management, and Assertion Issues in SQLite Components


Issue Overview: Overflow Risks, Variadic Handling, Memory Corruption, and Invalid Index Assertions

The SQLite codebase, while robust, is not immune to subtle edge cases and static analysis findings. This guide addresses four distinct issues identified in components related to Full-Text Search (FTS3/FTS5), variadic argument handling, and assertion logic:

  1. Potential Integer Overflow in FTS3 Snippet Calculation
    In ext/fts3/fts3_snippet.c, a calculation involving iStart = iEnd - pIter->nSnippet + 1 (line 401) raised concerns about integer overflow if pIter->nSnippet is negative. This variable is clamped between -64 and +64 elsewhere, but static analysis flagged scenarios where negative values might propagate.

  2. Missing va_end Call in Error Handling Path
    In ext/recover/sqlite3recover.c, a va_end macro was not invoked before returning from a function when a format string (zFmt) was NULL. This violates C standards, as va_end must always pair with va_start to avoid undefined behavior.

  3. Double Free and Use-After-Free in FTS5 Expression Parsing
    The FTS5 module’s ext/fts5/fts5_expr.c exhibited a scenario where sqlite3Fts5ParseNodeFree() could access or free memory already released by fts5ExprAddChildren(). This occurs under specific conditions where expression depth limits are exceeded after nodes are merged and freed prematurely.

  4. Incorrect Assertion for Array Index Validation
    In ext/expert/sqlite3expert.c, an assert(iSlot <= p->nSlot) (line 1517) failed to check for negative iSlot values. Additionally, the assertion’s upper bound was incorrect: iSlot should be less than p->nSlot, not less than or equal.


Possible Causes: Data Flow Ambiguities, Control Flow Gaps, and Ownership Miscommunication

1. Integer Overflow in FTS3 Snippet Logic

  • Misinterpretation of nSnippet Constraints: While pIter->nSnippet is clamped to a range of -64 to +64, the actual value used in calculations is adjusted to be non-negative in an earlier code path (e.g., via nFToken = abs(pIter->nSnippet)). Static analysis tools may not track this implicit guarantee, leading to false positives.
  • External Data Influence: iEnd (derived from pPhrase->iHead) could theoretically introduce values that exacerbate overflow risks if not properly sanitized.

2. va_end Omission in Error Paths

  • Conditional va_start Without Unconditional va_end: The original code placed va_start inside an if (zFmt) block but only called va_end within the same block. If zFmt is NULL, va_start is skipped, but va_end is still required if va_start was executed.

3. Memory Ownership Conflicts in FTS5 Expression Nodes

  • Dual Memory Management Strategies: fts5ExprAddChildren() uses two approaches:
    • Pointer Assignment: Transfers ownership of pSub to the parent node without freeing it.
    • Memory Copying and Freeing: Copies child nodes from pSub and then frees pSub, assuming the parent now owns the copied data.
  • Depth Check Timing: If expression depth validation (SQLITE_FTS5_MAX_EXPR_DEPTH) fails after fts5ExprAddChildren() frees pSub, subsequent cleanup in sqlite3Fts5ParseNodeFree() attempts to free pSub again or access its children, leading to crashes.

4. Assertion Logic Gaps in Slot Index Validation

  • Incomplete Range Checks: The assertion iSlot <= p->nSlot assumes iSlot is non-negative and that slots are zero-indexed. However, iSlot could be negative if derived from untrusted input, and p->nSlot represents the count of slots (indices 0 to nSlot-1).

Troubleshooting Steps, Solutions & Fixes: Code Adjustments and Ownership Clarification

1. Resolving FTS3 Integer Overflow Risk

  • Code Review Confirmation: Verify that pIter->nSnippet is non-negative at the point of use. In fts3SnippetText(), nFToken (derived from pIter->nSnippet) is explicitly set to its absolute value, ensuring non-negative values.
  • Add Assertion for Clarity: Insert assert(pIter->nSnippet >= 0); before line 401 to enforce the invariant.
  • Mitigate External Data Risks: Ensure pPhrase->iHead is validated upstream to prevent extreme values that could cause overflow even with non-negative nSnippet.

2. Correcting va_end Placement in Recovery Module

  • Move va_end Outside Conditional: Ensure va_end is called unconditionally after va_start, regardless of zFmt’s validity:
    if( zFmt ){
      va_start(ap, zFmt);
      // ... format message ...
      va_end(ap);
    }
    // Ensure va_end is called even if zFmt is NULL
    
  • Refactor Error Handling: Centralize va_end calls in a cleanup block to avoid redundancy.

3. Fixing FTS5 Memory Corruption

  • Modify fts5ExprAddChildren() Ownership Semantics:
    • Return Ownership Status: Change the function’s return type to int to indicate whether pSub was copied and freed (1) or merely assigned (0).
    • Defer Freeing to Caller: Remove sqlite3_free(pSub) from fts5ExprAddChildren(). Instead, let the caller (sqlite3Fts5ParseNode()) free pSub only if ownership was transferred.
  • Updated Code Snippet:
    static int fts5ExprAddChildren(Fts5ExprNode *p, Fts5ExprNode *pSub) {
      int ownershipTransferred = 0;
      if( p->eType != FTS5_NOT && pSub->eType == p->eType ) {
        // Copy children and free pSub
        ownershipTransferred = 1;
      } else {
        // Assign pSub as a child
      }
      return ownershipTransferred;
    }
    
  • Caller-Side Freeing: In sqlite3Fts5ParseNode(), check the return value of fts5ExprAddChildren() and free pSub only if ownership was transferred:
    int transferredLeft = fts5ExprAddChildren(pRet, pLeft);
    int transferredRight = fts5ExprAddChildren(pRet, pRight);
    if( pRet->iHeight > SQLITE_FTS5_MAX_EXPR_DEPTH ) {
      // Error handling
    } else {
      if( transferredLeft ) sqlite3_free(pLeft);
      if( transferredRight ) sqlite3_free(pRight);
    }
    

4. Correcting Slot Index Assertions

  • Adjust Assertion Bounds: Replace assert(iSlot <= p->nSlot) with assert(iSlot >= 0 && iSlot < p->nSlot).
  • Audit Callers for iSlot Validity: Ensure all callers of expertHandleSQL() validate iSlot against p->nSlot before invocation.
  • Documentation Update: Clarify in comments that iSlot is a zero-based index and must be within [0, nSlot-1].

General Recommendations for Static Analysis

  • Annotate Invariants: Use assert() or comments to document assumptions (e.g., nSnippet >= 0).
  • Leverage Compiler Warnings: Enable flags like -Wsign-conversion to catch implicit signed/unsigned mismatches.
  • Fuzz Testing: Integrate FTS3/FTS5 modules into fuzzing pipelines to uncover edge cases not covered by unit tests.

By addressing these issues through targeted code adjustments and ownership semantics clarification, SQLite’s robustness against edge cases and static analysis findings is significantly enhanced.

Related Guides

Leave a Reply

Your email address will not be published. Required fields are marked *