SQLite Forum

Crash when updating a table without rowid and with a partial unique index
Login

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.