12d16fb1dSshane# 2004 August 30 22d16fb1dSshane# 32d16fb1dSshane# The author disclaims copyright to this source code. In place of 42d16fb1dSshane# a legal notice, here is a blessing: 52d16fb1dSshane# 62d16fb1dSshane# May you do good and not evil. 72d16fb1dSshane# May you find forgiveness for yourself and forgive others. 82d16fb1dSshane# May you share freely, never taking more than you give. 92d16fb1dSshane# 102d16fb1dSshane#*********************************************************************** 112d16fb1dSshane# This file implements regression tests for SQLite library. 122d16fb1dSshane# 132d16fb1dSshane# This file implements tests to make sure SQLite does not crash or 142d16fb1dSshane# segfault if it sees a corrupt database file. It creates a base 152d16fb1dSshane# data base file, then tests that single byte corruptions in 162d16fb1dSshane# increasingly larger quantities are handled gracefully. 172d16fb1dSshane# 1893caf5adSdanielk1977# $Id: corruptC.test,v 1.14 2009/07/11 06:55:34 danielk1977 Exp $ 192d16fb1dSshane 20fda06befSmistachkincatch {forcedelete test.db test.db-journal test.bu} 212d16fb1dSshane 222d16fb1dSshaneset testdir [file dirname $argv0] 232d16fb1dSshanesource $testdir/tester.tcl 242d16fb1dSshane 2568928b6cSdan# Do not use a codec for tests in this file, as the database file is 2668928b6cSdan# manipulated directly using tcl scripts (using the [hexio_write] command). 2768928b6cSdan# 2868928b6cSdando_not_use_codec 2968928b6cSdan 3009fe6143Sdrh# These tests deal with corrupt database files 3109fe6143Sdrh# 3209fe6143Sdrhdatabase_may_be_corrupt 3309fe6143Sdrh 342d16fb1dSshane# Construct a compact, dense database for testing. 352d16fb1dSshane# 362d16fb1dSshanedo_test corruptC-1.1 { 37*66c48907Sdrh sqlite3_db_config db LEGACY_FILE_FORMAT 1 382d16fb1dSshane execsql { 39ef988b47Sdanielk1977 PRAGMA auto_vacuum = 0; 402d16fb1dSshane BEGIN; 41826d5b7eSshane CREATE TABLE t1(x,y); 42826d5b7eSshane INSERT INTO t1 VALUES(1,1); 43826d5b7eSshane INSERT OR IGNORE INTO t1 SELECT x*2,y FROM t1; 44826d5b7eSshane INSERT OR IGNORE INTO t1 SELECT x*3,y FROM t1; 45826d5b7eSshane INSERT OR IGNORE INTO t1 SELECT x*5,y FROM t1; 46826d5b7eSshane INSERT OR IGNORE INTO t1 SELECT x*7,y FROM t1; 47826d5b7eSshane INSERT OR IGNORE INTO t1 SELECT x*11,y FROM t1; 48826d5b7eSshane INSERT OR IGNORE INTO t1 SELECT x*13,y FROM t1; 492d16fb1dSshane CREATE INDEX t1i1 ON t1(x); 500af3f893Sshane CREATE TABLE t2 AS SELECT x,2 as y FROM t1 WHERE rowid%5!=0; 512d16fb1dSshane COMMIT; 522d16fb1dSshane } 532d16fb1dSshane} {} 542d16fb1dSshane 552d16fb1dSshaneifcapable {integrityck} { 562d16fb1dSshane integrity_check corruptC-1.2 572d16fb1dSshane} 582d16fb1dSshane 592d16fb1dSshane# Generate random integer 602d16fb1dSshane# 612d16fb1dSshaneproc random {range} { 622d16fb1dSshane return [expr {round(rand()*$range)}] 632d16fb1dSshane} 642d16fb1dSshane 652d16fb1dSshane# Setup for the tests. Make a backup copy of the good database in test.bu. 662d16fb1dSshane# 67826d5b7eSshanedb close 68fda06befSmistachkinforcecopy test.db test.bu 69826d5b7eSshanesqlite3 db test.db 702d16fb1dSshaneset fsize [file size test.db] 712d16fb1dSshane 72dcc50b74Sshane# Set a quasi-random random seed. 73430e74cdSdanif {[info exists ::G(issoak)]} { 74dcc50b74Sshane # If we are doing SOAK tests, we want a different 75dcc50b74Sshane # random seed for each run. Ideally we would like 76dcc50b74Sshane # to use [clock clicks] or something like that here. 77dcc50b74Sshane set qseed [file mtime test.db] 78dcc50b74Sshane} else { 79dcc50b74Sshane # If we are not doing soak tests, 80dcc50b74Sshane # make it repeatable. 81dcc50b74Sshane set qseed 0 82dcc50b74Sshane} 83dcc50b74Sshaneexpr srand($qseed) 84dcc50b74Sshane 855780ebdfSshane# 86dcc50b74Sshane# First test some specific corruption tests found from earlier runs 87dcc50b74Sshane# with specific seeds. 885780ebdfSshane# 895780ebdfSshane 905780ebdfSshane# test that a corrupt content offset size is handled (seed 5577) 915780ebdfSshanedo_test corruptC-2.1 { 925780ebdfSshane db close 93fda06befSmistachkin forcecopy test.bu test.db 945780ebdfSshane 955780ebdfSshane # insert corrupt byte(s) 96826d5b7eSshane hexio_write test.db 2053 [format %02x 0x04] 975780ebdfSshane 985780ebdfSshane sqlite3 db test.db 995780ebdfSshane catchsql {PRAGMA integrity_check} 100150dfbd2Sdan} {0 {{*** in database main *** 101150dfbd2SdanPage 3: free space corruption}}} 1025780ebdfSshane 103826d5b7eSshane# test that a corrupt content offset size is handled (seed 5649) 1045ad74a76Sdrh# 1055ad74a76Sdrh# Update 2016-12-27: As of check-in [0b86fbca66] "In sqlite3BtreeInsert() when 1065ad74a76Sdrh# replacing a re-existing row, try to overwrite the cell directly rather than 1075ad74a76Sdrh# deallocate and reallocate the cell" on 2016-12-09, this test case no longer 1085ad74a76Sdrh# detects the offset size problem during the UPDATE. We have to run a subsequent 1095ad74a76Sdrh# integrity_check to see it. 110826d5b7eSshanedo_test corruptC-2.2 { 111826d5b7eSshane db close 112fda06befSmistachkin forcecopy test.bu test.db 113826d5b7eSshane 114826d5b7eSshane # insert corrupt byte(s) 115826d5b7eSshane hexio_write test.db 27 [format %02x 0x08] 116826d5b7eSshane hexio_write test.db 233 [format %02x 0x6a] 117826d5b7eSshane hexio_write test.db 328 [format %02x 0x67] 118826d5b7eSshane hexio_write test.db 750 [format %02x 0x1f] 119826d5b7eSshane hexio_write test.db 1132 [format %02x 0x52] 120826d5b7eSshane hexio_write test.db 1133 [format %02x 0x84] 121826d5b7eSshane hexio_write test.db 1220 [format %02x 0x01] 122826d5b7eSshane hexio_write test.db 3688 [format %02x 0xc1] 123826d5b7eSshane hexio_write test.db 3714 [format %02x 0x58] 124826d5b7eSshane hexio_write test.db 3746 [format %02x 0x9a] 125826d5b7eSshane 126826d5b7eSshane sqlite3 db test.db 1275ad74a76Sdrh db eval {UPDATE t1 SET y=1} 1285ad74a76Sdrh db eval {PRAGMA integrity_check} 1295ad74a76Sdrh} {/Offset .* out of range/} 1305780ebdfSshane 13134ac18daSshane# test that a corrupt free cell size is handled (seed 13329) 13234ac18daSshanedo_test corruptC-2.3 { 13334ac18daSshane db close 134fda06befSmistachkin forcecopy test.bu test.db 13534ac18daSshane 13634ac18daSshane # insert corrupt byte(s) 13734ac18daSshane hexio_write test.db 1094 [format %02x 0x76] 13834ac18daSshane 13934ac18daSshane sqlite3 db test.db 14034ac18daSshane catchsql {UPDATE t1 SET y=1} 1410af3f893Sshane} {1 {database disk image is malformed}} 1420af3f893Sshane 1430af3f893Sshane# test that a corrupt free cell size is handled (seed 169571) 1440af3f893Sshanedo_test corruptC-2.4 { 1450af3f893Sshane db close 146fda06befSmistachkin forcecopy test.bu test.db 1470af3f893Sshane 1480af3f893Sshane # insert corrupt byte(s) 1490af3f893Sshane hexio_write test.db 3119 [format %02x 0xdf] 1500af3f893Sshane 1510af3f893Sshane sqlite3 db test.db 1520af3f893Sshane catchsql {UPDATE t2 SET y='abcdef-uvwxyz'} 1530af3f893Sshane} {1 {database disk image is malformed}} 1540af3f893Sshane 1550af3f893Sshane# test that a corrupt free cell size is handled (seed 169571) 1560af3f893Sshanedo_test corruptC-2.5 { 1570af3f893Sshane db close 158fda06befSmistachkin forcecopy test.bu test.db 1590af3f893Sshane 1600af3f893Sshane # insert corrupt byte(s) 1610af3f893Sshane hexio_write test.db 3119 [format %02x 0xdf] 1620af3f893Sshane hexio_write test.db 4073 [format %02x 0xbf] 1630af3f893Sshane 1640af3f893Sshane sqlite3 db test.db 1650af3f893Sshane catchsql {BEGIN; UPDATE t2 SET y='abcdef-uvwxyz'; ROLLBACK;} 1660af3f893Sshane catchsql {PRAGMA integrity_check} 1670af3f893Sshane} {0 {{*** in database main *** 168bb689622SdrhOn tree page 4 cell 19: Extends off end of page} {database disk image is malformed}}} 16993caf5adSdanielk1977 17093caf5adSdanielk1977# {0 {{*** in database main *** 17193caf5adSdanielk1977# Corruption detected in cell 710 on page 4 17293caf5adSdanielk1977# Multiple uses for byte 661 of page 4 17393caf5adSdanielk1977# Fragmented space is 249 byte reported as 21 on page 4}}} 1740af3f893Sshane 1750af3f893Sshane# test that a corrupt free cell size is handled (seed 169595) 1760af3f893Sshanedo_test corruptC-2.6 { 1770af3f893Sshane db close 178fda06befSmistachkin forcecopy test.bu test.db 1790af3f893Sshane 1800af3f893Sshane # insert corrupt byte(s) 1810af3f893Sshane hexio_write test.db 619 [format %02x 0xe2] 1820af3f893Sshane hexio_write test.db 3150 [format %02x 0xa8] 1830af3f893Sshane 1840af3f893Sshane sqlite3 db test.db 1850af3f893Sshane catchsql {BEGIN; UPDATE t2 SET y='abcdef-uvwxyz'; ROLLBACK;} 1860af3f893Sshane} {1 {database disk image is malformed}} 18734ac18daSshane 188dcc50b74Sshane# corruption (seed 178692) 189dcc50b74Sshanedo_test corruptC-2.7 { 190dcc50b74Sshane db close 191fda06befSmistachkin forcecopy test.bu test.db 192dcc50b74Sshane 193dcc50b74Sshane # insert corrupt byte(s) 194dcc50b74Sshane hexio_write test.db 3074 [format %02x 0xa0] 195dcc50b74Sshane 196dcc50b74Sshane sqlite3 db test.db 197dcc50b74Sshane catchsql {BEGIN; UPDATE t2 SET y='abcdef-uvwxyz'; ROLLBACK;} 198dcc50b74Sshane} {1 {database disk image is malformed}} 199dcc50b74Sshane 2009df5ad58Sdrh 201dcc50b74Sshane# corruption (seed 179069) 2029df5ad58Sdrh# Obsolete. With single-pass DELETE the corruption in the 2039df5ad58Sdrh# main database is not detected. 2049df5ad58Sdrhif 0 { 205dcc50b74Sshanedo_test corruptC-2.8 { 206dcc50b74Sshane db close 207fda06befSmistachkin forcecopy test.bu test.db 208dcc50b74Sshane 209dcc50b74Sshane # insert corrupt byte(s) 210dcc50b74Sshane hexio_write test.db 1393 [format %02x 0x7d] 211dcc50b74Sshane hexio_write test.db 84 [format %02x 0x19] 212dcc50b74Sshane hexio_write test.db 3287 [format %02x 0x3b] 213dcc50b74Sshane hexio_write test.db 2564 [format %02x 0xed] 214dcc50b74Sshane hexio_write test.db 2139 [format %02x 0x55] 215dcc50b74Sshane 216dcc50b74Sshane sqlite3 db test.db 217dcc50b74Sshane catchsql {BEGIN; DELETE FROM t1 WHERE x>13; ROLLBACK;} 218dcc50b74Sshane} {1 {database disk image is malformed}} 2199df5ad58Sdrh} 220dcc50b74Sshane 221dcc50b74Sshane# corruption (seed 170434) 2220d4a0cdfSdan# 2230d4a0cdfSdan# UPDATE: Prior to 3.8.2, this used to return SQLITE_CORRUPT. It no longer 2240d4a0cdfSdan# does. That is Ok, the point of these tests is to verify that no buffer 2250d4a0cdfSdan# overruns or overreads can be caused by corrupt databases. 226dcc50b74Sshanedo_test corruptC-2.9 { 227dcc50b74Sshane db close 228fda06befSmistachkin forcecopy test.bu test.db 229dcc50b74Sshane 230dcc50b74Sshane # insert corrupt byte(s) 231dcc50b74Sshane hexio_write test.db 2095 [format %02x 0xd6] 232dcc50b74Sshane 233dcc50b74Sshane sqlite3 db test.db 234dcc50b74Sshane catchsql {BEGIN; DELETE FROM t1 WHERE x>13; ROLLBACK;} 2350d4a0cdfSdan} {0 {}} 236dcc50b74Sshane 237dcc50b74Sshane# corruption (seed 186504) 238dcc50b74Sshanedo_test corruptC-2.10 { 239dcc50b74Sshane db close 240fda06befSmistachkin forcecopy test.bu test.db 241dcc50b74Sshane 242dcc50b74Sshane # insert corrupt byte(s) 243dcc50b74Sshane hexio_write test.db 3130 [format %02x 0x02] 244dcc50b74Sshane 245dcc50b74Sshane sqlite3 db test.db 246dcc50b74Sshane catchsql {BEGIN; UPDATE t2 SET y='abcdef-uvwxyz'; ROLLBACK;} 247dcc50b74Sshane} {1 {database disk image is malformed}} 248dcc50b74Sshane 249dcc50b74Sshane# corruption (seed 1589) 250dcc50b74Sshanedo_test corruptC-2.11 { 251dcc50b74Sshane db close 252fda06befSmistachkin forcecopy test.bu test.db 253dcc50b74Sshane 254dcc50b74Sshane # insert corrupt byte(s) 255dcc50b74Sshane hexio_write test.db 55 [format %02x 0xa7] 256dcc50b74Sshane 257dcc50b74Sshane sqlite3 db test.db 258dcc50b74Sshane catchsql {BEGIN; CREATE TABLE t3 AS SELECT x,3 as y FROM t2 WHERE rowid%5!=0; ROLLBACK;} 259dcc50b74Sshane} {1 {database disk image is malformed}} 260dcc50b74Sshane 261dcc50b74Sshane# corruption (seed 14166) 262dcc50b74Sshanedo_test corruptC-2.12 { 263dcc50b74Sshane db close 264fda06befSmistachkin forcecopy test.bu test.db 265dcc50b74Sshane 266dcc50b74Sshane # insert corrupt byte(s) 267dcc50b74Sshane hexio_write test.db 974 [format %02x 0x2e] 268dcc50b74Sshane 269dcc50b74Sshane sqlite3 db test.db 270dcc50b74Sshane catchsql {SELECT count(*) FROM sqlite_master;} 271dcc50b74Sshane} {1 {malformed database schema (t1i1) - corrupt database}} 272dcc50b74Sshane 273dcc50b74Sshane# corruption (seed 218803) 274dcc50b74Sshanedo_test corruptC-2.13 { 275dcc50b74Sshane db close 276fda06befSmistachkin forcecopy test.bu test.db 277dcc50b74Sshane 278dcc50b74Sshane # insert corrupt byte(s) 279dcc50b74Sshane hexio_write test.db 102 [format %02x 0x12] 280dcc50b74Sshane 281dcc50b74Sshane sqlite3 db test.db 282dcc50b74Sshane catchsql {BEGIN; CREATE TABLE t3 AS SELECT x,3 as y FROM t2 WHERE rowid%5!=0; ROLLBACK;} 283dcc50b74Sshane} {1 {database disk image is malformed}} 284dcc50b74Sshane 285e589a67fSdanielk1977do_test corruptC-2.14 { 286e589a67fSdanielk1977 db close 287fda06befSmistachkin forcecopy test.bu test.db 288e589a67fSdanielk1977 289e589a67fSdanielk1977 sqlite3 db test.db 290e589a67fSdanielk1977 set blob [string repeat abcdefghij 10000] 291e589a67fSdanielk1977 execsql { INSERT INTO t1 VALUES (1, $blob) } 292e589a67fSdanielk1977 293e589a67fSdanielk1977 sqlite3 db test.db 294e589a67fSdanielk1977 set filesize [file size test.db] 295e589a67fSdanielk1977 hexio_write test.db [expr $filesize-2048] 00000001 296e589a67fSdanielk1977 catchsql {DELETE FROM t1 WHERE rowid = (SELECT max(rowid) FROM t1)} 297e589a67fSdanielk1977} {1 {database disk image is malformed}} 298dcc50b74Sshane 299026e598dSdan# At one point this particular corrupt database was causing a buffer 300026e598dSdan# overread. Which caused a crash in a run of all.test once. 301026e598dSdan# 302026e598dSdando_test corruptC-2.15 { 303026e598dSdan db close 304fda06befSmistachkin forcecopy test.bu test.db 305026e598dSdan hexio_write test.db 986 b9 306026e598dSdan sqlite3 db test.db 307026e598dSdan catchsql {SELECT count(*) FROM sqlite_master;} 308b073771cSdrh} {1 {database disk image is malformed}} 309026e598dSdan 3105780ebdfSshane# 311bbc795fdSshane# Now test for a series of quasi-random seeds. 312bbc795fdSshane# We loop over the entire file size and touch 313bbc795fdSshane# each byte at least once. 314bbc795fdSshanefor {set tn 0} {$tn<$fsize} {incr tn 1} { 3152d16fb1dSshane 3162d16fb1dSshane # setup for test 3172d16fb1dSshane db close 318fda06befSmistachkin forcecopy test.bu test.db 319826d5b7eSshane sqlite3 db test.db 3202d16fb1dSshane 3212d16fb1dSshane # Seek to a random location in the file, and write a random single byte 3222d16fb1dSshane # value. Then do various operations on the file to make sure that 3232d16fb1dSshane # the database engine can handle the corruption gracefully. 3242d16fb1dSshane # 3252d16fb1dSshane set last 0 326dcc50b74Sshane for {set i 1} {$i<=512 && !$last} {incr i 1} { 3272d16fb1dSshane 328826d5b7eSshane db close 329bbc795fdSshane if {$i==1} { 330bbc795fdSshane # on the first corrupt value, use location $tn 331bbc795fdSshane # this ensures that we touch each location in the 332bbc795fdSshane # file at least once. 333bbc795fdSshane set roffset $tn 334bbc795fdSshane } else { 335bbc795fdSshane # insert random byte at random location 336dcc50b74Sshane set roffset [random $fsize] 337bbc795fdSshane } 338dcc50b74Sshane set rbyte [format %02x [random 255]] 339dcc50b74Sshane 340dcc50b74Sshane # You can uncomment the following to have it trace 341dcc50b74Sshane # exactly how it's corrupting the file. This is 342dcc50b74Sshane # useful for generating the "seed specific" tests 343dcc50b74Sshane # above. 344dcc50b74Sshane # set rline "$roffset $rbyte" 345dcc50b74Sshane # puts stdout $rline 346dcc50b74Sshane 347dcc50b74Sshane hexio_write test.db $roffset $rbyte 348826d5b7eSshane sqlite3 db test.db 3492d16fb1dSshane 3502d16fb1dSshane # do a few random operations to make sure that if 3512d16fb1dSshane # they error, they error gracefully instead of crashing. 352dcc50b74Sshane do_test corruptC-3.$tn.($qseed).$i.1 { 3532d16fb1dSshane catchsql {SELECT count(*) FROM sqlite_master} 3542d16fb1dSshane set x {} 3552d16fb1dSshane } {} 356dcc50b74Sshane do_test corruptC-3.$tn.($qseed).$i.2 { 3572d16fb1dSshane catchsql {SELECT count(*) FROM t1} 3582d16fb1dSshane set x {} 3592d16fb1dSshane } {} 360dcc50b74Sshane do_test corruptC-3.$tn.($qseed).$i.3 { 3612d16fb1dSshane catchsql {SELECT count(*) FROM t1 WHERE x>13} 3622d16fb1dSshane set x {} 3632d16fb1dSshane } {} 364dcc50b74Sshane do_test corruptC-3.$tn.($qseed).$i.4 { 3652d16fb1dSshane catchsql {SELECT count(*) FROM t2} 3662d16fb1dSshane set x {} 3672d16fb1dSshane } {} 368dcc50b74Sshane do_test corruptC-3.$tn.($qseed).$i.5 { 3692d16fb1dSshane catchsql {SELECT count(*) FROM t2 WHERE x<13} 3702d16fb1dSshane set x {} 3712d16fb1dSshane } {} 372dcc50b74Sshane do_test corruptC-3.$tn.($qseed).$i.6 { 37334ac18daSshane catchsql {BEGIN; UPDATE t1 SET y=1; ROLLBACK;} 374826d5b7eSshane set x {} 375826d5b7eSshane } {} 376dcc50b74Sshane do_test corruptC-3.$tn.($qseed).$i.7 { 3770af3f893Sshane catchsql {BEGIN; UPDATE t2 SET y='abcdef-uvwxyz'; ROLLBACK;} 378826d5b7eSshane set x {} 379826d5b7eSshane } {} 380dcc50b74Sshane do_test corruptC-3.$tn.($qseed).$i.8 { 381dcc50b74Sshane catchsql {BEGIN; DELETE FROM t1 WHERE x>13; ROLLBACK;} 382dcc50b74Sshane set x {} 383dcc50b74Sshane } {} 384dcc50b74Sshane do_test corruptC-3.$tn.($qseed).$i.9 { 385dcc50b74Sshane catchsql {BEGIN; DELETE FROM t2 WHERE x<13; ROLLBACK;} 386dcc50b74Sshane set x {} 387dcc50b74Sshane } {} 388dcc50b74Sshane do_test corruptC-3.$tn.($qseed).$i.10 { 389dcc50b74Sshane catchsql {BEGIN; CREATE TABLE t3 AS SELECT x,3 as y FROM t2 WHERE rowid%5!=0; ROLLBACK;} 390dcc50b74Sshane set x {} 391dcc50b74Sshane } {} 3922d16fb1dSshane 3932d16fb1dSshane # check the integrity of the database. 3942d16fb1dSshane # once the corruption is detected, we can stop. 3952d16fb1dSshane ifcapable {integrityck} { 3962d16fb1dSshane set res [ catchsql {PRAGMA integrity_check} ] 3972d16fb1dSshane set ans [lindex $res 1] 3982d16fb1dSshane if { [ string compare $ans "ok" ] != 0 } { 3992d16fb1dSshane set last -1 4002d16fb1dSshane } 4012d16fb1dSshane } 4022d16fb1dSshane # if we are not capable of doing an integrity check, 4032d16fb1dSshane # stop after corrupting 5 bytes. 4042d16fb1dSshane ifcapable {!integrityck} { 4052d16fb1dSshane if { $i > 5 } { 4062d16fb1dSshane set last -1 4072d16fb1dSshane } 4082d16fb1dSshane } 4092d16fb1dSshane 4102d16fb1dSshane # Check that no page references were leaked. 41134ac18daSshane # TBD: need to figure out why this doesn't work 41234ac18daSshane # work with ROLLBACKs... 41334ac18daSshane if {0} { 414dcc50b74Sshane do_test corruptC-3.$tn.($qseed).$i.11 { 4152d16fb1dSshane set bt [btree_from_db db] 4162d16fb1dSshane db_enter db 4172d16fb1dSshane array set stats [btree_pager_stats $bt] 4182d16fb1dSshane db_leave db 4192d16fb1dSshane set stats(ref) 4202d16fb1dSshane } {0} 4212d16fb1dSshane } 42234ac18daSshane } 4232d16fb1dSshane # end for i 4242d16fb1dSshane 4252d16fb1dSshane} 4262d16fb1dSshane# end for tn 4272d16fb1dSshane 4282d16fb1dSshanefinish_test 429