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..