SQLite Forum

infinite loop in the exec_prepared_stmt function
Login

infinite loop in the exec_prepared_stmt function

(1.1) Originally by Zhuo Zhang (izhuer) with edits by Richard Hipp (drh) on 2020-04-24 11:52:24 from 1.0 [link] [source]

In the latest version of sqlite (3.31 release and 3.32 dev), there is an infinite loop vulnerability in the exec_prepared_stmt function, which can be triggered with a crafted statement. This vulnerability may result in denial of service of the target application that using sqlite. 

The crafted poc is as followed. Note that the following code works well in mysql and postgresql.
WITH a AS ( SELECT 1 UNION ALL SELECT * FROM a ) SELECT * FROM a;

The cause of the vulnerability is that the generated opcode always return SQLITE_ROW status no matter how many times it is executed. This makes the do-while loop in the exec_prepared_stmt function never exists. We should note that there is no any loop semantic in the crafted poc statement. This is a logic problem in the code generation process of sqlite. 

========= shell.c ================

static void exec_prepared_stmt(ShellState *pArg, sqlite3_stmt *pStmt) {
  ...
  do {
    ...
    // execution of the generated opcode always return the SQLITE_ROW status
    rc = sqlite3_step(pStmt);  
    ...
  } while ( SQLITE_ROW = rc );
  ...
}

========= generated opcode ================
sqlite> explain WITH a AS ( SELECT 1 UNION ALL SELECT * FROM a ) SELECT * FROM a;
addr  opcode         p1    p2    p3    p4             p5  comment
----  -------------  ----  ----  ----  -------------  --  -------------
0     Init           0     1     0                    00  Start at 1
1     InitCoroutine  1     20    2                    00  a
2     OpenPseudo     1     2     1                    00  1 columns in r[2]
3     OpenEphemeral  2     1     0                    00  nColumn=1; Queue table
4     Integer        1     3     0                    00  r[3]=1
5     MakeRecord     3     1     4                    00  r[4]=mkrec(r[3])
6     NewRowid       2     5     0                    00  r[5]=rowid
7     Insert         2     4     5                    08  intkey=r[5] data=r[4]
8       Rewind         2     19    0                    00
9       NullRow        1     0     0                    00
10      RowData        2     2     0                    00  r[2]=data
11      Delete         2     0     0                    00
12      Column         1     0     6                    00  r[6]=1
13      Yield          1     0     0                    00
14      Column         1     0     3                    00  r[3]=a.1
15      MakeRecord     3     1     4                    00  r[4]=mkrec(r[3])
16      NewRowid       2     5     0                    00  r[5]=rowid
17      Insert         2     4     5                    08  intkey=r[5] data=r[4]
18    Goto           0     8     0                    00
19    EndCoroutine   1     0     0                    00
20    InitCoroutine  1     0     2                    00
21      Yield          1     25    0                    00  next row of a
22      Copy           6     7     0                    00  r[7]=r[6]; a.1
23      ResultRow      7     1     0                    00  output=r[7]
24    Goto           0     21    0                    00
25    Halt           0     0     0                    00

======== execution trace of opcode =====================
sqlite3VdbeExec(p)
Get into For-Loop
24: OP_Goto
21: OP_Yield
14: OP_Column
15: OP_MakeRecord
16: OP_NewRowid
17: OP_Insert
18: OP_Goto
8: OP_Rewind
9: OP_NullRow
10: OP_RowData
11: OP_Delete
12: OP_Column
13: OP_Yield
22: OP_Copy
23: OP_ResultRow
sqlite3VdbeExec(p)
end

(2.2) By Dan Kennedy (dan) on 2020-04-24 11:01:15 edited from 2.1 in reply to 1.0 [source]

You can get the same effect from postgres with:

    WITH RECURSIVE a AS ( SELECT 1 UNION ALL SELECT * FROM a ) SELECT * FROM a;

SQLite just doesn't require the RECURSIVE keyword.

It's a statement that returns an infinite number of rows. See point 4 here:

https://www.sqlite.org/security.html#untrusted_sql_inputs

for ways for an application that accepts untrusted SQL to defend against the DOS attack..