SQLite Forum

App using SQlite C API faces segmentation fault
Login

App using SQlite C API faces segmentation fault

(1) By Raymi (Raymi_C) on 2020-08-21 11:37:32 [link]

Hello,

I've an application writing log data to a SQLite3 database. Randomly, reason why I'm not able to reproduce the issue when needed, I'm facing app crash with segmentation fault. I succeeded to generate a SEGV dump which shows the following 2 traces.

I posting this as I'm trying to figure out what is the root cause for weeks but still struggling to solve. 

# 1) sqlite3ExprListDup Trace
> `ip 0x7fff797b5b5d: _sigtramp+0x1d
ip 0x7fff787b11d7: sqlite3ExprListDup+0x127
ip 0x7fff787b5e62: sqlite3SelectDup+0x132
ip 0x7fff787848af: selectExpander+0x4df
ip 0x7fff787842f1: sqlite3WalkSelect+0x31
ip 0x7fff7885a87a: sqlite3SelectPrep+0x8a
ip 0x7fff7877a7be: sqlite3Select+0x28e
ip 0x7fff7874e28a: yy_reduce+0x10ca
ip 0x7fff7874b658: sqlite3RunParser+0x218
ip 0x7fff78749d0a: sqlite3LockAndPrepare+0x48a
ip 0x7fff787d61e7: sqlite3_prepare_v2+0xf7
ip 0x10ac75422: Connector::sqlite3Driver::executeDQL(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)+0x82`

# 2) sqlite3WhereBegin Trace
> `ip 0x7fff69eb5b5d: _sigtramp+0x1d
ip 0x7fff68e8b804: sqlite3WhereBegin+0x1424
ip 0x7fff68e7da0f: sqlite3Select+0x34df
ip 0x7fff68f6010f: multiSelectOrderBy+0xcef
ip 0x7fff68e7d24e: sqlite3Select+0x2d1e
ip 0x7fff68e7caaa: sqlite3Select+0x257a
ip 0x7fff68e4e28a: yy_reduce+0x10ca
ip 0x7fff68e4b658: sqlite3RunParser+0x218
ip 0x7fff68e49d0a: sqlite3LockAndPrepare+0x48a
ip 0x7fff68ed61e7: sqlite3_prepare_v2+0xf7
ip 0x100996432: Connector::sqlite3Driver::executeDQL(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&)+0x82`

The ***first trace*** occurs on **INSERT** statements into table that contains 6 columns. I found on the Web similar cases (Firefox) resulting from a bug prior to SQlite version 3.18.x and that was corrected as from this version.
The ***second trace*** occurs on **SELECT** statement from the same table.

The environment is C++11 on MacOSX with sqlite-3.22.0 C API.

The log database is a one table structure.
# Table DDL
> `CREATE TABLE IF NOT EXISTS log (uuid TEXT NOT NULL
                                sourcename TEXT NOT NULL,
                                sourceipaddress TEXT NOT NULL,
                                stimestamp TEXT NOT NULL,
                                qtimestamp TEXT NOT NULL,
                                severity INTEGER NOT NULL,
                                content TEXT NOT NULL);
CREATE INDEX IF NOT EXISTS idx_uuid ON log (uuid);
CREATE INDEX IF NOT EXISTS idx_stimestamp ON log (stimestamp);
CREATE INDEX IF NOT EXISTS idx_qtimestamp ON log (qtimestamp);
CREATE INDEX IF NOT EXISTS idx_severity ON log (severity);`

When inserting data into the log table, the app executes **INSERT** statements by lot (50 rows) using a wrapper and ensures **TRANSACTION BEGIN** and **COMMIT/ROLLBACK**.
# Application source code extract
> `[public] const bool Connector::open(const std::string& dbName, sqlite3 *dbConnection) {
    int      connStatus   = sqlite3_open("log.db", &dbConnection);
    if (connStatus == SQLITE_OK) {
        return true;
        ... //Wrapper maintains the connection to the SQLite database
    }
    else {
        return false;
    }
}
[public] const bool Connector::startTransac() {
    ...
}
[public] const bool Connector::commitTransac() {
    ...
}
[public] const bool Connector::rollbackTransac() {
    ...
}
[public] const int Connector::executeDQL(const std::string& queryStmt) {
    if (queryStmt.find("SELECT ") >= 0) {
        sqlite3_stmt    *statement    = nullptr;
        int              sqLiteResult = sqlite3_prepare_v2(dbConnection, queryStmt.c_str(), -1, &statement, NULL);
        if (sqLiteResult == SQLITE_OK) {
            sqLiteResult = sqlite3_step(statement);
            sqLiteResult = sqlite3_finalize(statement);
        }
        return sqLiteResult;
    }
    else {
        return this->executeDML(queryStmt);
    }
}
[public] const int Connector::executeDML(const std::string& queryStmt) {
    char    *errorMsg = 0;
    int      sqLiteResult = sqlite3_exec(dbConnection, queryStmt.c_str(), nullptr, nullptr, &_errorMessage);
    if (sqLiteResult == SQLITE_OK) {
        sqLiteResult = sqlite3_changes(dbConnection);
    }
    return sqLiteResult
}`

The application is _multi-threaded_ with mutex lockers but the connector class is a _singleton_ not allowing concurrent calls.

Any help or suggestion to solve my issue would so much appreciated.

(2) By Larry Brasfield (LarryBrasfield) on 2020-08-21 15:28:59 in reply to 1

Please see [this post about heap corruption](https://sqlite.org/forum/forumpost/6009b7b9de34309c1a9efec124af1fc2017f2209f7b76961d53db9d9a8f08471).  The problem you report is much more likely due to that than random operation of the SQLite library. Given an intact heap, the library is remarkably deterministic.

(3) By Raymi (Raymi_C) on 2020-08-25 07:44:26 in reply to 2 [link]

Ok, will try with XCode Instruments to check if there is a heap misuse.

Thanks

(4) By Stephan Beal (stephan) on 2020-08-25 08:52:53 in reply to 1 [link]

A sidebar unrelated to the question but related to the demonstrated code:

> if (queryStmt.find("SELECT ") >= 0) ...

That's a very fragile way to check for whether a statement is capable of fetching data, for various reasons. Much more robust and generic is:

- Prepare the query, regardless of what type you *think* it is based on string-sniffing.
- Use [sqlite3_column_count()](https://www.sqlite.org/c3ref/column_count.html) to get the query's result column count: if it's 0, the statement is non-fetching (INSERT, UPDATE, etc.), else it is a SELECT, a SELECT-like pragma (if there are any such pragmas), or some such hypothetical construct.
- Step the cursor one time. That will execute an INSERT/UPDATE/etc. the same as it will a SELECT, but it will return `SQLITE_DONE` instead of `SQLITE_ROW`.

The notable functional difference there from your current code is that `sqlite3_exec()` will compile and run all of the statements in the given string, whereas prepare/step will only handle the first statement (noting that the final argument to the step family of functions can be used to get the next position in the SQL so that the caller can loop over the string to handle each statement it includes).

(5) By Raymi (Raymi_C) on 2020-08-30 07:57:02 in reply to 4 [link]

I fully agree with you, this should certainly be improved but in this case app is only performing SELECT and INSERT.

However, I gonna take into account your recommendation as I was not aware of the difference.

Many thanks

(6) By Keith Medcalf (kmedcalf) on 2020-08-30 09:03:34 in reply to 5 [link]

There is an API for that.  <https://sqlite.org/c3ref/stmt_readonly.html>