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.18 2008/09/29 11:49:48 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 array set A $args 237 238 catch {db close} 239 file delete -force corrupt.db 240 file delete -force corrupt.db-journal 241 242 sqlite3 db corrupt.db 243 db eval $A(-sqlprep) 244 db close 245 246 eval $A(-corrupt) 247 248 sqlite3 db corrupt.db 249 eval $A(-test) 250} 251 252ifcapable autovacuum { 253 # The tests within this block - corrupt2-6.* - aim to test corruption 254 # detection within an incremental-vacuum. When an incremental-vacuum 255 # step is executed, the last non-free page of the database file is 256 # moved into a free space in the body of the file. After doing so, 257 # the page reference in the parent page must be updated to refer 258 # to the new location. These tests test the outcome of corrupting 259 # that page reference before performing the incremental vacuum. 260 # 261 262 # The last page in the database page is the second page 263 # in an overflow chain. 264 # 265 corruption_test -sqlprep { 266 PRAGMA auto_vacuum = incremental; 267 PRAGMA page_size = 1024; 268 CREATE TABLE t1(a, b); 269 INSERT INTO t1 VALUES(1, randomblob(2500)); 270 INSERT INTO t1 VALUES(2, randomblob(2500)); 271 DELETE FROM t1 WHERE a = 1; 272 } -corrupt { 273 hexio_write corrupt.db [expr 1024*5] 00000008 274 } -test { 275 do_test corrupt2-6.1 { 276 catchsql { pragma incremental_vacuum = 1 } 277 } {1 {database disk image is malformed}} 278 } 279 280 # The last page in the database page is a non-root b-tree page. 281 # 282 corruption_test -sqlprep { 283 PRAGMA auto_vacuum = incremental; 284 PRAGMA page_size = 1024; 285 CREATE TABLE t1(a INTEGER PRIMARY KEY, b); 286 INSERT INTO t1 VALUES(1, randomblob(2500)); 287 INSERT INTO t1 VALUES(2, randomblob(50)); 288 INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1; 289 INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1; 290 INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1; 291 INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1; 292 DELETE FROM t1 WHERE a = 1; 293 } -corrupt { 294 hexio_write corrupt.db [expr 1024*2 + 8] 00000009 295 } -test { 296 do_test corrupt2-6.2 { 297 catchsql { pragma incremental_vacuum = 1 } 298 } {1 {database disk image is malformed}} 299 } 300 301 # Set up a pointer-map entry so that the last page of the database 302 # file appears to be a b-tree root page. This should be detected 303 # as corruption. 304 # 305 corruption_test -sqlprep { 306 PRAGMA auto_vacuum = incremental; 307 PRAGMA page_size = 1024; 308 CREATE TABLE t1(a INTEGER PRIMARY KEY, b); 309 INSERT INTO t1 VALUES(1, randomblob(2500)); 310 INSERT INTO t1 VALUES(2, randomblob(2500)); 311 INSERT INTO t1 VALUES(3, randomblob(2500)); 312 DELETE FROM t1 WHERE a = 1; 313 } -corrupt { 314 set nPage [expr [file size corrupt.db] / 1024] 315 hexio_write corrupt.db [expr 1024 + ($nPage-3)*5] 010000000 316 } -test { 317 do_test corrupt2-6.3 { 318 catchsql { pragma incremental_vacuum = 1 } 319 } {1 {database disk image is malformed}} 320 } 321 322 corruption_test -sqlprep { 323 PRAGMA auto_vacuum = 1; 324 PRAGMA page_size = 1024; 325 CREATE TABLE t1(a INTEGER PRIMARY KEY, b); 326 INSERT INTO t1 VALUES(1, randomblob(2500)); 327 DELETE FROM t1 WHERE a = 1; 328 } -corrupt { 329 set nAppend [expr 1024*207 - [file size corrupt.db]] 330 set fd [open corrupt.db r+] 331 seek $fd 0 end 332 puts -nonewline $fd [string repeat x $nAppend] 333 close $fd 334 } -test { 335 do_test corrupt2-6.4 { 336 catchsql { 337 BEGIN EXCLUSIVE; 338 COMMIT; 339 } 340 } {1 {database disk image is malformed}} 341 } 342} 343 344 345set sqlprep { 346 PRAGMA auto_vacuum = 0; 347 PRAGMA page_size = 1024; 348 CREATE TABLE t1(a INTEGER PRIMARY KEY, b); 349 CREATE INDEX i1 ON t1(b); 350 INSERT INTO t1 VALUES(1, randomblob(50)); 351 INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1; 352 INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1; 353 INSERT INTO t1 SELECT NULL, randomblob(50) FROM t1; 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} 358 359corruption_test -sqlprep $sqlprep -corrupt { 360 # Set the page-flags of one of the leaf pages of the index B-Tree to 361 # 0x0D (interpreted by SQLite as "leaf page of a table B-Tree"). 362 # 363 set fd [open corrupt.db r+] 364 fconfigure $fd -translation binary -encoding binary 365 seek $fd [expr 1024*2 + 8] 366 set zRightChild [read $fd 4] 367 binary scan $zRightChild I iRightChild 368 seek $fd [expr 1024*($iRightChild-1)] 369 puts -nonewline $fd "\x0D" 370 close $fd 371} -test { 372 do_test corrupt2-7.1 { 373 catchsql { SELECT b FROM t1 ORDER BY b ASC } 374 } {1 {database disk image is malformed}} 375} 376 377corruption_test -sqlprep $sqlprep -corrupt { 378 # Mess up the page-header of one of the leaf pages of the index B-Tree. 379 # The corruption is detected as part of an OP_Prev opcode. 380 # 381 set fd [open corrupt.db r+] 382 fconfigure $fd -translation binary -encoding binary 383 seek $fd [expr 1024*2 + 12] 384 set zCellOffset [read $fd 2] 385 binary scan $zCellOffset S iCellOffset 386 seek $fd [expr 1024*2 + $iCellOffset] 387 set zChild [read $fd 4] 388 binary scan $zChild I iChild 389 seek $fd [expr 1024*($iChild-1)+3] 390 puts -nonewline $fd "\xFFFF" 391 close $fd 392} -test { 393 do_test corrupt2-7.1 { 394 catchsql { SELECT b FROM t1 ORDER BY b DESC } 395 } {1 {database disk image is malformed}} 396} 397 398corruption_test -sqlprep $sqlprep -corrupt { 399 # Set the page-flags of one of the leaf pages of the table B-Tree to 400 # 0x0A (interpreted by SQLite as "leaf page of an index B-Tree"). 401 # 402 set fd [open corrupt.db r+] 403 fconfigure $fd -translation binary -encoding binary 404 seek $fd [expr 1024*1 + 8] 405 set zRightChild [read $fd 4] 406 binary scan $zRightChild I iRightChild 407 seek $fd [expr 1024*($iRightChild-1)] 408 puts -nonewline $fd "\x0A" 409 close $fd 410} -test { 411 do_test corrupt2-8.1 { 412 catchsql { SELECT * FROM t1 WHERE rowid=1000 } 413 } {1 {database disk image is malformed}} 414} 415 416corruption_test -sqlprep { 417 CREATE TABLE t1(a, b, c); CREATE TABLE t8(a, b, c); CREATE TABLE tE(a, b, c); 418 CREATE TABLE t2(a, b, c); CREATE TABLE t9(a, b, c); CREATE TABLE tF(a, b, c); 419 CREATE TABLE t3(a, b, c); CREATE TABLE tA(a, b, c); CREATE TABLE tG(a, b, c); 420 CREATE TABLE t4(a, b, c); CREATE TABLE tB(a, b, c); CREATE TABLE tH(a, b, c); 421 CREATE TABLE t5(a, b, c); CREATE TABLE tC(a, b, c); CREATE TABLE tI(a, b, c); 422 CREATE TABLE t6(a, b, c); CREATE TABLE tD(a, b, c); CREATE TABLE tJ(a, b, c); 423 CREATE TABLE x1(a, b, c); CREATE TABLE x8(a, b, c); CREATE TABLE xE(a, b, c); 424 CREATE TABLE x2(a, b, c); CREATE TABLE x9(a, b, c); CREATE TABLE xF(a, b, c); 425 CREATE TABLE x3(a, b, c); CREATE TABLE xA(a, b, c); CREATE TABLE xG(a, b, c); 426 CREATE TABLE x4(a, b, c); CREATE TABLE xB(a, b, c); CREATE TABLE xH(a, b, c); 427 CREATE TABLE x5(a, b, c); CREATE TABLE xC(a, b, c); CREATE TABLE xI(a, b, c); 428 CREATE TABLE x6(a, b, c); CREATE TABLE xD(a, b, c); CREATE TABLE xJ(a, b, c); 429} -corrupt { 430 set fd [open corrupt.db r+] 431 fconfigure $fd -translation binary -encoding binary 432 seek $fd 108 433 set zRightChild [read $fd 4] 434 binary scan $zRightChild I iRightChild 435 seek $fd [expr 1024*($iRightChild-1)+3] 436 puts -nonewline $fd "\x00\x00" 437 close $fd 438} -test { 439 do_test corrupt2-9.1 { 440 catchsql { SELECT sql FROM sqlite_master } 441 } {1 {database disk image is malformed}} 442} 443 444corruption_test -sqlprep { 445 CREATE TABLE t1(a, b, c); 446 CREATE TABLE t2(a, b, c); 447 PRAGMA writable_schema = 1; 448 UPDATE sqlite_master SET rootpage = NULL WHERE name = 't2'; 449} -test { 450 do_test corrupt2-10.1 { 451 catchsql { SELECT * FROM t2 } 452 } {1 {malformed database schema (t2)}} 453 do_test corrupt2-10.2 { 454 sqlite3_errcode db 455 } {SQLITE_CORRUPT} 456} 457 458finish_test 459