1887d4b2bSdan# 2004 August 30 {} 2ee696e22Sdrh# 3ee696e22Sdrh# The author disclaims copyright to this source code. In place of 4ee696e22Sdrh# a legal notice, here is a blessing: 5ee696e22Sdrh# 6ee696e22Sdrh# May you do good and not evil. 7ee696e22Sdrh# May you find forgiveness for yourself and forgive others. 8ee696e22Sdrh# May you share freely, never taking more than you give. 9ee696e22Sdrh# 10ee696e22Sdrh#*********************************************************************** 11ee696e22Sdrh# This file implements regression tests for SQLite library. 12ee696e22Sdrh# 13ee696e22Sdrh# This file implements tests to make sure SQLite does not crash or 14ee696e22Sdrh# segfault if it sees a corrupt database file. 15ee696e22Sdrh# 168f880a8cSdanielk1977# $Id: corrupt.test,v 1.12 2009/07/13 09:41:45 danielk1977 Exp $ 175558a8a6Sdanielk1977 18fda06befSmistachkincatch {forcedelete test.db test.db-journal test.bu} 19ee696e22Sdrh 20ee696e22Sdrhset testdir [file dirname $argv0] 21ee696e22Sdrhsource $testdir/tester.tcl 22ee696e22Sdrh 2368928b6cSdan# Do not use a codec for tests in this file, as the database file is 2468928b6cSdan# manipulated directly using tcl scripts (using the [hexio_write] command). 2568928b6cSdan# 2668928b6cSdando_not_use_codec 2768928b6cSdan 2809fe6143Sdrh# These tests deal with corrupt database files 2909fe6143Sdrh# 3009fe6143Sdrhdatabase_may_be_corrupt 3109fe6143Sdrh 32ee696e22Sdrh# Construct a large database for testing. 33ee696e22Sdrh# 34ee696e22Sdrhdo_test corrupt-1.1 { 35ee696e22Sdrh execsql { 36ee696e22Sdrh BEGIN; 37ee696e22Sdrh CREATE TABLE t1(x); 3836963fdcSdanielk1977 INSERT INTO t1 VALUES(randstr(100,100)); 3936963fdcSdanielk1977 INSERT INTO t1 VALUES(randstr(90,90)); 4036963fdcSdanielk1977 INSERT INTO t1 VALUES(randstr(80,80)); 4136963fdcSdanielk1977 INSERT INTO t1 SELECT x || randstr(5,5) FROM t1; 4236963fdcSdanielk1977 INSERT INTO t1 SELECT x || randstr(6,6) FROM t1; 4336963fdcSdanielk1977 INSERT INTO t1 SELECT x || randstr(7,7) FROM t1; 4436963fdcSdanielk1977 INSERT INTO t1 SELECT x || randstr(8,8) FROM t1; 4536963fdcSdanielk1977 INSERT INTO t1 VALUES(randstr(3000,3000)); 4636963fdcSdanielk1977 INSERT INTO t1 SELECT x || randstr(9,9) FROM t1; 4736963fdcSdanielk1977 INSERT INTO t1 SELECT x || randstr(10,10) FROM t1; 4836963fdcSdanielk1977 INSERT INTO t1 SELECT x || randstr(11,11) FROM t1; 4936963fdcSdanielk1977 INSERT INTO t1 SELECT x || randstr(12,12) FROM t1; 50ee696e22Sdrh CREATE INDEX t1i1 ON t1(x); 51ee696e22Sdrh CREATE TABLE t2 AS SELECT * FROM t1; 52ee696e22Sdrh DELETE FROM t2 WHERE rowid%5!=0; 53ee696e22Sdrh COMMIT; 54ee696e22Sdrh } 554489f9bdSdanielk1977} {} 564489f9bdSdanielk1977integrity_check corrupt-1.2 57ee696e22Sdrh 58615ae553Sdrh# Setup for the tests. Make a backup copy of the good database in test.bu. 59615ae553Sdrh# Create a string of garbage data that is 256 bytes long. 60615ae553Sdrh# 61fda06befSmistachkinforcecopy test.db test.bu 62ee696e22Sdrhset fsize [file size test.db] 63ee696e22Sdrhset junk "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" 64ee696e22Sdrhwhile {[string length $junk]<256} {append junk $junk} 65ee696e22Sdrhset junk [string range $junk 0 255] 66ee696e22Sdrh 67615ae553Sdrh# Go through the database and write garbage data into each 256 segment 68615ae553Sdrh# of the file. Then do various operations on the file to make sure that 69615ae553Sdrh# the database engine can recover gracefully from the corruption. 70615ae553Sdrh# 712ed11e7bSdanfor {set i [expr {1*256}]} {$i<$fsize-256} {incr i 256} { 72ee696e22Sdrh set tn [expr {$i/256}] 73ee696e22Sdrh db close 74fda06befSmistachkin forcecopy test.bu test.db 75ee696e22Sdrh set fd [open test.db r+] 76ee696e22Sdrh fconfigure $fd -translation binary 77ee696e22Sdrh seek $fd $i 78ee696e22Sdrh puts -nonewline $fd $junk 79ee696e22Sdrh close $fd 80ee696e22Sdrh do_test corrupt-2.$tn.1 { 81ee696e22Sdrh sqlite3 db test.db 82ee696e22Sdrh catchsql {SELECT count(*) FROM sqlite_master} 83ee696e22Sdrh set x {} 84ee696e22Sdrh } {} 85ee696e22Sdrh do_test corrupt-2.$tn.2 { 86ee696e22Sdrh catchsql {SELECT count(*) FROM t1} 87ee696e22Sdrh set x {} 88ee696e22Sdrh } {} 89ee696e22Sdrh do_test corrupt-2.$tn.3 { 90ee696e22Sdrh catchsql {SELECT count(*) FROM t1 WHERE x>'abcdef'} 91ee696e22Sdrh set x {} 92ee696e22Sdrh } {} 93ee696e22Sdrh do_test corrupt-2.$tn.4 { 94ee696e22Sdrh catchsql {SELECT count(*) FROM t2} 95ee696e22Sdrh set x {} 96ee696e22Sdrh } {} 97ee696e22Sdrh do_test corrupt-2.$tn.5 { 98ee696e22Sdrh catchsql {CREATE TABLE t3 AS SELECT * FROM t1} 99ee696e22Sdrh set x {} 100ee696e22Sdrh } {} 101ee696e22Sdrh do_test corrupt-2.$tn.6 { 102ee696e22Sdrh catchsql {DROP TABLE t1} 103ee696e22Sdrh set x {} 104ee696e22Sdrh } {} 105ee696e22Sdrh do_test corrupt-2.$tn.7 { 106ee696e22Sdrh catchsql {PRAGMA integrity_check} 107ee696e22Sdrh set x {} 108ee696e22Sdrh } {} 10943e377afSdanielk1977 11043e377afSdanielk1977 # Check that no page references were leaked. 11143e377afSdanielk1977 do_test corrupt-2.$tn.8 { 11243e377afSdanielk1977 set bt [btree_from_db db] 11343e377afSdanielk1977 db_enter db 11443e377afSdanielk1977 array set stats [btree_pager_stats $bt] 11543e377afSdanielk1977 db_leave db 11643e377afSdanielk1977 set stats(ref) 11743e377afSdanielk1977 } {0} 118ee696e22Sdrh} 119ee696e22Sdrh 120ac171788Sdanielk1977#------------------------------------------------------------------------ 121ac171788Sdanielk1977# For these tests, swap the rootpage entries of t1 (a table) and t1i1 (an 122ac171788Sdanielk1977# index on t1) in sqlite_master. Then perform a few different queries 123ac171788Sdanielk1977# and make sure this is detected as corruption. 124ac171788Sdanielk1977# 125ac171788Sdanielk1977do_test corrupt-3.1 { 126ac171788Sdanielk1977 db close 127fda06befSmistachkin forcecopy test.bu test.db 128ac171788Sdanielk1977 sqlite3 db test.db 129a2dc3b1aSdanielk1977 list 130a2dc3b1aSdanielk1977} {} 131ac171788Sdanielk1977do_test corrupt-3.2 { 132ac171788Sdanielk1977 set t1_r [execsql {SELECT rootpage FROM sqlite_master WHERE name = 't1i1'}] 133ac171788Sdanielk1977 set t1i1_r [execsql {SELECT rootpage FROM sqlite_master WHERE name = 't1'}] 134ac171788Sdanielk1977 set cookie [expr [execsql {PRAGMA schema_version}] + 1] 135*b7c2cf0aSdrh sqlite3_db_config db DEFENSIVE 0 136ac171788Sdanielk1977 execsql " 137ac171788Sdanielk1977 PRAGMA writable_schema = 1; 138ac171788Sdanielk1977 UPDATE sqlite_master SET rootpage = $t1_r WHERE name = 't1'; 139ac171788Sdanielk1977 UPDATE sqlite_master SET rootpage = $t1i1_r WHERE name = 't1i1'; 140ac171788Sdanielk1977 PRAGMA writable_schema = 0; 141ac171788Sdanielk1977 PRAGMA schema_version = $cookie; 142ac171788Sdanielk1977 " 143a2dc3b1aSdanielk1977} {} 144ac171788Sdanielk1977 145ac171788Sdanielk1977# This one tests the case caught by code in checkin [2313]. 146ac171788Sdanielk1977do_test corrupt-3.3 { 147ac171788Sdanielk1977 db close 148ac171788Sdanielk1977 sqlite3 db test.db 149ac171788Sdanielk1977 catchsql { 150ac171788Sdanielk1977 INSERT INTO t1 VALUES('abc'); 151ac171788Sdanielk1977 } 152ac171788Sdanielk1977} {1 {database disk image is malformed}} 153ac171788Sdanielk1977do_test corrupt-3.4 { 154ac171788Sdanielk1977 db close 155ac171788Sdanielk1977 sqlite3 db test.db 156ac171788Sdanielk1977 catchsql { 157ac171788Sdanielk1977 SELECT * FROM t1; 158ac171788Sdanielk1977 } 159ac171788Sdanielk1977} {1 {database disk image is malformed}} 160ac171788Sdanielk1977do_test corrupt-3.5 { 161ac171788Sdanielk1977 db close 162ac171788Sdanielk1977 sqlite3 db test.db 163ac171788Sdanielk1977 catchsql { 164ac171788Sdanielk1977 SELECT * FROM t1 WHERE oid = 10; 165ac171788Sdanielk1977 } 166ac171788Sdanielk1977} {1 {database disk image is malformed}} 167ac171788Sdanielk1977do_test corrupt-3.6 { 168ac171788Sdanielk1977 db close 169ac171788Sdanielk1977 sqlite3 db test.db 170ac171788Sdanielk1977 catchsql { 171ac171788Sdanielk1977 SELECT * FROM t1 WHERE x = 'abcde'; 172ac171788Sdanielk1977 } 173ac171788Sdanielk1977} {1 {database disk image is malformed}} 174ac171788Sdanielk1977 175bd5969a2Sdanielk1977do_test corrupt-4.1 { 176bd5969a2Sdanielk1977 db close 177fda06befSmistachkin forcedelete test.db test.db-journal 178bd5969a2Sdanielk1977 sqlite3 db test.db 179bd5969a2Sdanielk1977 execsql { 180bd5969a2Sdanielk1977 PRAGMA page_size = 1024; 181bd5969a2Sdanielk1977 CREATE TABLE t1(a INTEGER PRIMARY KEY, b TEXT); 182bd5969a2Sdanielk1977 } 183bd5969a2Sdanielk1977 for {set i 0} {$i < 10} {incr i} { 184bd5969a2Sdanielk1977 set text [string repeat $i 220] 185bd5969a2Sdanielk1977 execsql { INSERT INTO t1 VALUES($i, $text) } 186bd5969a2Sdanielk1977 } 187bd5969a2Sdanielk1977 execsql { CREATE INDEX i1 ON t1(b) } 188bd5969a2Sdanielk1977} {} 189bd5969a2Sdanielk1977do_test corrupt-4.2 { 190bd5969a2Sdanielk1977 set iRoot [db one {SELECT rootpage FROM sqlite_master WHERE name = 'i1'}] 191bd5969a2Sdanielk1977 set iOffset [hexio_get_int [hexio_read test.db [expr 12+($iRoot-1)*1024] 2]] 192bd5969a2Sdanielk1977 set data [hexio_render_int32 [expr $iRoot - 1]] 193bd5969a2Sdanielk1977 hexio_write test.db [expr ($iRoot-1)*1024 + $iOffset] $data 194bd5969a2Sdanielk1977 db close 195bd5969a2Sdanielk1977 sqlite3 db test.db 196bd5969a2Sdanielk1977 197bd5969a2Sdanielk1977 # The following DELETE statement attempts to delete a cell stored on the 198bd5969a2Sdanielk1977 # root page of index i1. After this cell is deleted it must be replaced 199bd5969a2Sdanielk1977 # by a cell retrieved from the child page (a leaf) of the deleted cell. 200bd5969a2Sdanielk1977 # This will fail, as the block modified the database image so that the 201bd5969a2Sdanielk1977 # child page of the deleted cell is from a table (intkey) b-tree, not an 202bd5969a2Sdanielk1977 # index b-tree as expected. At one point this was causing an assert() 203bd5969a2Sdanielk1977 # to fail. 204bd5969a2Sdanielk1977 catchsql { DELETE FROM t1 WHERE rowid = 3 } 205bd5969a2Sdanielk1977} {1 {database disk image is malformed}} 206bd5969a2Sdanielk1977 2078f880a8cSdanielk1977do_test corrupt-5.1 { 2088f880a8cSdanielk1977 db close 209fda06befSmistachkin forcedelete test.db test.db-journal 2108f880a8cSdanielk1977 sqlite3 db test.db 2118f880a8cSdanielk1977 2128f880a8cSdanielk1977 execsql { PRAGMA page_size = 1024 } 2138f880a8cSdanielk1977 set ct "CREATE TABLE t1(c0 " 2148f880a8cSdanielk1977 set i 0 2158f880a8cSdanielk1977 while {[string length $ct] < 950} { append ct ", c[incr i]" } 2168f880a8cSdanielk1977 append ct ")" 2178f880a8cSdanielk1977 execsql $ct 2188f880a8cSdanielk1977} {} 2198f880a8cSdanielk1977 2208f880a8cSdanielk1977do_test corrupt-5.2 { 2218f880a8cSdanielk1977 db close 2228f880a8cSdanielk1977 hexio_write test.db 108 00000000 2238f880a8cSdanielk1977 sqlite3 db test.db 2248f880a8cSdanielk1977 catchsql { SELECT * FROM sqlite_master } 2258f880a8cSdanielk1977} {1 {database disk image is malformed}} 2268f880a8cSdanielk1977 22759d60c2cSdan# At one point, the specific corruption caused by this test case was 22859d60c2cSdan# causing a buffer overwrite. Although a crash was never demonstrated, 22959d60c2cSdan# running this testcase under valgrind revealed the problem. 2304361e79fSdando_test corrupt-6.1 { 2314361e79fSdan db close 232fda06befSmistachkin forcedelete test.db test.db-journal 2334361e79fSdan sqlite3 db test.db 2344361e79fSdan execsql { 2354361e79fSdan PRAGMA page_size = 1024; CREATE TABLE t1(x); 2364361e79fSdan } 2374361e79fSdan 2384361e79fSdan # The root page of t1 is 1024 bytes in size. The header is 8 bytes, and 2394361e79fSdan # each of the cells inserted by the following INSERT statements consume 2404361e79fSdan # 16 bytes (including the 2 byte cell-offset array entry). So the page 2414361e79fSdan # can contain up to 63 cells. 2424361e79fSdan for {set i 0} {$i < 63} {incr i} { 2434361e79fSdan execsql { INSERT INTO t1 VALUES( randomblob(10) ) } 2444361e79fSdan } 2454361e79fSdan 2464361e79fSdan # Free the cell stored right at the end of the page (at offset pgsz-14). 2474361e79fSdan execsql { DELETE FROM t1 WHERE rowid=1 } 2484361e79fSdan set rootpage [db one {SELECT rootpage FROM sqlite_master WHERE name = 't1'}] 2494361e79fSdan db close 2504361e79fSdan 2514361e79fSdan set offset [expr ($rootpage * 1024)-14+2] 2524361e79fSdan hexio_write test.db $offset 00FF 2534361e79fSdan sqlite3 db test.db 2544361e79fSdan 2554361e79fSdan catchsql { INSERT INTO t1 VALUES( randomblob(10) ) } 25659d60c2cSdan} {1 {database disk image is malformed}} 2574361e79fSdan 258d1f7e926Sdanifcapable oversize_cell_check { 259d1f7e926Sdan db close 260fda06befSmistachkin forcedelete test.db test.db-journal 261d1f7e926Sdan sqlite3 db test.db 262d1f7e926Sdan execsql { 263d1f7e926Sdan PRAGMA page_size = 1024; CREATE TABLE t1(x); 264d1f7e926Sdan } 265d1f7e926Sdan 266d1f7e926Sdan do_test corrupt-7.1 { 267d1f7e926Sdan for {set i 0} {$i < 39} {incr i} { 268d1f7e926Sdan execsql { 269d1f7e926Sdan INSERT INTO t1 VALUES(X'000100020003000400050006000700080009000A'); 270d1f7e926Sdan } 271d1f7e926Sdan } 272d1f7e926Sdan } {} 273d1f7e926Sdan db close 274d1f7e926Sdan 275d1f7e926Sdan # Corrupt the root page of table t1 so that the first offset in the 276d1f7e926Sdan # cell-offset array points to the data for the SQL blob associated with 277d1f7e926Sdan # record (rowid=10). The root page still passes the checks in btreeInitPage(), 278d1f7e926Sdan # because the start of said blob looks like the start of a legitimate 279d1f7e926Sdan # page cell. 280d1f7e926Sdan # 281d1f7e926Sdan # Test case cc-2 overwrites the blob so that it no longer looks like a 282d1f7e926Sdan # real cell. But, by the time it is overwritten, btreeInitPage() has already 283d1f7e926Sdan # initialized the root page, so no corruption is detected. 284d1f7e926Sdan # 285d1f7e926Sdan # Test case cc-3 inserts an extra record into t1, forcing balance-deeper 286d1f7e926Sdan # to run. After copying the contents of the root page to the new child, 287d1f7e926Sdan # btreeInitPage() is called on the child. This time, it detects corruption 288d1f7e926Sdan # (because the start of the blob associated with the (rowid=10) record 289d1f7e926Sdan # no longer looks like a real cell). At one point the code assumed that 290d1f7e926Sdan # detecting corruption was not possible at that point, and an assert() failed. 291d1f7e926Sdan # 292d1f7e926Sdan set fd [open test.db r+] 293d1f7e926Sdan fconfigure $fd -translation binary -encoding binary 294d1f7e926Sdan seek $fd [expr 1024+8] 295d1f7e926Sdan puts -nonewline $fd "\x03\x14" 296d1f7e926Sdan close $fd 297d1f7e926Sdan 298d1f7e926Sdan sqlite3 db test.db 299d1f7e926Sdan do_test corrupt-7.2 { 300d1f7e926Sdan execsql { 301d1f7e926Sdan UPDATE t1 SET x = X'870400020003000400050006000700080009000A' 302d1f7e926Sdan WHERE rowid = 10; 303d1f7e926Sdan } 304d1f7e926Sdan } {} 305d1f7e926Sdan do_test corrupt-7.3 { 306d1f7e926Sdan catchsql { 307d1f7e926Sdan INSERT INTO t1 VALUES(X'000100020003000400050006000700080009000A'); 308d1f7e926Sdan } 309d1f7e926Sdan } {1 {database disk image is malformed}} 310d1f7e926Sdan} 311d1f7e926Sdan 312887d4b2bSdandb close 313fda06befSmistachkinforcedelete test.db test.db-journal 314887d4b2bSdando_test corrupt-8.1 { 315887d4b2bSdan sqlite3 db test.db 316887d4b2bSdan execsql { 317887d4b2bSdan PRAGMA page_size = 1024; 318887d4b2bSdan PRAGMA secure_delete = on; 319887d4b2bSdan PRAGMA auto_vacuum = 0; 320887d4b2bSdan CREATE TABLE t1(x INTEGER PRIMARY KEY, y); 321887d4b2bSdan INSERT INTO t1 VALUES(5, randomblob(1900)); 322887d4b2bSdan } 323887d4b2bSdan 324887d4b2bSdan hexio_write test.db 2044 [hexio_render_int32 2] 325887d4b2bSdan hexio_write test.db 24 [hexio_render_int32 45] 326887d4b2bSdan 327887d4b2bSdan catchsql { INSERT OR REPLACE INTO t1 VALUES(5, randomblob(1900)) } 328887d4b2bSdan} {1 {database disk image is malformed}} 329887d4b2bSdan 3302ed11e7bSdandb close 331fda06befSmistachkinforcedelete test.db test.db-journal 3322ed11e7bSdando_test corrupt-8.2 { 3332ed11e7bSdan sqlite3 db test.db 3342ed11e7bSdan execsql { 3352ed11e7bSdan PRAGMA page_size = 1024; 3362ed11e7bSdan PRAGMA secure_delete = on; 3372ed11e7bSdan PRAGMA auto_vacuum = 0; 3382ed11e7bSdan CREATE TABLE t1(x INTEGER PRIMARY KEY, y); 3392ed11e7bSdan INSERT INTO t1 VALUES(5, randomblob(900)); 3402ed11e7bSdan INSERT INTO t1 VALUES(6, randomblob(900)); 3412ed11e7bSdan } 3422ed11e7bSdan 3432ed11e7bSdan hexio_write test.db 2047 FF 3442ed11e7bSdan hexio_write test.db 24 [hexio_render_int32 45] 3452ed11e7bSdan 3462ed11e7bSdan catchsql { INSERT INTO t1 VALUES(4, randomblob(1900)) } 3472ed11e7bSdan} {1 {database disk image is malformed}} 3482ed11e7bSdan 349ee696e22Sdrhfinish_test 350