1# 2004 August 30 2# 3# The author disclaims copyright to this source code. In place of 4# a legal notice, here is a blessing: 5# 6# May you do good and not evil. 7# May you find forgiveness for yourself and forgive others. 8# May you share freely, never taking more than you give. 9# 10#*********************************************************************** 11# This file implements regression tests for SQLite library. 12# 13# This file implements tests to make sure SQLite does not crash or 14# segfault if it sees a corrupt database file. 15# 16# $Id: corrupt2.test,v 1.20 2009/04/06 17:50:03 danielk1977 Exp $ 17 18set testdir [file dirname $argv0] 19source $testdir/tester.tcl 20 21# The following tests - corrupt2-1.* - create some databases corrupted in 22# specific ways and ensure that SQLite detects them as corrupt. 23# 24do_test corrupt2-1.1 { 25 execsql { 26 PRAGMA auto_vacuum=0; 27 PRAGMA page_size=1024; 28 CREATE TABLE abc(a, b, c); 29 } 30} {} 31 32do_test corrupt2-1.2 { 33 34 # Corrupt the 16 byte magic string at the start of the file 35 file delete -force corrupt.db 36 file delete -force corrupt.db-journal 37 copy_file test.db corrupt.db 38 set f [open corrupt.db RDWR] 39 seek $f 8 start 40 puts $f blah 41 close $f 42 43 sqlite3 db2 corrupt.db 44 catchsql { 45 SELECT * FROM sqlite_master; 46 } db2 47} {1 {file is encrypted or is not a database}} 48 49do_test corrupt2-1.3 { 50 db2 close 51 52 # Corrupt the page-size (bytes 16 and 17 of page 1). 53 file delete -force corrupt.db 54 file delete -force corrupt.db-journal 55 copy_file test.db corrupt.db 56 set f [open corrupt.db RDWR] 57 fconfigure $f -encoding binary 58 seek $f 16 start 59 puts -nonewline $f "\x00\xFF" 60 close $f 61 62 sqlite3 db2 corrupt.db 63 catchsql { 64 SELECT * FROM sqlite_master; 65 } db2 66} {1 {file is encrypted or is not a database}} 67 68do_test corrupt2-1.4 { 69 db2 close 70 71 # Corrupt the free-block list on page 1. 72 file delete -force corrupt.db 73 file delete -force corrupt.db-journal 74 copy_file test.db corrupt.db 75 set f [open corrupt.db RDWR] 76 fconfigure $f -encoding binary 77 seek $f 101 start 78 puts -nonewline $f "\xFF\xFF" 79 close $f 80 81 sqlite3 db2 corrupt.db 82 catchsql { 83 SELECT * FROM sqlite_master; 84 } db2 85} {1 {database disk image is malformed}} 86 87do_test corrupt2-1.5 { 88 db2 close 89 90 # Corrupt the free-block list on page 1. 91 file delete -force corrupt.db 92 file delete -force corrupt.db-journal 93 copy_file test.db corrupt.db 94 set f [open corrupt.db RDWR] 95 fconfigure $f -encoding binary 96 seek $f 101 start 97 puts -nonewline $f "\x00\xC8" 98 seek $f 200 start 99 puts -nonewline $f "\x00\x00" 100 puts -nonewline $f "\x10\x00" 101 close $f 102 103 sqlite3 db2 corrupt.db 104 catchsql { 105 SELECT * FROM sqlite_master; 106 } db2 107} {1 {database disk image is malformed}} 108db2 close 109 110# Corrupt a database by having 2 indices of the same name: 111do_test corrupt2-2.1 { 112 113 file delete -force corrupt.db 114 file delete -force corrupt.db-journal 115 copy_file test.db corrupt.db 116 117 sqlite3 db2 corrupt.db 118 execsql { 119 CREATE INDEX a1 ON abc(a); 120 CREATE INDEX a2 ON abc(b); 121 PRAGMA writable_schema = 1; 122 UPDATE sqlite_master 123 SET name = 'a3', sql = 'CREATE INDEX a3' || substr(sql, 16, 10000) 124 WHERE type = 'index'; 125 PRAGMA writable_schema = 0; 126 } db2 127 128 db2 close 129 sqlite3 db2 corrupt.db 130 catchsql { 131 SELECT * FROM sqlite_master; 132 } db2 133} {1 {malformed database schema (a3) - index a3 already exists}} 134 135db2 close 136 137do_test corrupt2-3.1 { 138 file delete -force corrupt.db 139 file delete -force corrupt.db-journal 140 sqlite3 db2 corrupt.db 141 142 execsql { 143 PRAGMA auto_vacuum = 1; 144 PRAGMA page_size = 1024; 145 CREATE TABLE t1(a, b, c); 146 CREATE TABLE t2(a, b, c); 147 INSERT INTO t2 VALUES(randomblob(100), randomblob(100), randomblob(100)); 148 INSERT INTO t2 SELECT * FROM t2; 149 INSERT INTO t2 SELECT * FROM t2; 150 INSERT INTO t2 SELECT * FROM t2; 151 INSERT INTO t2 SELECT * FROM t2; 152 } db2 153 154 db2 close 155 156 # On the root page of table t2 (page 4), set one of the child page-numbers 157 # to 0. This corruption will be detected when SQLite attempts to update 158 # the pointer-map after moving the content of page 4 to page 3 as part 159 # of the DROP TABLE operation below. 160 # 161 set fd [open corrupt.db r+] 162 fconfigure $fd -encoding binary -translation binary 163 seek $fd [expr 1024*3 + 12] 164 set zCelloffset [read $fd 2] 165 binary scan $zCelloffset S iCelloffset 166 seek $fd [expr 1024*3 + $iCelloffset] 167 puts -nonewline $fd "\00\00\00\00" 168 close $fd 169 170 sqlite3 db2 corrupt.db 171 catchsql { 172 DROP TABLE t1; 173 } db2 174} {1 {database disk image is malformed}} 175 176do_test corrupt2-4.1 { 177 catchsql { 178 SELECT * FROM t2; 179 } db2 180} {1 {database disk image is malformed}} 181 182db2 close 183 184unset -nocomplain result 185do_test corrupt2-5.1 { 186 file delete -force corrupt.db 187 file delete -force corrupt.db-journal 188 sqlite3 db2 corrupt.db 189 190 execsql { 191 PRAGMA auto_vacuum = 0; 192 PRAGMA page_size = 1024; 193 CREATE TABLE t1(a, b, c); 194 CREATE TABLE t2(a, b, c); 195 INSERT INTO t2 VALUES(randomblob(100), randomblob(100), randomblob(100)); 196 INSERT INTO t2 SELECT * FROM t2; 197 INSERT INTO t2 SELECT * FROM t2; 198 INSERT INTO t2 SELECT * FROM t2; 199 INSERT INTO t2 SELECT * FROM t2; 200 INSERT INTO t1 SELECT * FROM t2; 201 } db2 202 203 db2 close 204 205 # This block links a page from table t2 into the t1 table structure. 206 # 207 set fd [open corrupt.db r+] 208 fconfigure $fd -encoding binary -translation binary 209 seek $fd [expr 1024 + 12] 210 set zCelloffset [read $fd 2] 211 binary scan $zCelloffset S iCelloffset 212 seek $fd [expr 1024 + $iCelloffset] 213 set zChildPage [read $fd 4] 214 seek $fd [expr 2*1024 + 12] 215 set zCelloffset [read $fd 2] 216 binary scan $zCelloffset S iCelloffset 217 seek $fd [expr 2*1024 + $iCelloffset] 218 puts -nonewline $fd $zChildPage 219 close $fd 220 221 sqlite3 db2 corrupt.db 222 db2 eval {SELECT rowid FROM t1} { 223 set result [db2 eval {pragma integrity_check}] 224 break 225 } 226 set result 227} {{*** in database main *** 228On tree page 2 cell 0: 2nd reference to page 10 229On tree page 2 cell 1: Child page depth differs 230Page 4 is never used}} 231 232db2 close 233 234proc corruption_test {args} { 235 set A(-corrupt) {} 236 set A(-sqlprep) {} 237 set A(-tclprep) {} 238 array set A $args 239 240 catch {db close} 241 file delete -force corrupt.db 242 file delete -force corrupt.db-journal 243 244 sqlite3 db corrupt.db 245 eval $A(-tclprep) 246 db eval $A(-sqlprep) 247 db close 248 249 eval $A(-corrupt) 250 251 sqlite3 db corrupt.db 252 eval $A(-test) 253} 254 255ifcapable autovacuum { 256 # The tests within this block - corrupt2-6.* - aim to test corruption 257 # detection within an incremental-vacuum. When an incremental-vacuum 258 # step is executed, the last non-free page of the database file is 259 # moved into a free space in the body of the file. After doing so, 260 # the page reference in the parent page must be updated to refer 261 # to the new location. These tests test the outcome of corrupting 262 # that page reference before performing the incremental vacuum. 263 # 264 265 # The last page in the database page is the second page 266 # in an overflow chain. 267 # 268 corruption_test -sqlprep { 269 PRAGMA auto_vacuum = incremental; 270 PRAGMA page_size = 1024; 271 CREATE TABLE t1(a, b); 272 INSERT INTO t1 VALUES(1, randomblob(2500)); 273 INSERT INTO t1 VALUES(2, randomblob(2500)); 274 DELETE FROM t1 WHERE a = 1; 275 } -corrupt { 276 hexio_write corrupt.db [expr 1024*5] 00000008 277 } -test { 278 do_test corrupt2-6.1 { 279 catchsql { pragma incremental_vacuum = 1 } 280 } {1 {database disk image is malformed}} 281 } 282 283 # The last page in the database page is a non-root b-tree page. 284 # 285 corruption_test -sqlprep { 286 PRAGMA auto_vacuum = incremental; 287 PRAGMA page_size = 1024; 288 CREATE TABLE t1(a INTEGER PRIMARY KEY, b); 289 INSERT INTO t1 VALUES(1, randomblob(2500)); 290 INSERT INTO t1 VALUES(2, randomblob(50)); 291 INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1; 292 INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1; 293 INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1; 294 INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1; 295 DELETE FROM t1 WHERE a = 1; 296 } -corrupt { 297 hexio_write corrupt.db [expr 1024*2 + 8] 00000009 298 } -test { 299 do_test corrupt2-6.2 { 300 catchsql { pragma incremental_vacuum = 1 } 301 } {1 {database disk image is malformed}} 302 } 303 304 # Set up a pointer-map entry so that the last page of the database 305 # file appears to be a b-tree root page. This should be detected 306 # as corruption. 307 # 308 corruption_test -sqlprep { 309 PRAGMA auto_vacuum = incremental; 310 PRAGMA page_size = 1024; 311 CREATE TABLE t1(a INTEGER PRIMARY KEY, b); 312 INSERT INTO t1 VALUES(1, randomblob(2500)); 313 INSERT INTO t1 VALUES(2, randomblob(2500)); 314 INSERT INTO t1 VALUES(3, randomblob(2500)); 315 DELETE FROM t1 WHERE a = 1; 316 } -corrupt { 317 set nPage [expr [file size corrupt.db] / 1024] 318 hexio_write corrupt.db [expr 1024 + ($nPage-3)*5] 010000000 319 } -test { 320 do_test corrupt2-6.3 { 321 catchsql { pragma incremental_vacuum = 1 } 322 } {1 {database disk image is malformed}} 323 } 324 325 corruption_test -sqlprep { 326 PRAGMA auto_vacuum = 1; 327 PRAGMA page_size = 1024; 328 CREATE TABLE t1(a INTEGER PRIMARY KEY, b); 329 INSERT INTO t1 VALUES(1, randomblob(2500)); 330 DELETE FROM t1 WHERE a = 1; 331 } -corrupt { 332 set nAppend [expr 1024*207 - [file size corrupt.db]] 333 set fd [open corrupt.db r+] 334 seek $fd 0 end 335 puts -nonewline $fd [string repeat x $nAppend] 336 close $fd 337 hexio_write corrupt.db 28 00000000 338 } -test { 339 do_test corrupt2-6.4 { 340 catchsql { 341 BEGIN EXCLUSIVE; 342 COMMIT; 343 } 344 } {1 {database disk image is malformed}} 345 } 346} 347 348 349set sqlprep { 350 PRAGMA auto_vacuum = 0; 351 PRAGMA page_size = 1024; 352 CREATE TABLE t1(a INTEGER PRIMARY KEY, b); 353 CREATE INDEX i1 ON t1(b); 354 INSERT INTO t1 VALUES(1, randomblob(50)); 355 INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1; 356 INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1; 357 INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1; 358 INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1; 359 INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1; 360 INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1; 361} 362 363corruption_test -sqlprep $sqlprep -corrupt { 364 # Set the page-flags of one of the leaf pages of the index B-Tree to 365 # 0x0D (interpreted by SQLite as "leaf page of a table B-Tree"). 366 # 367 set fd [open corrupt.db r+] 368 fconfigure $fd -translation binary -encoding binary 369 seek $fd [expr 1024*2 + 8] 370 set zRightChild [read $fd 4] 371 binary scan $zRightChild I iRightChild 372 seek $fd [expr 1024*($iRightChild-1)] 373 puts -nonewline $fd "\x0D" 374 close $fd 375} -test { 376 do_test corrupt2-7.1 { 377 catchsql { SELECT b FROM t1 ORDER BY b ASC } 378 } {1 {database disk image is malformed}} 379} 380 381corruption_test -sqlprep $sqlprep -corrupt { 382 # Mess up the page-header of one of the leaf pages of the index B-Tree. 383 # The corruption is detected as part of an OP_Prev opcode. 384 # 385 set fd [open corrupt.db r+] 386 fconfigure $fd -translation binary -encoding binary 387 seek $fd [expr 1024*2 + 12] 388 set zCellOffset [read $fd 2] 389 binary scan $zCellOffset S iCellOffset 390 seek $fd [expr 1024*2 + $iCellOffset] 391 set zChild [read $fd 4] 392 binary scan $zChild I iChild 393 seek $fd [expr 1024*($iChild-1)+3] 394 puts -nonewline $fd "\xFFFF" 395 close $fd 396} -test { 397 do_test corrupt2-7.1 { 398 catchsql { SELECT b FROM t1 ORDER BY b DESC } 399 } {1 {database disk image is malformed}} 400} 401 402corruption_test -sqlprep $sqlprep -corrupt { 403 # Set the page-flags of one of the leaf pages of the table B-Tree to 404 # 0x0A (interpreted by SQLite as "leaf page of an index B-Tree"). 405 # 406 set fd [open corrupt.db r+] 407 fconfigure $fd -translation binary -encoding binary 408 seek $fd [expr 1024*1 + 8] 409 set zRightChild [read $fd 4] 410 binary scan $zRightChild I iRightChild 411 seek $fd [expr 1024*($iRightChild-1)] 412 puts -nonewline $fd "\x0A" 413 close $fd 414} -test { 415 do_test corrupt2-8.1 { 416 catchsql { SELECT * FROM t1 WHERE rowid=1000 } 417 } {1 {database disk image is malformed}} 418} 419 420corruption_test -sqlprep { 421 CREATE TABLE t1(a, b, c); CREATE TABLE t8(a, b, c); CREATE TABLE tE(a, b, c); 422 CREATE TABLE t2(a, b, c); CREATE TABLE t9(a, b, c); CREATE TABLE tF(a, b, c); 423 CREATE TABLE t3(a, b, c); CREATE TABLE tA(a, b, c); CREATE TABLE tG(a, b, c); 424 CREATE TABLE t4(a, b, c); CREATE TABLE tB(a, b, c); CREATE TABLE tH(a, b, c); 425 CREATE TABLE t5(a, b, c); CREATE TABLE tC(a, b, c); CREATE TABLE tI(a, b, c); 426 CREATE TABLE t6(a, b, c); CREATE TABLE tD(a, b, c); CREATE TABLE tJ(a, b, c); 427 CREATE TABLE x1(a, b, c); CREATE TABLE x8(a, b, c); CREATE TABLE xE(a, b, c); 428 CREATE TABLE x2(a, b, c); CREATE TABLE x9(a, b, c); CREATE TABLE xF(a, b, c); 429 CREATE TABLE x3(a, b, c); CREATE TABLE xA(a, b, c); CREATE TABLE xG(a, b, c); 430 CREATE TABLE x4(a, b, c); CREATE TABLE xB(a, b, c); CREATE TABLE xH(a, b, c); 431 CREATE TABLE x5(a, b, c); CREATE TABLE xC(a, b, c); CREATE TABLE xI(a, b, c); 432 CREATE TABLE x6(a, b, c); CREATE TABLE xD(a, b, c); CREATE TABLE xJ(a, b, c); 433} -corrupt { 434 set fd [open corrupt.db r+] 435 fconfigure $fd -translation binary -encoding binary 436 seek $fd 108 437 set zRightChild [read $fd 4] 438 binary scan $zRightChild I iRightChild 439 seek $fd [expr 1024*($iRightChild-1)+3] 440 puts -nonewline $fd "\x00\x00" 441 close $fd 442} -test { 443 do_test corrupt2-9.1 { 444 catchsql { SELECT sql FROM sqlite_master } 445 } {1 {database disk image is malformed}} 446} 447 448corruption_test -sqlprep { 449 CREATE TABLE t1(a, b, c); 450 CREATE TABLE t2(a, b, c); 451 PRAGMA writable_schema = 1; 452 UPDATE sqlite_master SET rootpage = NULL WHERE name = 't2'; 453} -test { 454 do_test corrupt2-10.1 { 455 catchsql { SELECT * FROM t2 } 456 } {1 {malformed database schema (t2)}} 457 do_test corrupt2-10.2 { 458 sqlite3_errcode db 459 } {SQLITE_CORRUPT} 460} 461 462corruption_test -sqlprep { 463 PRAGMA auto_vacuum = incremental; 464 CREATE TABLE t1(a INTEGER PRIMARY KEY, b); 465 CREATE TABLE t2(a INTEGER PRIMARY KEY, b); 466 INSERT INTO t1 VALUES(1, randstr(100,100)); 467 INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1; 468 INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1; 469 INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1; 470 INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1; 471 INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1; 472 INSERT INTO t2 SELECT * FROM t1; 473 DELETE FROM t1; 474} -corrupt { 475 set offset [expr [file size corrupt.db] - 1024] 476 hexio_write corrupt.db $offset FF 477 hexio_write corrupt.db 24 12345678 478} -test { 479 do_test corrupt2-11.1 { 480 catchsql { PRAGMA incremental_vacuum } 481 } {1 {database disk image is malformed}} 482} 483corruption_test -sqlprep { 484 PRAGMA auto_vacuum = incremental; 485 CREATE TABLE t1(a INTEGER PRIMARY KEY, b); 486 CREATE TABLE t2(a INTEGER PRIMARY KEY, b); 487 INSERT INTO t1 VALUES(1, randstr(100,100)); 488 INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1; 489 INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1; 490 INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1; 491 INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1; 492 INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1; 493 INSERT INTO t2 SELECT * FROM t1; 494 DELETE FROM t1; 495} -corrupt { 496 set pgno [expr [file size corrupt.db] / 1024] 497 hexio_write corrupt.db [expr 1024+5*($pgno-3)] 03 498 hexio_write corrupt.db 24 12345678 499} -test { 500 do_test corrupt2-12.1 { 501 catchsql { PRAGMA incremental_vacuum } 502 } {1 {database disk image is malformed}} 503} 504 505ifcapable autovacuum { 506 # It is not possible for the last page in a database file to be the 507 # pending-byte page (AKA the locking page). This test verifies that if 508 # an attempt is made to commit a transaction to such an auto-vacuum 509 # database SQLITE_CORRUPT is returned. 510 # 511 corruption_test -tclprep { 512 db eval { 513 PRAGMA auto_vacuum = full; 514 PRAGMA page_size = 1024; 515 CREATE TABLE t1(a INTEGER PRIMARY KEY, b); 516 INSERT INTO t1 VALUES(NULL, randstr(50,50)); 517 } 518 for {set ii 0} {$ii < 10} {incr ii} { 519 db eval { INSERT INTO t1 SELECT NULL, randstr(50,50) FROM t1 } 520 } 521 } -corrupt { 522 do_test corrupt2-13.1 { 523 file size corrupt.db 524 } $::sqlite_pending_byte 525 hexio_write corrupt.db [expr $::sqlite_pending_byte+1023] 00 526 hexio_write corrupt.db 28 00000000 527 } -test { 528 do_test corrupt2-13.2 { 529 file size corrupt.db 530 } [expr $::sqlite_pending_byte + 1024] 531 do_test corrupt2-13.3 { 532 catchsql { DELETE FROM t1 WHERE rowid < 30; } 533 } {1 {database disk image is malformed}} 534 } 535} 536 537finish_test 538