SQLite Forum

Bug found in SQLite version 3.38.1
Login

Bug found in SQLite version 3.38.1

(1) By salmonx on 2022-03-19 11:11:33 [link] [source]

POC

CREATE TABLE def AS SELECT * FROM (SELECT * FROM (SELECT *, * FROM sqlite_dbpage ORDER BY 1), (SELECT *, * FROM sqlite_dbpage)), (SELECT *, * FROM sqlite_dbpage);

This will lead to 100%CPU and 100%MEM, then killed by OS. Please check it.

Regards.

(2) By Simon Slavin (slavin) on 2022-03-19 12:03:12 in reply to 1 [link] [source]

SQLite version 3.37.0 2021-12-09 01:34:53
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> CREATE TABLE def AS SELECT * FROM (SELECT * FROM (SELECT *, * FROM sqlite_dbpage ORDER BY 1), (SELECT *, * FROM sqlite_dbpage)), (SELECT *, * FROM sqlite_dbpage);
zsh: killed     sqlite3
simon@184 Desktop % 

Verified on my computer. Well, my OS doesn't allow 100% CPU or boundless memory use but yes, it crashes. Don't know whether the bug is in the SQLite library or the SQLIte CLI.

(3) By Larry Brasfield (larrybr) on 2022-03-19 12:53:55 in reply to 2 [link] [source]

Don't know whether the bug is in the SQLite library or the SQLIte CLI.

I'm not convinced it is a bug at all. If it is to be deemed "a bug", it would have to be attributed to library.

The sqlite_dbpage virtual table is exposed for purposes of low-level access to the database file. Doing an insert (such as the OP's SQL does) from that source is a runaway process, as the database can never become large enough to contain both its underlying storage AND the book-keeping data necessary to permit efficient queries and manipulation of that data.

I think it is the same sort of "bug" as complaining that "while (1) malloc(100);" never finishes and slurps all available memory.

The multi-way join is mere icing on that runaway process. It makes an O(N^3) operation out of an O(N) operation having N=infinity.

(4) By Richard Hipp (drh) on 2022-03-19 13:05:15 in reply to 1 [source]

There are lots of ways to create infinite loops that consume 100% CPU and memory using perfectly valid SQL. That's the nature of the SQL language.

Here's an example that is less obscure than they one you posted:

WITH RECURSIVE c(x) AS (VALUES(1) UNION ALL SELECT x+1 FROM c)
SELECT group_concat(quote(zeroblob(100))) FROM c;

SQLite includes interfaces that applications can use to limit the memory and CPU usage in cases where run-away SQL statements might be a concern. You should make use of those interfaces in your fuzzer. Everybody else who fuzzes SQLite does. For example:

PRAGMA hard_heap_limit=100000000;
.progress 10000000 --limit 1
CREATE TABLE def AS SELECT * FROM (SELECT * FROM (SELECT *, * FROM sqlite_dbpage ORDER BY 1), (SELECT *, * FROM sqlite_dbpage)), (SELECT *, * FROM sqlite_dbpage);

The first statement limits the total amount of memory used to 100MB. (You can adjust the parameter to meet your needs.) The second statement causes each SQL statement to interrupt and quit after running 10 million virtual machine opcodes. (Once again, adjust the limit to suit your needs.)

(5) By Simon Willison (simonw) on 2022-03-19 17:14:30 in reply to 4 [link] [source]

I had never seen this mechanism before:

.progress 10000000 --limit 1

Is that covered in the documentation - and do you know if it's available to the Python standard library module?

I guess I can replicate what it's doing in Python like this:

conn.set_progress_handler(lambda: 1, 10000000)

Since returning 1 from a progress handler cancels the query.

(6) By Larry Brasfield (larrybr) on 2022-03-19 17:31:54 in reply to 5 [link] [source]

The keyword index leads to the sqlite3_progress_handler() API. The shell's response to .help mentions the .progress dot command. I highly recommend using those resources.

I would have to go read some Python docs to answer your question about Python. However, my guess from your use of the set_progress_handler() method is that SQLite's sqlite3_progress_handler() API is usefully exposed.

If you are asking whether the SQLite shell's functionality (such as the .progress dot command) is available from Python -- I doubt it.

(7) By Simon Slavin (slavin) on 2022-03-19 18:54:57 in reply to 3 [link] [source]

Point taken. Not a bug. The program is doing exactly what you asked it to do: try to use all available memory, then get aborted by the OS.

(8) By Simon Willison (simonw) on 2022-03-19 23:20:05 in reply to 6 [link] [source]

Yes, that worked:

sqlite> .help .progress
.progress N              Invoke progress handler after every N opcodes
   --limit N                 Interrupt after N progress callbacks
   --once                    Do no more than one progress interrupt
   --quiet|-q                No output except at interrupts
   --reset                   Reset the count for each input and interrupt

It looks like the only place that documentation is available right now is inside the interactive prompt - my attempts to find it through Google searches didn't turn up anything useful.

A trick I've been using recently for my own command line projects is to have a page of the documentation which automatically shows the output of the --help commands - for example: https://sqlite-utils.datasette.io/en/stable/cli-reference.html

This is useful for search, and also as a development tool because it gives me somewhere I can review the current --help output of everything in one place.

Maybe the sqlite3 command would benefit from something similar!

(9) By Simon Willison (simonw) on 2022-03-19 23:36:42 in reply to 8 [link] [source]

I built a page that does that here: https://github.com/simonw/help-scraper/blob/main/sqlite3-help.md - using the "help scraping" technique I described in https://simonwillison.net/2022/Feb/2/help-scraping/