Crash when updating a table without rowid and with a partial unique index
(1) By Adrian Dewhurst (sailorfrag) on 2024-08-08 00:02:36 [source]
Today while trying to do some data cleanup at work, I constructed a query that reliably caused a segfault in our embedded SQLite instance. It seems to be an interaction between using parameters, a table without rowid, and a partial unique index.
If I run the query without parameters, it succeeds. If I remove the WITHOUT ROWID
, it succeeds. If I change the index to non-UNIQUE
, it succeeds. While this is a somewhat complicated combination, I was surprised to find it because none of these features are particularly unusual.
This appears to be the minimal reproduction:
CREATE TABLE Repro (
ID INTEGER NOT NULL PRIMARY KEY,
Type INTEGER NOT NULL,
Val INTEGER
) WITHOUT ROWID;
CREATE UNIQUE INDEX Type1Unique ON Repro (Val) WHERE Type=1;
INSERT INTO Repro (ID, Type, Val) VALUES (8, 1, 2);
.parameter init
.parameter set ?1 1
.parameter set ?2 2
UPDATE Repro SET ID=0 WHERE Type=?1 AND Val=?2;
If I run this with a freshly downloaded precompiled binary, it also crashes:
$ ../sqlite-tools-linux-x64-3460000/sqlite3 -init run.sql
-- Loading resources from run.sql
Segmentation fault (core dumped)
Running EXPLAIN
on the query:
-- Loading resources from run.sql
addr opcode p1 p2 p3 p4 p5 comment
---- ------------- ---- ---- ---- ------------- -- -------------
0 Init 0 56 0 0 Start at 56
1 Null 0 17 17 0 r[17..17]=NULL
2 OpenEphemeral 3 1 0 k(1,) 0 nColumn=1
3 OpenRead 4 2 0 k(1,) 0 root=2 iDb=0; sqlite_autoindex_Repro_1
4 Rewind 4 13 19 0 0
5 Column 4 1 19 0 r[19]= cursor 4 column 1
6 Ne 20 12 19 BINARY-8 84 if r[19]!=r[20] goto 12
7 Column 4 2 19 0 r[19]= cursor 4 column 2
8 Ne 21 12 19 BINARY-8 84 if r[19]!=r[21] goto 12
9 Column 4 0 17 0 r[17]= cursor 4 column 0
10 MakeRecord 17 1 18 C 0 r[18]=mkrec(r[17])
11 IdxInsert 3 18 17 1 0 key=r[18]
12 Next 4 5 0 1
13 OpenWrite 1 3 0 k(2,,) 0 root=3 iDb=0; Type1Unique
14 OpenWrite 2 2 0 k(1,) 0 root=2 iDb=0; sqlite_autoindex_Repro_1
15 Rewind 3 55 0 0
16 RowData 3 18 0 0 r[18]=data
17 NotFound 2 54 18 0 0 key=r[18]
18 Column 2 0 10 0 r[10]= cursor 2 column 0
19 Null 0 11 0 0 r[11]=NULL
20 Null 0 12 0 0 r[12]=NULL
21 Integer 0 14 0 0 r[14]=0
22 Column 2 1 15 0 r[15]= cursor 2 column 1
23 Column 2 2 16 0 r[16]= cursor 2 column 2
24 HaltIfNull 1299 2 14 Repro.ID 1 if r[14]=null halt
25 Affinity 14 3 0 DDD 0 affinity(r[14..16])
26 Noop 0 0 0 0 prep index Type1Unique
27 Null 0 1 0 0 r[1]=NULL
28 Ne 22 36 15 BINARY-8 84 if r[15]!=r[22] goto 36
29 SCopy 16 2 0 0 r[2]=r[16]; Val
30 SCopy 14 3 0 0 r[3]=r[14]; ID
31 MakeRecord 2 2 1 0 r[1]=mkrec(r[2..3]); for Type1Unique
32 NoConflict 1 36 2 1 0 key=r[2]
33 Column 1 1 19 0 r[19]=Repro.ID
34 Eq 10 36 19 BINARY-8 144 if r[19]==r[10] goto 36
35 Halt 2067 2 0 Repro.Val 2
36 Noop 0 0 0 0 prep index sqlite_autoindex_Repro_1
37 SCopy 14 5 0 0 r[5]=r[14]; ID
38 SCopy 15 6 0 0 r[6]=r[15]; Type
39 SCopy 16 7 0 0 r[7]=r[16]; Val
40 MakeRecord 5 3 4 0 r[4]=mkrec(r[5..7]); for sqlite_autoindex_Repro_1
41 NoConflict 2 44 5 1 0 key=r[5]
42 Eq 10 44 5 BINARY-8 144 if r[5]==r[10] goto 44
43 Halt 1555 2 0 Repro.ID 2
44 NotFound 2 54 18 0 0 key=r[18]
45 Column 2 1 19 0 r[19]= cursor 2 column 1
46 Ne 22 50 19 BINARY-8 84 if r[19]!=r[22] goto 50
47 Column 2 2 23 0 r[23]= cursor 2 column 2
48 Column 2 0 24 0 r[24]= cursor 2 column 0
49 IdxDelete 1 23 2 1 key=r[23..24]
50 Delete 2 4 13 Repro 0
51 IsNull 1 53 0 0 if r[1]==NULL goto 53
52 IdxInsert 1 1 2 2 0 key=r[1]
53 IdxInsert 2 4 5 1 1 key=r[4]
54 Next 3 16 0 0
55 Halt 0 0 0 0
56 Transaction 0 1 2 0 1 usesStmtJournal=1
57 Variable 1 20 0 0 r[20]=parameter(1)
58 Variable 2 21 0 0 r[21]=parameter(2)
59 Integer 1 22 0 0 r[22]=1
60 Goto 0 1 0 0
The crash seems to be a null pointer dereference of pC
(gdb) bt
#0 sqlite3VdbeExec (p=p@entry=0x55555572f490) at sqlite3.c:96045
#1 0x000055555566208f in sqlite3Step (p=0x55555572f490) at sqlite3.c:91220
#2 sqlite3_step (pStmt=pStmt@entry=0x55555572f490) at sqlite3.c:91281
#3 0x000055555557fc20 in exec_prepared_stmt (pArg=0x7fffffffc380, pStmt=0x55555572f490) at shell.c:22105
#4 0x00005555555883c4 in shell_exec (pArg=pArg@entry=0x7fffffffc380, zSql=zSql@entry=0x555555723550 "UPDATE Repro SET ID=0 WHERE Type=?1 AND Val=?2;", pzErrMsg=pzErrMsg@entry=0x7fffffffb9f8) at shell.c:22414
#5 0x000055555558a947 in runOneSqlLine (p=p@entry=0x7fffffffc380, zSql=zSql@entry=0x555555723550 "UPDATE Repro SET ID=0 WHERE Type=?1 AND Val=?2;", in=0x5555557222f0, startline=startline@entry=14) at shell.c:29798
#6 0x0000555555596a12 in process_input (p=<optimized out>) at shell.c:29966
#7 process_input (p=<optimized out>) at shell.c:29885
#8 0x000055555556659f in process_sqliterc (sqliterc_override=0x7fffffffde47 "run.sql", p=0x7fffffffc380) at shell.c:30133
#9 main (argc=<optimized out>, argv=<optimized out>) at shell.c:30649
(gdb) p pC
$1 = (VdbeCursor *) 0x0
(gdb) p pOp
$2 = (Op *) 0x555555756460
(gdb) p *pOp
$3 = {opcode = 94 '^', p4type = 0 '\000', p5 = 0, p1 = 2, p2 = 1, p3 = 20, p4 = {i = 0, p = 0x0, z = 0x0, pI64 = 0x0, pReal = 0x0, pFunc = 0x0, pCtx = 0x0, pColl = 0x0, pMem = 0x0, pVtab = 0x0, pKeyInfo = 0x0, ai = 0x0, pProgram = 0x0, pTab = 0x0}, zComment = 0x0, nExec = 0, nCycle = 0}
(gdb) p p
$4 = (Vdbe *) 0x55555572f490
(gdb) p *p
$5 = {db = 0x5555557237b0, ppVPrev = 0x5555557237b8, pVNext = 0x0, pParse = 0x7fffffffb400, nVar = 2, nMem = 28, nCursor = 4, cacheCtr = 1, pc = 0, rc = 0, nChange = 0, iStatement = 0, iCurrentTime = 0, nFkConstraint = 0, nStmtDefCons = 0, nStmtDefImmCons = 12884965494, aMem = 0x555555756f58, apArg = 0x555555756ee8, apCsr = 0x555555756ec8,
aVar = 0x555555756ee8, aOp = 0x5555557562b0, nOp = 56, nOpAlloc = 100, aColName = 0x0, pResultRow = 0x0, zErrMsg = 0x0, pVList = 0x555555731150, startTime = 0, nResColumn = 0, nResAlloc = 0, errorAction = 2 '\002', minWriteFileFormat = 4 '\004', prepFlags = 128 '\200', eVdbeState = 2 '\002', expired = 0, explain = 0, changeCntOn = 1, usesStmtJournal = 0,
readOnly = 0, bIsReader = 1, haveEqpOps = 0, btreeMask = 1, lockMask = 0, aCounter = {0, 0, 0, 0, 0, 1, 1, 0, 0}, zSql = 0x555555730fd0 "UPDATE Repro SET ID=0 WHERE Type=?1 AND Val=?2;", pFree = 0x0, pFrame = 0x0, pDelFrame = 0x0, nFrame = 0, expmask = 1, pProgram = 0x0, pAuxData = 0x0, nScan = 0, aScan = 0x0}
(gdb) p p->apCsr
$6 = (VdbeCursor **) 0x555555756ec8
(gdb) p p->apCsr[0]
$7 = (VdbeCursor *) 0x0
(gdb) p p->apCsr[1]
$8 = (VdbeCursor *) 0x5555557302a0
(gdb) p p->apCsr[2]
$9 = (VdbeCursor *) 0x0
I'm struggling to follow exactly what's going on. pOp-aOp
is 9, which I think means it's supposed to be the Column
operation at addr 9 in the EXPLAIN
, but p1
, p2
and p3
don't seem to match.
Anyway, I don't think this is a mistake on my part, so hopefully this is sufficiently detailed to be easy to isolate the problem.
(2) By Richard Hipp (drh) on 2024-08-08 00:40:52 in reply to 1 [link] [source]
Thanks for the repro script.
The problem bisects to check-in 8d4160910d651246 from 2023-09-25 and first appeared in release 3.44.0. I will probably have it fixed shortly.
(3) By Richard Hipp (drh) on 2024-08-08 19:48:58 in reply to 1 [link] [source]
Check-in 7058d93b097aeb46 contains a small patch that seems to resolve this issue. But I'm not happy with it yet. We are still testing. Further revisions are likely to appear.
But if you need a quick solution, the patch is available.
(4) By Adrian Dewhurst (sailorfrag) on 2024-08-09 14:52:47 in reply to 3 [link] [source]
Thanks for the quick response!
I've worked around it for now by not using a parameter, so we'll probably just pick up the fix in the next stable release.