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 } -test { 338 do_test corrupt2-6.4 { 339 catchsql { 340 BEGIN EXCLUSIVE; 341 COMMIT; 342 } 343 } {1 {database disk image is malformed}} 344 } 345} 346 347 348set sqlprep { 349 PRAGMA auto_vacuum = 0; 350 PRAGMA page_size = 1024; 351 CREATE TABLE t1(a INTEGER PRIMARY KEY, b); 352 CREATE INDEX i1 ON t1(b); 353 INSERT INTO t1 VALUES(1, randomblob(50)); 354 INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1; 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} 361 362corruption_test -sqlprep $sqlprep -corrupt { 363 # Set the page-flags of one of the leaf pages of the index B-Tree to 364 # 0x0D (interpreted by SQLite as "leaf page of a table B-Tree"). 365 # 366 set fd [open corrupt.db r+] 367 fconfigure $fd -translation binary -encoding binary 368 seek $fd [expr 1024*2 + 8] 369 set zRightChild [read $fd 4] 370 binary scan $zRightChild I iRightChild 371 seek $fd [expr 1024*($iRightChild-1)] 372 puts -nonewline $fd "\x0D" 373 close $fd 374} -test { 375 do_test corrupt2-7.1 { 376 catchsql { SELECT b FROM t1 ORDER BY b ASC } 377 } {1 {database disk image is malformed}} 378} 379 380corruption_test -sqlprep $sqlprep -corrupt { 381 # Mess up the page-header of one of the leaf pages of the index B-Tree. 382 # The corruption is detected as part of an OP_Prev opcode. 383 # 384 set fd [open corrupt.db r+] 385 fconfigure $fd -translation binary -encoding binary 386 seek $fd [expr 1024*2 + 12] 387 set zCellOffset [read $fd 2] 388 binary scan $zCellOffset S iCellOffset 389 seek $fd [expr 1024*2 + $iCellOffset] 390 set zChild [read $fd 4] 391 binary scan $zChild I iChild 392 seek $fd [expr 1024*($iChild-1)+3] 393 puts -nonewline $fd "\xFFFF" 394 close $fd 395} -test { 396 do_test corrupt2-7.1 { 397 catchsql { SELECT b FROM t1 ORDER BY b DESC } 398 } {1 {database disk image is malformed}} 399} 400 401corruption_test -sqlprep $sqlprep -corrupt { 402 # Set the page-flags of one of the leaf pages of the table B-Tree to 403 # 0x0A (interpreted by SQLite as "leaf page of an index B-Tree"). 404 # 405 set fd [open corrupt.db r+] 406 fconfigure $fd -translation binary -encoding binary 407 seek $fd [expr 1024*1 + 8] 408 set zRightChild [read $fd 4] 409 binary scan $zRightChild I iRightChild 410 seek $fd [expr 1024*($iRightChild-1)] 411 puts -nonewline $fd "\x0A" 412 close $fd 413} -test { 414 do_test corrupt2-8.1 { 415 catchsql { SELECT * FROM t1 WHERE rowid=1000 } 416 } {1 {database disk image is malformed}} 417} 418 419corruption_test -sqlprep { 420 CREATE TABLE t1(a, b, c); CREATE TABLE t8(a, b, c); CREATE TABLE tE(a, b, c); 421 CREATE TABLE t2(a, b, c); CREATE TABLE t9(a, b, c); CREATE TABLE tF(a, b, c); 422 CREATE TABLE t3(a, b, c); CREATE TABLE tA(a, b, c); CREATE TABLE tG(a, b, c); 423 CREATE TABLE t4(a, b, c); CREATE TABLE tB(a, b, c); CREATE TABLE tH(a, b, c); 424 CREATE TABLE t5(a, b, c); CREATE TABLE tC(a, b, c); CREATE TABLE tI(a, b, c); 425 CREATE TABLE t6(a, b, c); CREATE TABLE tD(a, b, c); CREATE TABLE tJ(a, b, c); 426 CREATE TABLE x1(a, b, c); CREATE TABLE x8(a, b, c); CREATE TABLE xE(a, b, c); 427 CREATE TABLE x2(a, b, c); CREATE TABLE x9(a, b, c); CREATE TABLE xF(a, b, c); 428 CREATE TABLE x3(a, b, c); CREATE TABLE xA(a, b, c); CREATE TABLE xG(a, b, c); 429 CREATE TABLE x4(a, b, c); CREATE TABLE xB(a, b, c); CREATE TABLE xH(a, b, c); 430 CREATE TABLE x5(a, b, c); CREATE TABLE xC(a, b, c); CREATE TABLE xI(a, b, c); 431 CREATE TABLE x6(a, b, c); CREATE TABLE xD(a, b, c); CREATE TABLE xJ(a, b, c); 432} -corrupt { 433 set fd [open corrupt.db r+] 434 fconfigure $fd -translation binary -encoding binary 435 seek $fd 108 436 set zRightChild [read $fd 4] 437 binary scan $zRightChild I iRightChild 438 seek $fd [expr 1024*($iRightChild-1)+3] 439 puts -nonewline $fd "\x00\x00" 440 close $fd 441} -test { 442 do_test corrupt2-9.1 { 443 catchsql { SELECT sql FROM sqlite_master } 444 } {1 {database disk image is malformed}} 445} 446 447corruption_test -sqlprep { 448 CREATE TABLE t1(a, b, c); 449 CREATE TABLE t2(a, b, c); 450 PRAGMA writable_schema = 1; 451 UPDATE sqlite_master SET rootpage = NULL WHERE name = 't2'; 452} -test { 453 do_test corrupt2-10.1 { 454 catchsql { SELECT * FROM t2 } 455 } {1 {malformed database schema (t2)}} 456 do_test corrupt2-10.2 { 457 sqlite3_errcode db 458 } {SQLITE_CORRUPT} 459} 460 461corruption_test -sqlprep { 462 PRAGMA auto_vacuum = incremental; 463 CREATE TABLE t1(a INTEGER PRIMARY KEY, b); 464 CREATE TABLE t2(a INTEGER PRIMARY KEY, b); 465 INSERT INTO t1 VALUES(1, randstr(100,100)); 466 INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1; 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 t2 SELECT * FROM t1; 472 DELETE FROM t1; 473} -corrupt { 474 set offset [expr [file size corrupt.db] - 1024] 475 hexio_write corrupt.db $offset FF 476 hexio_write corrupt.db 24 12345678 477} -test { 478 do_test corrupt2-11.1 { 479 catchsql { PRAGMA incremental_vacuum } 480 } {1 {database disk image is malformed}} 481} 482corruption_test -sqlprep { 483 PRAGMA auto_vacuum = incremental; 484 CREATE TABLE t1(a INTEGER PRIMARY KEY, b); 485 CREATE TABLE t2(a INTEGER PRIMARY KEY, b); 486 INSERT INTO t1 VALUES(1, randstr(100,100)); 487 INSERT INTO t1 SELECT NULL, randstr(100,100) FROM t1; 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 t2 SELECT * FROM t1; 493 DELETE FROM t1; 494} -corrupt { 495 set pgno [expr [file size corrupt.db] / 1024] 496 hexio_write corrupt.db [expr 1024+5*($pgno-3)] 03 497 hexio_write corrupt.db 24 12345678 498} -test { 499 do_test corrupt2-12.1 { 500 catchsql { PRAGMA incremental_vacuum } 501 } {1 {database disk image is malformed}} 502} 503 504ifcapable autovacuum { 505 # It is not possible for the last page in a database file to be the 506 # pending-byte page (AKA the locking page). This test verifies that if 507 # an attempt is made to commit a transaction to such an auto-vacuum 508 # database SQLITE_CORRUPT is returned. 509 # 510 corruption_test -tclprep { 511 db eval { 512 PRAGMA auto_vacuum = full; 513 PRAGMA page_size = 1024; 514 CREATE TABLE t1(a INTEGER PRIMARY KEY, b); 515 INSERT INTO t1 VALUES(NULL, randstr(50,50)); 516 } 517 for {set ii 0} {$ii < 10} {incr ii} { 518 db eval { INSERT INTO t1 SELECT NULL, randstr(50,50) FROM t1 } 519 } 520 } -corrupt { 521 do_test corrupt2-13.1 { 522 file size corrupt.db 523 } $::sqlite_pending_byte 524 hexio_write corrupt.db [expr $::sqlite_pending_byte+1023] 00 525 } -test { 526 do_test corrupt2-13.2 { 527 file size corrupt.db 528 } [expr $::sqlite_pending_byte + 1024] 529 do_test corrupt2-13.3 { 530 catchsql { DELETE FROM t1 WHERE rowid < 30; } 531 } {1 {database disk image is malformed}} 532 } 533} 534 535finish_test 536